texsmith 0.0.2.dev0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- texsmith/__init__.py +107 -0
- texsmith/_alias.py +59 -0
- texsmith/adapters/__init__.py +6 -0
- texsmith/adapters/docker.py +258 -0
- texsmith/adapters/handlers/__init__.py +3 -0
- texsmith/adapters/handlers/_assets.py +363 -0
- texsmith/adapters/handlers/_helpers.py +62 -0
- texsmith/adapters/handlers/_mermaid.py +122 -0
- texsmith/adapters/handlers/admonitions.py +267 -0
- texsmith/adapters/handlers/basic.py +228 -0
- texsmith/adapters/handlers/blocks.py +851 -0
- texsmith/adapters/handlers/code.py +309 -0
- texsmith/adapters/handlers/inline.py +937 -0
- texsmith/adapters/handlers/links.py +239 -0
- texsmith/adapters/handlers/media.py +380 -0
- texsmith/adapters/latex/__init__.py +10 -0
- texsmith/adapters/latex/engines/__init__.py +726 -0
- texsmith/adapters/latex/engines/latex/__init__.py +26 -0
- texsmith/adapters/latex/engines/latex/log.py +720 -0
- texsmith/adapters/latex/engines/latex/runner.py +28 -0
- texsmith/adapters/latex/engines/tectonic/__init__.py +38 -0
- texsmith/adapters/latex/formatter.py +297 -0
- texsmith/adapters/latex/latexmk.py +148 -0
- texsmith/adapters/latex/partials/acronym.tex +1 -0
- texsmith/adapters/latex/partials/add.tex +1 -0
- texsmith/adapters/latex/partials/addition.tex +1 -0
- texsmith/adapters/latex/partials/blockquote.tex +3 -0
- texsmith/adapters/latex/partials/callout.tex +3 -0
- texsmith/adapters/latex/partials/choices.tex +5 -0
- texsmith/adapters/latex/partials/citation.tex +1 -0
- texsmith/adapters/latex/partials/codeblock.tex +7 -0
- texsmith/adapters/latex/partials/codeblock_listings.tex +5 -0
- texsmith/adapters/latex/partials/codeblock_pygments.tex +5 -0
- texsmith/adapters/latex/partials/codeblock_verbatim.tex +12 -0
- texsmith/adapters/latex/partials/codeinline.tex +1 -0
- texsmith/adapters/latex/partials/codeinlinett.tex +1 -0
- texsmith/adapters/latex/partials/comment.tex +1 -0
- texsmith/adapters/latex/partials/del.tex +1 -0
- texsmith/adapters/latex/partials/deletion.tex +1 -0
- texsmith/adapters/latex/partials/description_list.tex +5 -0
- texsmith/adapters/latex/partials/enquote.tex +1 -0
- texsmith/adapters/latex/partials/epigraph.tex +1 -0
- texsmith/adapters/latex/partials/exercises_solutions.tex +7 -0
- texsmith/adapters/latex/partials/figure.tex +33 -0
- texsmith/adapters/latex/partials/figure_tcolorbox.tex +15 -0
- texsmith/adapters/latex/partials/footnote.tex +3 -0
- texsmith/adapters/latex/partials/glossary.tex +1 -0
- texsmith/adapters/latex/partials/heading.tex +14 -0
- texsmith/adapters/latex/partials/highlight.tex +25 -0
- texsmith/adapters/latex/partials/horizontal_rule.tex +1 -0
- texsmith/adapters/latex/partials/href.tex +1 -0
- texsmith/adapters/latex/partials/icon.tex +1 -0
- texsmith/adapters/latex/partials/include.tex +1 -0
- texsmith/adapters/latex/partials/index.tex +1 -0
- texsmith/adapters/latex/partials/italic.tex +1 -0
- texsmith/adapters/latex/partials/keystroke.tex +46 -0
- texsmith/adapters/latex/partials/label.tex +1 -0
- texsmith/adapters/latex/partials/list_acronyms.tex +5 -0
- texsmith/adapters/latex/partials/list_glossary.tex +8 -0
- texsmith/adapters/latex/partials/multicolumn.tex +3 -0
- texsmith/adapters/latex/partials/ordered_list.tex +5 -0
- texsmith/adapters/latex/partials/pagestyle.tex +1 -0
- texsmith/adapters/latex/partials/ref.tex +1 -0
- texsmith/adapters/latex/partials/regex.tex +1 -0
- texsmith/adapters/latex/partials/smallcaps.tex +1 -0
- texsmith/adapters/latex/partials/strikethrough.tex +1 -0
- texsmith/adapters/latex/partials/strong.tex +1 -0
- texsmith/adapters/latex/partials/subscript.tex +1 -0
- texsmith/adapters/latex/partials/substitution.tex +1 -0
- texsmith/adapters/latex/partials/superscript.tex +1 -0
- texsmith/adapters/latex/partials/tabbed.tex +3 -0
- texsmith/adapters/latex/partials/table.tex +48 -0
- texsmith/adapters/latex/partials/underline.tex +1 -0
- texsmith/adapters/latex/partials/unordered_list.tex +5 -0
- texsmith/adapters/latex/partials/url.tex +1 -0
- texsmith/adapters/latex/pygments.py +98 -0
- texsmith/adapters/latex/pyxindy.py +64 -0
- texsmith/adapters/latex/renderer.py +220 -0
- texsmith/adapters/latex/tectonic.py +366 -0
- texsmith/adapters/latex/utils.py +86 -0
- texsmith/adapters/markdown/__init__.py +342 -0
- texsmith/adapters/plugins/__init__.py +8 -0
- texsmith/adapters/plugins/material.py +174 -0
- texsmith/adapters/plugins/snippet.py +1734 -0
- texsmith/adapters/transformers/__init__.py +122 -0
- texsmith/adapters/transformers/base.py +140 -0
- texsmith/adapters/transformers/strategies.py +1456 -0
- texsmith/adapters/transformers/utils.py +59 -0
- texsmith/api/__init__.py +80 -0
- texsmith/api/_utils.py +56 -0
- texsmith/api/document.py +645 -0
- texsmith/api/pipeline.py +229 -0
- texsmith/api/service.py +648 -0
- texsmith/api/templates.py +287 -0
- texsmith/core/__init__.py +6 -0
- texsmith/core/bibliography/__init__.py +57 -0
- texsmith/core/bibliography/collection.py +354 -0
- texsmith/core/bibliography/doi.py +194 -0
- texsmith/core/bibliography/issues.py +15 -0
- texsmith/core/bibliography/parsing.py +45 -0
- texsmith/core/callouts.py +99 -0
- texsmith/core/config.py +191 -0
- texsmith/core/context.py +242 -0
- texsmith/core/conversion/__init__.py +103 -0
- texsmith/core/conversion/core.py +780 -0
- texsmith/core/conversion/debug.py +87 -0
- texsmith/core/conversion/inputs.py +474 -0
- texsmith/core/conversion/renderer.py +578 -0
- texsmith/core/conversion/templates.py +646 -0
- texsmith/core/conversion_contexts.py +95 -0
- texsmith/core/diagnostics.py +118 -0
- texsmith/core/exceptions.py +41 -0
- texsmith/core/fonts/__init__.py +3 -0
- texsmith/core/fragments/__init__.py +716 -0
- texsmith/core/fragments/base.py +117 -0
- texsmith/core/metadata.py +280 -0
- texsmith/core/mustache.py +86 -0
- texsmith/core/partials.py +20 -0
- texsmith/core/rules.py +394 -0
- texsmith/core/templates/__init__.py +58 -0
- texsmith/core/templates/base.py +343 -0
- texsmith/core/templates/builtins.py +68 -0
- texsmith/core/templates/context_usage.py +137 -0
- texsmith/core/templates/loader.py +303 -0
- texsmith/core/templates/manifest.py +861 -0
- texsmith/core/templates/runtime.py +347 -0
- texsmith/core/templates/text.py +14 -0
- texsmith/core/templates/wrapper.py +340 -0
- texsmith/core/user_dir.py +179 -0
- texsmith/devtools.py +28 -0
- texsmith/extensions/__init__.py +162 -0
- texsmith/extensions/index/__init__.py +21 -0
- texsmith/extensions/index/markdown.py +94 -0
- texsmith/extensions/index/mkdocs_plugin.py +136 -0
- texsmith/extensions/index/registry.py +57 -0
- texsmith/extensions/index/renderer.py +183 -0
- texsmith/extensions/index/templates/index.tex +1 -0
- texsmith/extensions/latex_raw.py +115 -0
- texsmith/extensions/latex_text.py +117 -0
- texsmith/extensions/mermaid.py +267 -0
- texsmith/extensions/missing_footnotes.py +125 -0
- texsmith/extensions/multi_citations.py +54 -0
- texsmith/extensions/progressbar/__init__.py +9 -0
- texsmith/extensions/progressbar/markdown.py +212 -0
- texsmith/extensions/progressbar/renderer.py +117 -0
- texsmith/extensions/smallcaps.py +48 -0
- texsmith/extensions/texlogos/__init__.py +10 -0
- texsmith/extensions/texlogos/markdown.py +236 -0
- texsmith/extensions/texlogos/renderer.py +81 -0
- texsmith/extensions/texlogos/specs.py +66 -0
- texsmith/fonts/__init__.py +57 -0
- texsmith/fonts/cache.py +46 -0
- texsmith/fonts/constants.py +60 -0
- texsmith/fonts/coverage.py +275 -0
- texsmith/fonts/downloader.py +103 -0
- texsmith/fonts/fallback.py +438 -0
- texsmith/fonts/html_scripts.py +168 -0
- texsmith/fonts/logging.py +127 -0
- texsmith/fonts/pipeline.py +338 -0
- texsmith/fonts/scripts.py +493 -0
- texsmith/fonts/ucharclasses.py +164 -0
- texsmith/fragments/__init__.py +11 -0
- texsmith/fragments/bibliography/__init__.py +65 -0
- texsmith/fragments/bibliography/fragment.toml +3 -0
- texsmith/fragments/bibliography/ts-bibliography-backmatter.jinja.tex +35 -0
- texsmith/fragments/bibliography/ts-bibliography.jinja.tex +10 -0
- texsmith/fragments/callouts/__init__.py +88 -0
- texsmith/fragments/callouts/fragment.toml +3 -0
- texsmith/fragments/callouts/ts-callouts.jinja.sty +129 -0
- texsmith/fragments/code/__init__.py +82 -0
- texsmith/fragments/code/fragment.toml +3 -0
- texsmith/fragments/code/ts-code.jinja.sty +140 -0
- texsmith/fragments/extra/__init__.py +180 -0
- texsmith/fragments/extra/fragment.toml +3 -0
- texsmith/fragments/extra/ts-extra.jinja.tex +13 -0
- texsmith/fragments/fonts/__init__.py +880 -0
- texsmith/fragments/fonts/fragment.toml +3 -0
- texsmith/fragments/fonts/ts-fonts.jinja.sty +292 -0
- texsmith/fragments/frame/__init__.py +170 -0
- texsmith/fragments/frame/fragment.toml +3 -0
- texsmith/fragments/frame/ts-frame.tex.jinja +49 -0
- texsmith/fragments/geometry/__init__.py +169 -0
- texsmith/fragments/geometry/fragment.toml +3 -0
- texsmith/fragments/geometry/paper.py +526 -0
- texsmith/fragments/geometry/ts_geometry.tex.jinja +53 -0
- texsmith/fragments/glossary/__init__.py +67 -0
- texsmith/fragments/glossary/fragment.toml +3 -0
- texsmith/fragments/glossary/ts-glossary-backmatter.jinja.tex +5 -0
- texsmith/fragments/glossary/ts-glossary.jinja.sty +28 -0
- texsmith/fragments/index/__init__.py +66 -0
- texsmith/fragments/index/fragment.toml +3 -0
- texsmith/fragments/index/ts-index-backmatter.jinja.tex +3 -0
- texsmith/fragments/index/ts-index.jinja.sty +12 -0
- texsmith/fragments/keystrokes/__init__.py +69 -0
- texsmith/fragments/keystrokes/fragment.toml +3 -0
- texsmith/fragments/keystrokes/ts-keystrokes.jinja.sty +20 -0
- texsmith/fragments/todolist/__init__.py +71 -0
- texsmith/fragments/todolist/fragment.toml +3 -0
- texsmith/fragments/todolist/ts-todolist.jinja.sty +21 -0
- texsmith/fragments/typesetting/__init__.py +214 -0
- texsmith/fragments/typesetting/fragment.toml +3 -0
- texsmith/fragments/typesetting/ts-typesetting.tex.jinja +81 -0
- texsmith/index.py +26 -0
- texsmith/plugins/__init__.py +14 -0
- texsmith/progressbar.py +8 -0
- texsmith/quotes.py +40 -0
- texsmith/smart_dashes.py +66 -0
- texsmith/templates/__init__.py +3 -0
- texsmith/templates/article/README.md +33 -0
- texsmith/templates/article/__init__.py +281 -0
- texsmith/templates/article/template/manifest.toml +125 -0
- texsmith/templates/article/template/mermaid-config.json +18 -0
- texsmith/templates/article/template/template.tex +74 -0
- texsmith/templates/book/README.md +26 -0
- texsmith/templates/book/__init__.py +75 -0
- texsmith/templates/book/overrides/codeblock.tex +7 -0
- texsmith/templates/book/overrides/codeinline.tex +1 -0
- texsmith/templates/book/template/fixtoc.sty +81 -0
- texsmith/templates/book/template/manifest.toml +198 -0
- texsmith/templates/book/template/template.tex +314 -0
- texsmith/templates/common/__init__.py +1 -0
- texsmith/templates/common/latexmkrc +46 -0
- texsmith/templates/letter/README.md +59 -0
- texsmith/templates/letter/__init__.py +495 -0
- texsmith/templates/letter/demo.md +31 -0
- texsmith/templates/letter/fonts/modernline bold.otf +0 -0
- texsmith/templates/letter/fonts/modernline.otf +0 -0
- texsmith/templates/letter/manifest.toml +197 -0
- texsmith/templates/letter/template/callouts.jinja.sty +290 -0
- texsmith/templates/letter/template/template.tex +123 -0
- texsmith/templates/snippet/README.md +21 -0
- texsmith/templates/snippet/__init__.py +80 -0
- texsmith/templates/snippet/template/manifest.toml +66 -0
- texsmith/templates/snippet/template/template.tex +47 -0
- texsmith/texlogos.py +15 -0
- texsmith/ui/__init__.py +6 -0
- texsmith/ui/cli/__init__.py +22 -0
- texsmith/ui/cli/_options.py +338 -0
- texsmith/ui/cli/app.py +65 -0
- texsmith/ui/cli/bibliography.py +300 -0
- texsmith/ui/cli/commands/__init__.py +14 -0
- texsmith/ui/cli/commands/render.py +1128 -0
- texsmith/ui/cli/commands/templates.py +397 -0
- texsmith/ui/cli/diagnostics.py +36 -0
- texsmith/ui/cli/presenter.py +663 -0
- texsmith/ui/cli/state.py +263 -0
- texsmith/ui/cli/utils.py +235 -0
- texsmith-0.0.2.dev0.dist-info/METADATA +187 -0
- texsmith-0.0.2.dev0.dist-info/RECORD +252 -0
- texsmith-0.0.2.dev0.dist-info/WHEEL +4 -0
- texsmith-0.0.2.dev0.dist-info/entry_points.txt +22 -0
- texsmith-0.0.2.dev0.dist-info/licenses/LICENSE.md +21 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""Built-in callout definitions and helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Mapping
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
CalloutConfig = dict[str, Any]
|
|
10
|
+
|
|
11
|
+
DEFAULT_CALLOUTS: dict[str, CalloutConfig] = {
|
|
12
|
+
"note": {"background_color": "ecf3ff", "border_color": "448aff", "icon": "📝"},
|
|
13
|
+
"abstract": {"background_color": "e5f7ff", "border_color": "00b0ff", "icon": "📄"},
|
|
14
|
+
"summary": {"background_color": "e5f8fb", "border_color": "00b8d4", "icon": "📋"},
|
|
15
|
+
"info": {"background_color": "e5f8fb", "border_color": "00b8d4", "icon": "ℹ"}, # noqa: RUF001
|
|
16
|
+
"tip": {"background_color": "e5f8f6", "border_color": "00bfa5", "icon": "⭐"},
|
|
17
|
+
"success": {"background_color": "e5f9ed", "border_color": "00c853", "icon": "✅"},
|
|
18
|
+
"question": {"background_color": "effce7", "border_color": "64dd17", "icon": "❓"},
|
|
19
|
+
"warning": {"background_color": "fff1d6", "border_color": "ffb200", "icon": "⚠"},
|
|
20
|
+
"caution": {"background_color": "fff8e1", "border_color": "ff9100", "icon": "🚧"},
|
|
21
|
+
"failure": {"background_color": "ffeded", "border_color": "ff5252", "icon": "❗"},
|
|
22
|
+
"danger": {"background_color": "ffe2e7", "border_color": "c62828", "icon": "🔥"},
|
|
23
|
+
"important": {"background_color": "ffe0f0", "border_color": "d81b60", "icon": "❗"},
|
|
24
|
+
"bug": {"background_color": "fee5ee", "border_color": "f50057", "icon": "🐞"},
|
|
25
|
+
"example": {"background_color": "f2edff", "border_color": "7c4dff", "icon": "🧪"},
|
|
26
|
+
"quote": {"background_color": "f5f5f5", "border_color": "9e9e9e", "icon": "✒️"},
|
|
27
|
+
"hint": {"background_color": "e5f8f6", "border_color": "00bfa5", "icon": "💡"},
|
|
28
|
+
"default": {"background_color": "f0f0f0", "border_color": "808080", "icon": "🎤"},
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _flatten_callouts(definitions: Mapping[str, Any]) -> dict[str, CalloutConfig]:
|
|
33
|
+
"""Flatten nested callout definitions declared under arbitrary keys."""
|
|
34
|
+
flat: dict[str, CalloutConfig] = {}
|
|
35
|
+
for name, cfg in definitions.items():
|
|
36
|
+
if not isinstance(cfg, Mapping):
|
|
37
|
+
continue
|
|
38
|
+
if {"icon", "background_color", "border_color", "title_color"} & set(cfg):
|
|
39
|
+
flat[name] = dict(cfg)
|
|
40
|
+
continue
|
|
41
|
+
nested = _flatten_callouts(cfg)
|
|
42
|
+
for child_name, child_cfg in nested.items():
|
|
43
|
+
flat[child_name] = child_cfg
|
|
44
|
+
return flat
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def merge_callouts(
|
|
48
|
+
base: Mapping[str, CalloutConfig],
|
|
49
|
+
overrides: Mapping[str, Any] | None = None,
|
|
50
|
+
) -> dict[str, CalloutConfig]:
|
|
51
|
+
"""Merge callout definitions with user-provided overrides."""
|
|
52
|
+
combined: dict[str, CalloutConfig] = {
|
|
53
|
+
key: dict(value) for key, value in _flatten_callouts(base).items()
|
|
54
|
+
}
|
|
55
|
+
if not overrides:
|
|
56
|
+
return combined
|
|
57
|
+
for key, cfg in _flatten_callouts(overrides).items():
|
|
58
|
+
merged = dict(combined.get(key, {}))
|
|
59
|
+
for name, value in cfg.items():
|
|
60
|
+
if value is None:
|
|
61
|
+
continue
|
|
62
|
+
merged[name] = value
|
|
63
|
+
combined[key] = merged
|
|
64
|
+
return combined
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _normalise_color(value: Any) -> str | None:
|
|
68
|
+
if value is None:
|
|
69
|
+
return None
|
|
70
|
+
if isinstance(value, int):
|
|
71
|
+
return f"{value:06X}"
|
|
72
|
+
if isinstance(value, str):
|
|
73
|
+
stripped = value.strip().lstrip("#").lstrip("0x").lstrip("0X")
|
|
74
|
+
if not stripped:
|
|
75
|
+
return None
|
|
76
|
+
try:
|
|
77
|
+
parsed = int(stripped, 16)
|
|
78
|
+
except ValueError:
|
|
79
|
+
return None
|
|
80
|
+
return f"{parsed:06X}"
|
|
81
|
+
return None
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def normalise_callouts(definitions: Mapping[str, CalloutConfig]) -> dict[str, CalloutConfig]:
|
|
85
|
+
"""Return a copy of callouts with hex color strings normalised."""
|
|
86
|
+
normalised: dict[str, CalloutConfig] = {}
|
|
87
|
+
for name, cfg in definitions.items():
|
|
88
|
+
if not isinstance(cfg, Mapping):
|
|
89
|
+
continue
|
|
90
|
+
updated: CalloutConfig = dict(cfg)
|
|
91
|
+
for key in ("background_color", "border_color", "title_color"):
|
|
92
|
+
colour = _normalise_color(updated.get(key))
|
|
93
|
+
if colour:
|
|
94
|
+
updated[key] = colour
|
|
95
|
+
normalised[name] = updated
|
|
96
|
+
return normalised
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
__all__ = ["DEFAULT_CALLOUTS", "CalloutConfig", "merge_callouts"]
|
texsmith/core/config.py
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"""Configuration models used by the LaTeX renderer.
|
|
2
|
+
|
|
3
|
+
CommonConfig
|
|
4
|
+
|
|
5
|
+
`build_dir` (`Path | None`)
|
|
6
|
+
: Base directory for LaTeX artifacts. Provide an absolute or project-relative
|
|
7
|
+
path to override the default export root that books inherit when they do not
|
|
8
|
+
specify one.
|
|
9
|
+
|
|
10
|
+
`save_html` (`bool`)
|
|
11
|
+
: Persist the intermediate HTML render next to the PDF to aid troubleshooting
|
|
12
|
+
before LaTeX compilation.
|
|
13
|
+
|
|
14
|
+
`mermaid_config` (`Path | None`)
|
|
15
|
+
: Path to a Mermaid configuration file. Point to a `.json` or `.mermaid`
|
|
16
|
+
document to customise diagram rendering.
|
|
17
|
+
|
|
18
|
+
`project_dir` (`Path | None`)
|
|
19
|
+
: MkDocs project root used to resolve relative paths when copying additional
|
|
20
|
+
assets.
|
|
21
|
+
|
|
22
|
+
: BCP 47 language code forwarded to LaTeX for hyphenation, translations, and
|
|
23
|
+
: metadata localisation.
|
|
24
|
+
|
|
25
|
+
`legacy_latex_accents` (`bool`)
|
|
26
|
+
: When `True`, escape accented characters, ligatures, and typographic punctuation
|
|
27
|
+
using legacy LaTeX macros. When `False`, keep Unicode glyphs compatible with
|
|
28
|
+
LuaLaTeX/XeLaTeX (default).
|
|
29
|
+
|
|
30
|
+
`language` (`str | None`)
|
|
31
|
+
: BCP 47 language code forwarded to LaTeX for hyphenation, translations, and
|
|
32
|
+
metadata localisation.
|
|
33
|
+
|
|
34
|
+
CoverConfig
|
|
35
|
+
|
|
36
|
+
`name` (`str`)
|
|
37
|
+
: Identifier of the cover template to apply. The value must match a template
|
|
38
|
+
declared in the cover bundle.
|
|
39
|
+
|
|
40
|
+
`color` (`str | None`)
|
|
41
|
+
: Primary colour override applied by the cover template.
|
|
42
|
+
|
|
43
|
+
`logo` (`str | None`)
|
|
44
|
+
: Project-relative path to a logo asset displayed on the cover.
|
|
45
|
+
|
|
46
|
+
BookConfig
|
|
47
|
+
|
|
48
|
+
`root` (`str | None`)
|
|
49
|
+
: Navigation entry treated as the starting point for the book. Use it when the
|
|
50
|
+
root differs from the first MkDocs page.
|
|
51
|
+
|
|
52
|
+
`title` (`str | None`)
|
|
53
|
+
: Title displayed on the cover and in output metadata. Falls back to `site_name`
|
|
54
|
+
when omitted.
|
|
55
|
+
|
|
56
|
+
`subtitle` (`str | None`)
|
|
57
|
+
: Optional subtitle appended to the cover and metadata.
|
|
58
|
+
|
|
59
|
+
`author` (`str | None`)
|
|
60
|
+
: Primary author string rendered in the book metadata.
|
|
61
|
+
|
|
62
|
+
`year` (`int | None`)
|
|
63
|
+
: Publication year to freeze in the output when `site_date` is not supplied.
|
|
64
|
+
|
|
65
|
+
`email` (`str | None`)
|
|
66
|
+
: Contact address printed in the credits.
|
|
67
|
+
|
|
68
|
+
`folder` (`Path | None`)
|
|
69
|
+
: Output directory for the rendered book. Defaults to a slug of the title when
|
|
70
|
+
not provided.
|
|
71
|
+
|
|
72
|
+
`frontmatter` (`list[str]`)
|
|
73
|
+
: MkDocs page titles moved before the main matter.
|
|
74
|
+
|
|
75
|
+
`backmatter` (`list[str]`)
|
|
76
|
+
: MkDocs page titles grouped into the appendices.
|
|
77
|
+
|
|
78
|
+
`base_level` (`int`)
|
|
79
|
+
: Heading offset applied to align section numbering with the template
|
|
80
|
+
expectations.
|
|
81
|
+
|
|
82
|
+
`copy_files` (`dict[str, str]`)
|
|
83
|
+
: Mapping of glob patterns to destination paths for copying additional assets
|
|
84
|
+
alongside the book.
|
|
85
|
+
|
|
86
|
+
`index_is_foreword` (`bool`)
|
|
87
|
+
: Treat the `index` page as a foreword, typically removing numbering.
|
|
88
|
+
|
|
89
|
+
`drop_title_index` (`bool`)
|
|
90
|
+
: Suppress the `index` page heading when it acts as a foreword.
|
|
91
|
+
|
|
92
|
+
`cover` (`CoverConfig`)
|
|
93
|
+
: Nested configuration controlling the book cover.
|
|
94
|
+
|
|
95
|
+
LaTeXConfig
|
|
96
|
+
|
|
97
|
+
`enabled` (`bool`)
|
|
98
|
+
: Toggle LaTeX generation without discarding configuration.
|
|
99
|
+
|
|
100
|
+
`books` (`list[BookConfig]`)
|
|
101
|
+
: Collection of books to produce, inheriting defaults from `CommonConfig`.
|
|
102
|
+
|
|
103
|
+
`clean_assets` (`bool`)
|
|
104
|
+
: Remove stale assets from `build_dir` to avoid accumulating unused files.
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
from __future__ import annotations
|
|
108
|
+
|
|
109
|
+
from pathlib import Path
|
|
110
|
+
from typing import Any
|
|
111
|
+
|
|
112
|
+
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
|
113
|
+
from slugify import slugify
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class CommonConfig(BaseModel):
|
|
117
|
+
"""Common configuration propagated to each book."""
|
|
118
|
+
|
|
119
|
+
model_config = ConfigDict(extra="forbid")
|
|
120
|
+
|
|
121
|
+
build_dir: Path | None = None
|
|
122
|
+
save_html: bool = False
|
|
123
|
+
mermaid_config: Path | None = None
|
|
124
|
+
project_dir: Path | None = None
|
|
125
|
+
language: str | None = None
|
|
126
|
+
legacy_latex_accents: bool = False
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class CoverConfig(BaseModel):
|
|
130
|
+
"""Metadata used to render book covers."""
|
|
131
|
+
|
|
132
|
+
model_config = ConfigDict(extra="forbid")
|
|
133
|
+
|
|
134
|
+
name: str = Field(default="circles", description="Cover template name")
|
|
135
|
+
color: str | None = Field(default="black", description="Primary color")
|
|
136
|
+
logo: str | None = Field(default=None, description="Logo path")
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class BookConfig(CommonConfig):
|
|
140
|
+
"""Configuration for an individual book."""
|
|
141
|
+
|
|
142
|
+
root: str | None = None
|
|
143
|
+
title: str | None = None
|
|
144
|
+
subtitle: str | None = None
|
|
145
|
+
author: str | None = None
|
|
146
|
+
year: int | None = None
|
|
147
|
+
email: str | None = None
|
|
148
|
+
folder: Path | None = None
|
|
149
|
+
frontmatter: list[str] = Field(default_factory=list)
|
|
150
|
+
backmatter: list[str] = Field(default_factory=list)
|
|
151
|
+
base_level: int = -2
|
|
152
|
+
copy_files: dict[str, str] = Field(default_factory=dict)
|
|
153
|
+
index_is_foreword: bool = False
|
|
154
|
+
drop_title_index: bool = False
|
|
155
|
+
cover: CoverConfig = Field(default_factory=CoverConfig)
|
|
156
|
+
|
|
157
|
+
@model_validator(mode="after")
|
|
158
|
+
def set_folder(self) -> BookConfig:
|
|
159
|
+
"""Populate the output folder from the book title when missing."""
|
|
160
|
+
if self.folder is None and self.title:
|
|
161
|
+
self.folder = Path(slugify(self.title, separator="-"))
|
|
162
|
+
return self
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class LaTeXConfig(CommonConfig):
|
|
166
|
+
"""Configuration for LaTeX taken from ``mkdocs.yml``."""
|
|
167
|
+
|
|
168
|
+
enabled: bool = True
|
|
169
|
+
books: list[BookConfig] = Field(default_factory=lambda: [BookConfig()])
|
|
170
|
+
clean_assets: bool = True
|
|
171
|
+
|
|
172
|
+
@model_validator(mode="after")
|
|
173
|
+
def propagate(self) -> LaTeXConfig:
|
|
174
|
+
"""Propagate common values to nested book configurations."""
|
|
175
|
+
to_propagate = (
|
|
176
|
+
"build_dir",
|
|
177
|
+
"mermaid_config",
|
|
178
|
+
"save_html",
|
|
179
|
+
"project_dir",
|
|
180
|
+
"language",
|
|
181
|
+
)
|
|
182
|
+
for book in self.books:
|
|
183
|
+
for key in to_propagate:
|
|
184
|
+
if getattr(book, key) is None:
|
|
185
|
+
setattr(book, key, getattr(self, key))
|
|
186
|
+
return self
|
|
187
|
+
|
|
188
|
+
def add_extra(self, **extra_data: Any) -> None:
|
|
189
|
+
"""Allow consumers to attach additional attributes at runtime."""
|
|
190
|
+
for key, value in extra_data.items():
|
|
191
|
+
object.__setattr__(self, key, value)
|
texsmith/core/context.py
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
"""Rendering context primitives shared across the LaTeX pipeline."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections import defaultdict
|
|
6
|
+
from collections.abc import Iterable, MutableMapping
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
import os
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import TYPE_CHECKING, Any
|
|
11
|
+
import warnings
|
|
12
|
+
|
|
13
|
+
from slugify import slugify
|
|
14
|
+
|
|
15
|
+
from .exceptions import AssetMissingError
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING: # pragma: no cover - typing only
|
|
19
|
+
from texsmith.adapters.latex.formatter import LaTeXFormatter
|
|
20
|
+
|
|
21
|
+
from .config import BookConfig
|
|
22
|
+
from .rules import RenderPhase
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(slots=True)
|
|
26
|
+
class DocumentState:
|
|
27
|
+
"""In-memory state accumulated while rendering a document."""
|
|
28
|
+
|
|
29
|
+
abbreviations: dict[str, str] = field(default_factory=dict)
|
|
30
|
+
acronym_keys: dict[str, str] = field(default_factory=dict)
|
|
31
|
+
acronyms: dict[str, tuple[str, str]] = field(default_factory=dict)
|
|
32
|
+
glossary: dict[str, dict[str, Any]] = field(default_factory=dict)
|
|
33
|
+
snippets: dict[str, dict[str, Any]] = field(default_factory=dict)
|
|
34
|
+
solutions: list[dict[str, Any]] = field(default_factory=list)
|
|
35
|
+
headings: list[dict[str, Any]] = field(default_factory=list)
|
|
36
|
+
exercise_counter: int = 0
|
|
37
|
+
has_index_entries: bool = False
|
|
38
|
+
requires_shell_escape: bool = False
|
|
39
|
+
counters: dict[str, int] = field(default_factory=dict)
|
|
40
|
+
bibliography: dict[str, dict[str, Any]] = field(default_factory=dict)
|
|
41
|
+
citations: list[str] = field(default_factory=list)
|
|
42
|
+
_citation_index: set[str] = field(default_factory=set, init=False, repr=False)
|
|
43
|
+
footnotes: dict[str, str] = field(default_factory=dict)
|
|
44
|
+
index_entries: list[tuple[str, ...]] = field(default_factory=list)
|
|
45
|
+
pygments_styles: dict[str, str] = field(default_factory=dict)
|
|
46
|
+
script_usage: list[dict[str, Any]] = field(default_factory=list)
|
|
47
|
+
fallback_summary: list[dict[str, Any]] = field(default_factory=list)
|
|
48
|
+
callouts_used: bool = False
|
|
49
|
+
|
|
50
|
+
def remember_acronym(self, term: str, description: str) -> str:
|
|
51
|
+
"""Register an acronym definition keyed by a normalised identifier."""
|
|
52
|
+
return self.remember_abbreviation(term=term, description=description)
|
|
53
|
+
|
|
54
|
+
def remember_abbreviation(self, term: str, description: str) -> str:
|
|
55
|
+
"""Track abbreviation definitions while ensuring consistency."""
|
|
56
|
+
normalised_term = term.strip()
|
|
57
|
+
normalised_description = description.strip()
|
|
58
|
+
if not normalised_term or not normalised_description:
|
|
59
|
+
return ""
|
|
60
|
+
|
|
61
|
+
existing_description = self.abbreviations.get(normalised_term)
|
|
62
|
+
if existing_description is not None:
|
|
63
|
+
if existing_description != normalised_description:
|
|
64
|
+
warnings.warn(
|
|
65
|
+
(
|
|
66
|
+
f"Inconsistent acronym definition for '{normalised_term}': "
|
|
67
|
+
f"'{existing_description}' vs '{normalised_description}'"
|
|
68
|
+
),
|
|
69
|
+
stacklevel=2,
|
|
70
|
+
)
|
|
71
|
+
return self.acronym_keys.get(normalised_term, "")
|
|
72
|
+
|
|
73
|
+
key = self._generate_acronym_key(normalised_term)
|
|
74
|
+
self.abbreviations[normalised_term] = normalised_description
|
|
75
|
+
self.acronym_keys[normalised_term] = key
|
|
76
|
+
self.acronyms[key] = (normalised_term, normalised_description)
|
|
77
|
+
return key
|
|
78
|
+
|
|
79
|
+
def _generate_acronym_key(self, term: str) -> str:
|
|
80
|
+
"""Produce a unique key suitable for the glossaries package."""
|
|
81
|
+
slug = slugify(term, separator="", lowercase=False)
|
|
82
|
+
if not slug:
|
|
83
|
+
slug = "acronym"
|
|
84
|
+
candidate = slug
|
|
85
|
+
suffix = 2
|
|
86
|
+
while candidate in self.acronyms:
|
|
87
|
+
candidate = f"{slug}{suffix}"
|
|
88
|
+
suffix += 1
|
|
89
|
+
return candidate
|
|
90
|
+
|
|
91
|
+
def remember_glossary(self, key: str, entry: dict[str, Any]) -> None:
|
|
92
|
+
"""Record a glossary entry keyed by its identifier."""
|
|
93
|
+
self.glossary[key] = entry
|
|
94
|
+
|
|
95
|
+
def register_snippet(self, key: str, payload: dict[str, Any]) -> None:
|
|
96
|
+
"""Cache snippet metadata to render later in the pipeline."""
|
|
97
|
+
self.snippets[key] = payload
|
|
98
|
+
|
|
99
|
+
def add_solution(self, solution: dict[str, Any]) -> None:
|
|
100
|
+
"""Append a solution block encountered during parsing."""
|
|
101
|
+
self.solutions.append(solution)
|
|
102
|
+
|
|
103
|
+
def add_heading(self, *, level: int, text: str, ref: str | None = None) -> None:
|
|
104
|
+
"""Track heading metadata to power table-of-contents generation."""
|
|
105
|
+
self.headings.append({"level": level, "text": text, "ref": ref})
|
|
106
|
+
|
|
107
|
+
def next_exercise(self) -> int:
|
|
108
|
+
"""Increment and return the exercise counter."""
|
|
109
|
+
counter = self.next_counter("exercise")
|
|
110
|
+
self.exercise_counter = counter
|
|
111
|
+
return counter
|
|
112
|
+
|
|
113
|
+
def next_counter(self, key: str = "default") -> int:
|
|
114
|
+
"""Increment and return the named counter."""
|
|
115
|
+
value = self.counters.get(key, 0) + 1
|
|
116
|
+
self.counters[key] = value
|
|
117
|
+
return value
|
|
118
|
+
|
|
119
|
+
def peek_counter(self, key: str = "default") -> int:
|
|
120
|
+
"""Return the current value of the named counter without modifying it."""
|
|
121
|
+
return self.counters.get(key, 0)
|
|
122
|
+
|
|
123
|
+
def reset_counter(self, key: str) -> None:
|
|
124
|
+
"""Clear the named counter if it has been tracked."""
|
|
125
|
+
self.counters.pop(key, None)
|
|
126
|
+
|
|
127
|
+
def record_citation(self, key: str) -> None:
|
|
128
|
+
"""Track citation keys used throughout the document."""
|
|
129
|
+
if key in self._citation_index:
|
|
130
|
+
return
|
|
131
|
+
self._citation_index.add(key)
|
|
132
|
+
self.citations.append(key)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@dataclass(slots=True)
|
|
136
|
+
class AssetRegistry:
|
|
137
|
+
"""Centralised registry for rendered assets."""
|
|
138
|
+
|
|
139
|
+
output_root: Path
|
|
140
|
+
assets_map: MutableMapping[str, Path] = field(default_factory=dict)
|
|
141
|
+
copy_assets: bool = True
|
|
142
|
+
|
|
143
|
+
def register(self, key: str, artefact: Path | str) -> Path:
|
|
144
|
+
"""Register a generated artefact and return its resolved path."""
|
|
145
|
+
path = Path(artefact)
|
|
146
|
+
if not path.is_absolute():
|
|
147
|
+
path = (self.output_root / path).resolve() if self.copy_assets else Path(path)
|
|
148
|
+
self.assets_map[key] = path
|
|
149
|
+
return path
|
|
150
|
+
|
|
151
|
+
def lookup(self, key: str) -> Path | None:
|
|
152
|
+
"""Return a previously registered artefact when available."""
|
|
153
|
+
stored = self.assets_map.get(key)
|
|
154
|
+
return Path(stored) if stored is not None else None
|
|
155
|
+
|
|
156
|
+
def get(self, key: str) -> Path:
|
|
157
|
+
"""Retrieve a previously registered artefact."""
|
|
158
|
+
try:
|
|
159
|
+
return Path(self.assets_map[key])
|
|
160
|
+
except KeyError as exc:
|
|
161
|
+
raise AssetMissingError(f"Missing asset '{key}'") from exc
|
|
162
|
+
|
|
163
|
+
def items(self) -> Iterable[tuple[str, Path]]:
|
|
164
|
+
"""Iterate over registered assets yielding key/path pairs."""
|
|
165
|
+
return ((k, Path(v)) for k, v in self.assets_map.items())
|
|
166
|
+
|
|
167
|
+
def latex_path(self, path: Path | str) -> str:
|
|
168
|
+
"""Return a LaTeX-friendly path for an artefact."""
|
|
169
|
+
candidate = Path(path)
|
|
170
|
+
if not candidate.is_absolute():
|
|
171
|
+
return candidate.as_posix()
|
|
172
|
+
|
|
173
|
+
output_dir = self.output_root.parent
|
|
174
|
+
try:
|
|
175
|
+
reference = candidate.relative_to(output_dir)
|
|
176
|
+
except ValueError:
|
|
177
|
+
try:
|
|
178
|
+
reference = Path(os.path.relpath(candidate, output_dir))
|
|
179
|
+
except ValueError:
|
|
180
|
+
reference = candidate
|
|
181
|
+
|
|
182
|
+
return reference.as_posix()
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
@dataclass
|
|
186
|
+
class RenderContext:
|
|
187
|
+
"""Shared context passed to every handler during rendering."""
|
|
188
|
+
|
|
189
|
+
config: BookConfig
|
|
190
|
+
formatter: LaTeXFormatter
|
|
191
|
+
document: Any
|
|
192
|
+
assets: AssetRegistry
|
|
193
|
+
state: DocumentState = field(default_factory=DocumentState)
|
|
194
|
+
runtime: dict[str, Any] = field(default_factory=dict)
|
|
195
|
+
phase: RenderPhase | None = None
|
|
196
|
+
|
|
197
|
+
_processed_nodes: defaultdict[int, set[int]] = field(
|
|
198
|
+
default_factory=lambda: defaultdict(set), init=False
|
|
199
|
+
)
|
|
200
|
+
_skip_children: defaultdict[int, set[int]] = field(
|
|
201
|
+
default_factory=lambda: defaultdict(set), init=False
|
|
202
|
+
)
|
|
203
|
+
_persistent_runtime: dict[str, Any] = field(default_factory=dict, init=False)
|
|
204
|
+
|
|
205
|
+
def enter_phase(self, phase: RenderPhase) -> None:
|
|
206
|
+
"""Mark the current phase and reset transient runtime data."""
|
|
207
|
+
self.phase = phase
|
|
208
|
+
self.runtime = dict(self._persistent_runtime)
|
|
209
|
+
self._skip_children[phase.value].clear()
|
|
210
|
+
|
|
211
|
+
def attach_runtime(self, **runtime: Any) -> None:
|
|
212
|
+
"""Attach ad-hoc data visible to handlers for the running phase."""
|
|
213
|
+
self._persistent_runtime.update(runtime)
|
|
214
|
+
self.runtime.update(runtime)
|
|
215
|
+
|
|
216
|
+
def mark_processed(self, node: Any, *, phase: RenderPhase | None = None) -> None:
|
|
217
|
+
"""Flag a node as already transformed for the selected phase."""
|
|
218
|
+
label = phase or self.phase
|
|
219
|
+
if label is None:
|
|
220
|
+
return
|
|
221
|
+
self._processed_nodes[label.value].add(id(node))
|
|
222
|
+
|
|
223
|
+
def is_processed(self, node: Any, *, phase: RenderPhase | None = None) -> bool:
|
|
224
|
+
"""Check whether a node has been processed in the given phase."""
|
|
225
|
+
label = phase or self.phase
|
|
226
|
+
if label is None:
|
|
227
|
+
return False
|
|
228
|
+
return id(node) in self._processed_nodes[label.value]
|
|
229
|
+
|
|
230
|
+
def suppress_children(self, node: Any, *, phase: RenderPhase | None = None) -> None:
|
|
231
|
+
"""Prevent traversal of node children for the active phase."""
|
|
232
|
+
label = phase or self.phase
|
|
233
|
+
if label is None:
|
|
234
|
+
return
|
|
235
|
+
self._skip_children[label.value].add(id(node))
|
|
236
|
+
|
|
237
|
+
def should_skip_children(self, node: Any, *, phase: RenderPhase | None = None) -> bool:
|
|
238
|
+
"""Check whether children should be skipped during traversal."""
|
|
239
|
+
label = phase or self.phase
|
|
240
|
+
if label is None:
|
|
241
|
+
return False
|
|
242
|
+
return id(node) in self._skip_children[label.value]
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""Public gateway into the low-level conversion engine."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from texsmith.core.conversion_contexts import (
|
|
6
|
+
BinderContext,
|
|
7
|
+
DocumentContext,
|
|
8
|
+
GenerationStrategy,
|
|
9
|
+
SegmentContext,
|
|
10
|
+
)
|
|
11
|
+
from texsmith.core.templates import (
|
|
12
|
+
DEFAULT_TEMPLATE_LANGUAGE,
|
|
13
|
+
TemplateBinding,
|
|
14
|
+
TemplateRuntime,
|
|
15
|
+
build_template_overrides,
|
|
16
|
+
load_template_runtime,
|
|
17
|
+
resolve_template_language,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
from .core import (
|
|
21
|
+
ConversionResult,
|
|
22
|
+
attempt_transformer_fallback,
|
|
23
|
+
convert_document,
|
|
24
|
+
copy_document_state,
|
|
25
|
+
ensure_fallback_converters,
|
|
26
|
+
render_with_fallback,
|
|
27
|
+
)
|
|
28
|
+
from .debug import (
|
|
29
|
+
ConversionError,
|
|
30
|
+
DiagnosticEmitter,
|
|
31
|
+
LoggingEmitter,
|
|
32
|
+
NullEmitter,
|
|
33
|
+
debug_enabled,
|
|
34
|
+
ensure_emitter,
|
|
35
|
+
format_rendering_error,
|
|
36
|
+
persist_debug_artifacts,
|
|
37
|
+
raise_conversion_error,
|
|
38
|
+
record_event,
|
|
39
|
+
)
|
|
40
|
+
from .inputs import (
|
|
41
|
+
DOCUMENT_SELECTOR_SENTINEL,
|
|
42
|
+
InputKind,
|
|
43
|
+
UnsupportedInputError,
|
|
44
|
+
build_document_context,
|
|
45
|
+
coerce_slot_selector,
|
|
46
|
+
extract_content,
|
|
47
|
+
extract_front_matter_bibliography,
|
|
48
|
+
extract_front_matter_slots,
|
|
49
|
+
parse_slot_mapping,
|
|
50
|
+
)
|
|
51
|
+
from .renderer import (
|
|
52
|
+
FragmentOverrideError,
|
|
53
|
+
TemplateFragment,
|
|
54
|
+
TemplateRenderer,
|
|
55
|
+
TemplateRendererResult,
|
|
56
|
+
)
|
|
57
|
+
from .templates import build_binder_context, extract_slot_fragments, heading_level_for
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
__all__ = [
|
|
61
|
+
"DEFAULT_TEMPLATE_LANGUAGE",
|
|
62
|
+
"DOCUMENT_SELECTOR_SENTINEL",
|
|
63
|
+
"BinderContext",
|
|
64
|
+
"ConversionError",
|
|
65
|
+
"ConversionResult",
|
|
66
|
+
"DiagnosticEmitter",
|
|
67
|
+
"DocumentContext",
|
|
68
|
+
"FragmentOverrideError",
|
|
69
|
+
"GenerationStrategy",
|
|
70
|
+
"InputKind",
|
|
71
|
+
"LoggingEmitter",
|
|
72
|
+
"NullEmitter",
|
|
73
|
+
"SegmentContext",
|
|
74
|
+
"TemplateBinding",
|
|
75
|
+
"TemplateFragment",
|
|
76
|
+
"TemplateRenderer",
|
|
77
|
+
"TemplateRendererResult",
|
|
78
|
+
"TemplateRuntime",
|
|
79
|
+
"UnsupportedInputError",
|
|
80
|
+
"attempt_transformer_fallback",
|
|
81
|
+
"build_binder_context",
|
|
82
|
+
"build_document_context",
|
|
83
|
+
"build_template_overrides",
|
|
84
|
+
"coerce_slot_selector",
|
|
85
|
+
"convert_document",
|
|
86
|
+
"copy_document_state",
|
|
87
|
+
"debug_enabled",
|
|
88
|
+
"ensure_emitter",
|
|
89
|
+
"ensure_fallback_converters",
|
|
90
|
+
"extract_content",
|
|
91
|
+
"extract_front_matter_bibliography",
|
|
92
|
+
"extract_front_matter_slots",
|
|
93
|
+
"extract_slot_fragments",
|
|
94
|
+
"format_rendering_error",
|
|
95
|
+
"heading_level_for",
|
|
96
|
+
"load_template_runtime",
|
|
97
|
+
"parse_slot_mapping",
|
|
98
|
+
"persist_debug_artifacts",
|
|
99
|
+
"raise_conversion_error",
|
|
100
|
+
"record_event",
|
|
101
|
+
"render_with_fallback",
|
|
102
|
+
"resolve_template_language",
|
|
103
|
+
]
|