Skip to content

Commit 4c31290

Browse files
authored
Merge pull request #123 from Omikhleia/feat-experiment-hook-symbols
feat(djot): Support programmatic symbols (extensible via Lua)
2 parents 8ce6f42 + 1df7804 commit 4c31290

File tree

3 files changed

+103
-61
lines changed

3 files changed

+103
-61
lines changed

examples/sile-and-djot.dj

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,11 @@ Actually, let's start with attributes, as they have the same general syntax for
6565

6666
Attributes are put inside curly braces.
6767
On inline elements, attributes are placed immediately after the element they are attached to, with no intervening whitespace.
68-
They may contain line breaks, and may be "stacked," in which case they will be combined.
68+
They may contain line breaks, and may be "stacked," in which case they are combined.
6969

7070
To attach attributes to a block-level element, put the attributes on the line immediately before the block.
71-
Block attributes have the same syntax as inline attributes, but they must fit on one line. Repeated attribute specifiers can also be used, and the attributes will accumulate.
71+
Block attributes have the same syntax as inline attributes, but they must fit on one line.
72+
Repeated attribute specifiers can also be used, and the attributes accumulate.
7273

7374
Inside the curly braces, the following syntax is possible:
7475

@@ -78,7 +79,7 @@ Inside the curly braces, the following syntax is possible:
7879

7980
- `.foo` specifies a class, for styling purposes.
8081

81-
Multiple classes may be given in this way; they will be combined.
82+
Multiple classes may be given in this way.
8283

8384
- `key="value"` or `key=value` specifies a key-value attribute.
8485

@@ -303,7 +304,7 @@ Attributes are optional, and are passed through to the underlying SILE package.
303304
You can notably specify the required image width and/or height, as done just above, by appending the `{width=... height=...}` attributes --- Note that any unit system supported by SILE is accepted, as well as percentages (see §[](#final-notes-units)).
304305
There are actually two kinds of image syntax: (direct) inline images and (indirect) reference images.
305306
Above, we used the former kind.
306-
The reference images are defined elsewhere in the document, and are referenced by their label:
307+
The reference images are defined elsewhere in the document, referred to by their label.
307308

308309
{custom-style=CodeBlock}
309310
:::
@@ -321,11 +322,11 @@ Direct (inline) link's attributes override reference's attributes.
321322
{#djot-captioned-images}
322323
#### Implicit captioned figures
323324

324-
An image with nonempty caption (i.e. "alternate" text), occurring alone by itself in a paragraph, will be rendered as a figure with a caption, as actually seen above.
325+
An image with nonempty caption (i.e. "alternate" text), occurring alone by itself in a paragraph, is rendered as a figure with a caption, as actually seen above.
325326
Otherwise, the caption is ignored.
326327

327-
If your document class or previously loaded packages provide a `captioned-figure` environment, it will be wrapped around the image (and it is then assumed to take care of the caption, i.e. to extract and display it appropriately).
328-
Specifically, if the *resilient* book class is used, the caption will be numbered by default, and added to the list of figures. Specify `.unnumbered`, and `.notoc` respectively, if you do not want it.
328+
If your document class or previously loaded packages provide a `captioned-figure` environment, it is wrapped around the image (and it is then assumed to take care of the caption, i.e. to extract and display it appropriately).
329+
Specifically, if the *resilient* book class is used, the caption is numbered by default, and added to the list of figures. Specify `.unnumbered`, and `.notoc` respectively, if you do not want it.
329330
Otherwise, the converter uses its own fallback method.
330331

331332
#### Extended image types
@@ -415,7 +416,7 @@ Another link to [the SILE website][sile].
415416
[sile]: https://sile-typesetter.org/
416417

417418
The reference label should be defined somewhere in the document.
418-
If the label is empty, then the link text will be taken to be the reference label as well as the link text.
419+
If the label is empty, then the link text is taken to be the reference label as well as the link text.
419420

420421
{#djot-cross-references}
421422
#### Cross-references
@@ -431,7 +432,7 @@ Empty local links (that is, without inline display content) are interpreted as c
431432
By default, they are resolved to the closest numbering item, whatever that might be in the hierarchical structure of your document.
432433
A pseudo-class attribute may be used to override the default behavior and specify which type
433434
of reference is expected (page number, section number or title text).
434-
Thus, the above example was obtained from the following input:
435+
Thus, the above example was obtained with:
435436

436437
{custom-style=CodeBlock}
437438
:::
@@ -626,7 +627,7 @@ The contents of the block quote are parsed as block-level content.
626627
>
627628
> > Such quotes can be nested.
628629

629-
If your document class or previously loaded packages provide a `blockquote` environment, it will be used.
630+
If your document class or previously loaded packages provide a `blockquote` environment, it is used.
630631
Otherwise, the converter uses its own fallback method, with hard-coded styling.
631632

632633
#### Attributed quotes (epigraphs)
@@ -896,7 +897,7 @@ Djot supports the "pipe table" syntax, with its own way for marking the optional
896897
:::
897898

898899

899-
When using the *resilient* classes, the caption will be numbered by default, and added to the list of tables.
900+
When using the *resilient* classes, the caption is numbered by default, and added to the list of tables.
900901
Specify `.unnumbered`, and `.notoc` respectively, as table attributes, if you do not want it.
901902

902903
### Code blocks
@@ -946,7 +947,7 @@ This is a very naive approach to syntax-highlighting, until the converter possib
946947

947948
#### Rendered code blocks
948949

949-
If the converter knows how to render the content of a code block, it will do so by default.
950+
If the converter knows how to render the content of a code block, it does so by default.
950951
The `render` attribute can be set to `false` to prevent this behavior, and enforce the content to be rendered as raw verbatim text.
951952

952953
: Mardown and Djot code blocks
@@ -1027,7 +1028,7 @@ The `render` attribute can be set to `false` to prevent this behavior, and enfor
10271028

10281029
: Pie charts
10291030

1030-
Likewise, if you installed the optional *piecharts.sile* collection, then code blocks marked as "piechart" are automatically rendered, with all other attributes are passed to the underlying processor.
1031+
Likewise, if you installed the optional *piecharts.sile* collection, then code blocks marked as "piechart" are automatically rendered, with all other attributes passed to the underlying processor.
10311032
Consider the following code block, consisting in a CSV table...
10321033

10331034
{custom-style=CodeBlock}
@@ -1331,22 +1332,24 @@ Since it's possible to have unused footnote definitions, let's craft one as show
13311332
When encountering a symbol, this converter looks for such a footnote and expands its content.
13321333
It works with inline elements as shown above, but also with full blocks, provided the symbol is the only element in a paragraph of its own.
13331334

1334-
Of course, these pseudo-footnotes[^djot-pseudo-footnotes] can in turn contain symbols, which will get replaced too.
1335+
Of course, these pseudo-footnotes[^djot-pseudo-footnotes] can in turn contain symbols, which get replaced too.
13351336

13361337

13371338
[^djot-pseudo-footnotes]: You may still use them as regular footnotes.
13381339
Whether this is a good idea is another question...
13391340

13401341
### Predefined symbols
13411342

1342-
This converter also comes with a few symbols predefined.
1343+
This converter also comes with a few symbols predefined.[^djot-programmatic-symbols]
13431344

13441345
- `:_TOC_:` must stand alone in its own paragraph. It inserts a table of contents.
13451346
Attributes on the symbol are passed through, e.g. `:_TOC_:{depth=3}`.
13461347
- `:U+xxxx:` (where `xxxx` is a hexadecimal value in upper case) is replaced by the corresponding Unicode character --- `:U+2122:` gives :U+2122:.
13471348

13481349
The above variable substitution mechanism has precedence over these symbols, allowing you to possibly override them.
13491350

1351+
[^djot-programmatic-symbols]: For 3d-party class and package designers, a Lua method is provided to add their own symbols.
1352+
13501353
{#djot-metadata-symbols}
13511354
### Contextual metadata
13521355

inputters/djot.lua

Lines changed: 5 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -682,25 +682,6 @@ function Renderer.en_dash (_)
682682
return("")
683683
end
684684

685-
local predefinedSymbols = {
686-
_TOC_ = {
687-
standalone = true,
688-
render = function (node)
689-
return createCommand("markdown:internal:toc", node.attr)
690-
end
691-
},
692-
_FANCYTOC_ = { -- of course, requires having installed the fancytoc.sile module
693-
-- We are not going to check that here, so I won't document it.
694-
standalone = true,
695-
render = function (node)
696-
return {
697-
createCommand("use", { module = "packages.fancytoc" }),
698-
createCommand("fancytableofcontents", node.attr)
699-
}
700-
end
701-
},
702-
}
703-
704685
function Renderer:getUserDefinedSymbol (label, node_fake_metadata)
705686
local content
706687
if self.metadata[label] then -- use memoized
@@ -758,33 +739,11 @@ function Renderer:symbol (node)
758739
end
759740
return text
760741
end
761-
-- Let's finally look for predefined symbols
762-
local symbol = predefinedSymbols[node.alias]
763-
if symbol then
764-
if symbol.standalone and not node._standalone_ then
765-
SU.error("Cannot use " .. label .." as inline content")
766-
end
767-
return symbol.render(node)
768-
end
769-
local pos = node_pos(node)
770-
if node.alias:match("U%+[0-9A-F]+") then
771-
local content = {
772-
createCommand("use", { module = "packages.unichar" }),
773-
createCommand("unichar", {}, node.alias, pos)
774-
}
775-
if node.attr then
776-
-- Add a span for attributes
777-
return createCommand("markdown:internal:span", node.attr, content, pos)
778-
end
779-
return content
780-
end
781-
SU.warn("Symbol '" .. node.alias .. "' was not expanded (no corresponding metadata found)")
782-
local text = ":" .. node.alias .. ":"
783-
if node.attr then
784-
-- Add a span for attributes
785-
return createCommand("markdown:internal:span", node.attr, text, pos)
786-
end
787-
return text
742+
-- Not a metadata symbol, pass our internal properties and delegate
743+
local options = node.attr or {}
744+
options._symbol_ = node.alias
745+
options._standalone_ = node._standalone_
746+
return createCommand("markdown:internal:symbol", options, nil, node_pos(node))
788747
end
789748
end
790749

packages/markdown/commands.lua

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,20 @@ end
121121

122122
function package:_init (_)
123123
base._init(self)
124+
if not SILE.scratch._markdown_commands then
125+
-- NOTE:
126+
-- I don't like those scratch variables, but package reinstancing
127+
-- may occur in SILE. I never liked it, but it still occurs in SILE 0.15.5
128+
-- when silex-x is not present, so we have to deal with it.
129+
-- For putative readers:
130+
-- load the djot package, cause you want to use Djot.
131+
-- load the markdown package, cause you want to use Markdown too.
132+
-- Both load the markdown.commands package.
133+
-- Guess what? The markdown.commands package is instanciated twice.
134+
-- It's supposed to be a SILE feature...
135+
SILE.scratch._markdown_commands = {}
136+
end
137+
self.predefinedSymbols = SILE.scratch._markdown_commands
124138

125139
-- Only load low-level packages (= utilities)
126140
-- The class should be responsible for loading the appropriate higher-level
@@ -151,6 +165,40 @@ function package:_init (_)
151165
self:loadPackage("resilient.epigraph")
152166
self:loadPackage("resilient.defn")
153167
end
168+
169+
-- Register some predefined symbols
170+
-- Later we'll have packages or classes possibly register their own
171+
-- predefined symbols.
172+
self:registerSymbol("_TOC_", true, function (options)
173+
return {
174+
createCommand("markdown:internal:toc", options),
175+
}
176+
end)
177+
self:registerSymbol("_FANCYTOC_", true, function (options)
178+
-- Of course, it requires having installed the fancytoc.sile module
179+
-- We are not going to check that here, so I won't document it.
180+
return {
181+
createCommand("use", { module = "packages.fancytoc" }),
182+
createCommand("fancytableofcontents", options),
183+
}
184+
end)
185+
end
186+
187+
-- Register a predefined symbol for use in Djot (as of now)
188+
-- The symbol is a leaf inline command that will be expanded when encountered.
189+
-- The symbol is registered with
190+
-- - a name
191+
-- - a boolean indicating if must be standalone (i.e. alone at block-level)
192+
-- - a function that will be called to render the symbol
193+
-- The function will receive as options the attributes set on the symbol,
194+
-- and must return a table of AST elements.
195+
-- Note that a span will also be created around an inline symbol if it has
196+
-- attributes, so styling can be applied to the symbol.
197+
function package:registerSymbol(name, standalone, render)
198+
-- Multiple package reinstancing is may occur in SILE, see comment above
199+
-- on scratch variables... So we do not warn if a symbol is already registered,
200+
-- and just overwrite it silently.
201+
self.predefinedSymbols[name] = { standalone = standalone, render = render }
154202
end
155203

156204
local UsualSectioning = { "part", "chapter", "section", "subsection", "subsubsection" }
@@ -777,6 +825,36 @@ Please consider using a resilient-compatible class!]])
777825
end
778826
end, "Definition item in Markdown (internal)")
779827

828+
self:registerCommand("markdown:internal:symbol", function (options, _)
829+
local symbol = SU.required(options, "_symbol_", "symbol")
830+
local standalone = SU.boolean(options._standalone_, false)
831+
local content
832+
local predefined = self.predefinedSymbols[symbol]
833+
if predefined then
834+
if predefined.standalone and not standalone then
835+
SU.error("Cannot use " .. symbol .. " as inline content")
836+
end
837+
content = predefined.render(options)
838+
elseif symbol:match("^U%+[0-9A-F]+$") then
839+
content = {
840+
createCommand("use", { module = "packages.unichar" }),
841+
createCommand("unichar", {}, symbol)
842+
}
843+
else
844+
SU.warn("Symbol '" .. symbol .. "' was not expanded (no corresponding metadata found)")
845+
local text = ":" .. symbol .. ":"
846+
content = { text }
847+
end
848+
options._symbol_ = nil
849+
options._standalone_ = nil
850+
if next(options) and not standalone then
851+
-- Add a span for attributes
852+
SILE.call("markdown:internal:span", options, content);
853+
else
854+
SILE.process(content)
855+
end
856+
end, "Symbol in Djot (internal)")
857+
780858
-- B. Fallback commands
781859

782860
self:registerCommand("markdown:fallback:blockquote", function (_, content)
@@ -952,6 +1030,8 @@ package.documentation = [[\begin{document}
9521030
A helper package for Markdown and Djot processing, providing common hooks and fallback commands.
9531031
9541032
It is not intended to be used alone.
1033+
1034+
For class or package designers, it provides a method \code{registerSymbol} to define their own symbols, which can be used in the Djot syntax.
9551035
\end{document}]]
9561036

9571037
return package

0 commit comments

Comments
 (0)