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.
Files changed (252) hide show
  1. texsmith/__init__.py +107 -0
  2. texsmith/_alias.py +59 -0
  3. texsmith/adapters/__init__.py +6 -0
  4. texsmith/adapters/docker.py +258 -0
  5. texsmith/adapters/handlers/__init__.py +3 -0
  6. texsmith/adapters/handlers/_assets.py +363 -0
  7. texsmith/adapters/handlers/_helpers.py +62 -0
  8. texsmith/adapters/handlers/_mermaid.py +122 -0
  9. texsmith/adapters/handlers/admonitions.py +267 -0
  10. texsmith/adapters/handlers/basic.py +228 -0
  11. texsmith/adapters/handlers/blocks.py +851 -0
  12. texsmith/adapters/handlers/code.py +309 -0
  13. texsmith/adapters/handlers/inline.py +937 -0
  14. texsmith/adapters/handlers/links.py +239 -0
  15. texsmith/adapters/handlers/media.py +380 -0
  16. texsmith/adapters/latex/__init__.py +10 -0
  17. texsmith/adapters/latex/engines/__init__.py +726 -0
  18. texsmith/adapters/latex/engines/latex/__init__.py +26 -0
  19. texsmith/adapters/latex/engines/latex/log.py +720 -0
  20. texsmith/adapters/latex/engines/latex/runner.py +28 -0
  21. texsmith/adapters/latex/engines/tectonic/__init__.py +38 -0
  22. texsmith/adapters/latex/formatter.py +297 -0
  23. texsmith/adapters/latex/latexmk.py +148 -0
  24. texsmith/adapters/latex/partials/acronym.tex +1 -0
  25. texsmith/adapters/latex/partials/add.tex +1 -0
  26. texsmith/adapters/latex/partials/addition.tex +1 -0
  27. texsmith/adapters/latex/partials/blockquote.tex +3 -0
  28. texsmith/adapters/latex/partials/callout.tex +3 -0
  29. texsmith/adapters/latex/partials/choices.tex +5 -0
  30. texsmith/adapters/latex/partials/citation.tex +1 -0
  31. texsmith/adapters/latex/partials/codeblock.tex +7 -0
  32. texsmith/adapters/latex/partials/codeblock_listings.tex +5 -0
  33. texsmith/adapters/latex/partials/codeblock_pygments.tex +5 -0
  34. texsmith/adapters/latex/partials/codeblock_verbatim.tex +12 -0
  35. texsmith/adapters/latex/partials/codeinline.tex +1 -0
  36. texsmith/adapters/latex/partials/codeinlinett.tex +1 -0
  37. texsmith/adapters/latex/partials/comment.tex +1 -0
  38. texsmith/adapters/latex/partials/del.tex +1 -0
  39. texsmith/adapters/latex/partials/deletion.tex +1 -0
  40. texsmith/adapters/latex/partials/description_list.tex +5 -0
  41. texsmith/adapters/latex/partials/enquote.tex +1 -0
  42. texsmith/adapters/latex/partials/epigraph.tex +1 -0
  43. texsmith/adapters/latex/partials/exercises_solutions.tex +7 -0
  44. texsmith/adapters/latex/partials/figure.tex +33 -0
  45. texsmith/adapters/latex/partials/figure_tcolorbox.tex +15 -0
  46. texsmith/adapters/latex/partials/footnote.tex +3 -0
  47. texsmith/adapters/latex/partials/glossary.tex +1 -0
  48. texsmith/adapters/latex/partials/heading.tex +14 -0
  49. texsmith/adapters/latex/partials/highlight.tex +25 -0
  50. texsmith/adapters/latex/partials/horizontal_rule.tex +1 -0
  51. texsmith/adapters/latex/partials/href.tex +1 -0
  52. texsmith/adapters/latex/partials/icon.tex +1 -0
  53. texsmith/adapters/latex/partials/include.tex +1 -0
  54. texsmith/adapters/latex/partials/index.tex +1 -0
  55. texsmith/adapters/latex/partials/italic.tex +1 -0
  56. texsmith/adapters/latex/partials/keystroke.tex +46 -0
  57. texsmith/adapters/latex/partials/label.tex +1 -0
  58. texsmith/adapters/latex/partials/list_acronyms.tex +5 -0
  59. texsmith/adapters/latex/partials/list_glossary.tex +8 -0
  60. texsmith/adapters/latex/partials/multicolumn.tex +3 -0
  61. texsmith/adapters/latex/partials/ordered_list.tex +5 -0
  62. texsmith/adapters/latex/partials/pagestyle.tex +1 -0
  63. texsmith/adapters/latex/partials/ref.tex +1 -0
  64. texsmith/adapters/latex/partials/regex.tex +1 -0
  65. texsmith/adapters/latex/partials/smallcaps.tex +1 -0
  66. texsmith/adapters/latex/partials/strikethrough.tex +1 -0
  67. texsmith/adapters/latex/partials/strong.tex +1 -0
  68. texsmith/adapters/latex/partials/subscript.tex +1 -0
  69. texsmith/adapters/latex/partials/substitution.tex +1 -0
  70. texsmith/adapters/latex/partials/superscript.tex +1 -0
  71. texsmith/adapters/latex/partials/tabbed.tex +3 -0
  72. texsmith/adapters/latex/partials/table.tex +48 -0
  73. texsmith/adapters/latex/partials/underline.tex +1 -0
  74. texsmith/adapters/latex/partials/unordered_list.tex +5 -0
  75. texsmith/adapters/latex/partials/url.tex +1 -0
  76. texsmith/adapters/latex/pygments.py +98 -0
  77. texsmith/adapters/latex/pyxindy.py +64 -0
  78. texsmith/adapters/latex/renderer.py +220 -0
  79. texsmith/adapters/latex/tectonic.py +366 -0
  80. texsmith/adapters/latex/utils.py +86 -0
  81. texsmith/adapters/markdown/__init__.py +342 -0
  82. texsmith/adapters/plugins/__init__.py +8 -0
  83. texsmith/adapters/plugins/material.py +174 -0
  84. texsmith/adapters/plugins/snippet.py +1734 -0
  85. texsmith/adapters/transformers/__init__.py +122 -0
  86. texsmith/adapters/transformers/base.py +140 -0
  87. texsmith/adapters/transformers/strategies.py +1456 -0
  88. texsmith/adapters/transformers/utils.py +59 -0
  89. texsmith/api/__init__.py +80 -0
  90. texsmith/api/_utils.py +56 -0
  91. texsmith/api/document.py +645 -0
  92. texsmith/api/pipeline.py +229 -0
  93. texsmith/api/service.py +648 -0
  94. texsmith/api/templates.py +287 -0
  95. texsmith/core/__init__.py +6 -0
  96. texsmith/core/bibliography/__init__.py +57 -0
  97. texsmith/core/bibliography/collection.py +354 -0
  98. texsmith/core/bibliography/doi.py +194 -0
  99. texsmith/core/bibliography/issues.py +15 -0
  100. texsmith/core/bibliography/parsing.py +45 -0
  101. texsmith/core/callouts.py +99 -0
  102. texsmith/core/config.py +191 -0
  103. texsmith/core/context.py +242 -0
  104. texsmith/core/conversion/__init__.py +103 -0
  105. texsmith/core/conversion/core.py +780 -0
  106. texsmith/core/conversion/debug.py +87 -0
  107. texsmith/core/conversion/inputs.py +474 -0
  108. texsmith/core/conversion/renderer.py +578 -0
  109. texsmith/core/conversion/templates.py +646 -0
  110. texsmith/core/conversion_contexts.py +95 -0
  111. texsmith/core/diagnostics.py +118 -0
  112. texsmith/core/exceptions.py +41 -0
  113. texsmith/core/fonts/__init__.py +3 -0
  114. texsmith/core/fragments/__init__.py +716 -0
  115. texsmith/core/fragments/base.py +117 -0
  116. texsmith/core/metadata.py +280 -0
  117. texsmith/core/mustache.py +86 -0
  118. texsmith/core/partials.py +20 -0
  119. texsmith/core/rules.py +394 -0
  120. texsmith/core/templates/__init__.py +58 -0
  121. texsmith/core/templates/base.py +343 -0
  122. texsmith/core/templates/builtins.py +68 -0
  123. texsmith/core/templates/context_usage.py +137 -0
  124. texsmith/core/templates/loader.py +303 -0
  125. texsmith/core/templates/manifest.py +861 -0
  126. texsmith/core/templates/runtime.py +347 -0
  127. texsmith/core/templates/text.py +14 -0
  128. texsmith/core/templates/wrapper.py +340 -0
  129. texsmith/core/user_dir.py +179 -0
  130. texsmith/devtools.py +28 -0
  131. texsmith/extensions/__init__.py +162 -0
  132. texsmith/extensions/index/__init__.py +21 -0
  133. texsmith/extensions/index/markdown.py +94 -0
  134. texsmith/extensions/index/mkdocs_plugin.py +136 -0
  135. texsmith/extensions/index/registry.py +57 -0
  136. texsmith/extensions/index/renderer.py +183 -0
  137. texsmith/extensions/index/templates/index.tex +1 -0
  138. texsmith/extensions/latex_raw.py +115 -0
  139. texsmith/extensions/latex_text.py +117 -0
  140. texsmith/extensions/mermaid.py +267 -0
  141. texsmith/extensions/missing_footnotes.py +125 -0
  142. texsmith/extensions/multi_citations.py +54 -0
  143. texsmith/extensions/progressbar/__init__.py +9 -0
  144. texsmith/extensions/progressbar/markdown.py +212 -0
  145. texsmith/extensions/progressbar/renderer.py +117 -0
  146. texsmith/extensions/smallcaps.py +48 -0
  147. texsmith/extensions/texlogos/__init__.py +10 -0
  148. texsmith/extensions/texlogos/markdown.py +236 -0
  149. texsmith/extensions/texlogos/renderer.py +81 -0
  150. texsmith/extensions/texlogos/specs.py +66 -0
  151. texsmith/fonts/__init__.py +57 -0
  152. texsmith/fonts/cache.py +46 -0
  153. texsmith/fonts/constants.py +60 -0
  154. texsmith/fonts/coverage.py +275 -0
  155. texsmith/fonts/downloader.py +103 -0
  156. texsmith/fonts/fallback.py +438 -0
  157. texsmith/fonts/html_scripts.py +168 -0
  158. texsmith/fonts/logging.py +127 -0
  159. texsmith/fonts/pipeline.py +338 -0
  160. texsmith/fonts/scripts.py +493 -0
  161. texsmith/fonts/ucharclasses.py +164 -0
  162. texsmith/fragments/__init__.py +11 -0
  163. texsmith/fragments/bibliography/__init__.py +65 -0
  164. texsmith/fragments/bibliography/fragment.toml +3 -0
  165. texsmith/fragments/bibliography/ts-bibliography-backmatter.jinja.tex +35 -0
  166. texsmith/fragments/bibliography/ts-bibliography.jinja.tex +10 -0
  167. texsmith/fragments/callouts/__init__.py +88 -0
  168. texsmith/fragments/callouts/fragment.toml +3 -0
  169. texsmith/fragments/callouts/ts-callouts.jinja.sty +129 -0
  170. texsmith/fragments/code/__init__.py +82 -0
  171. texsmith/fragments/code/fragment.toml +3 -0
  172. texsmith/fragments/code/ts-code.jinja.sty +140 -0
  173. texsmith/fragments/extra/__init__.py +180 -0
  174. texsmith/fragments/extra/fragment.toml +3 -0
  175. texsmith/fragments/extra/ts-extra.jinja.tex +13 -0
  176. texsmith/fragments/fonts/__init__.py +880 -0
  177. texsmith/fragments/fonts/fragment.toml +3 -0
  178. texsmith/fragments/fonts/ts-fonts.jinja.sty +292 -0
  179. texsmith/fragments/frame/__init__.py +170 -0
  180. texsmith/fragments/frame/fragment.toml +3 -0
  181. texsmith/fragments/frame/ts-frame.tex.jinja +49 -0
  182. texsmith/fragments/geometry/__init__.py +169 -0
  183. texsmith/fragments/geometry/fragment.toml +3 -0
  184. texsmith/fragments/geometry/paper.py +526 -0
  185. texsmith/fragments/geometry/ts_geometry.tex.jinja +53 -0
  186. texsmith/fragments/glossary/__init__.py +67 -0
  187. texsmith/fragments/glossary/fragment.toml +3 -0
  188. texsmith/fragments/glossary/ts-glossary-backmatter.jinja.tex +5 -0
  189. texsmith/fragments/glossary/ts-glossary.jinja.sty +28 -0
  190. texsmith/fragments/index/__init__.py +66 -0
  191. texsmith/fragments/index/fragment.toml +3 -0
  192. texsmith/fragments/index/ts-index-backmatter.jinja.tex +3 -0
  193. texsmith/fragments/index/ts-index.jinja.sty +12 -0
  194. texsmith/fragments/keystrokes/__init__.py +69 -0
  195. texsmith/fragments/keystrokes/fragment.toml +3 -0
  196. texsmith/fragments/keystrokes/ts-keystrokes.jinja.sty +20 -0
  197. texsmith/fragments/todolist/__init__.py +71 -0
  198. texsmith/fragments/todolist/fragment.toml +3 -0
  199. texsmith/fragments/todolist/ts-todolist.jinja.sty +21 -0
  200. texsmith/fragments/typesetting/__init__.py +214 -0
  201. texsmith/fragments/typesetting/fragment.toml +3 -0
  202. texsmith/fragments/typesetting/ts-typesetting.tex.jinja +81 -0
  203. texsmith/index.py +26 -0
  204. texsmith/plugins/__init__.py +14 -0
  205. texsmith/progressbar.py +8 -0
  206. texsmith/quotes.py +40 -0
  207. texsmith/smart_dashes.py +66 -0
  208. texsmith/templates/__init__.py +3 -0
  209. texsmith/templates/article/README.md +33 -0
  210. texsmith/templates/article/__init__.py +281 -0
  211. texsmith/templates/article/template/manifest.toml +125 -0
  212. texsmith/templates/article/template/mermaid-config.json +18 -0
  213. texsmith/templates/article/template/template.tex +74 -0
  214. texsmith/templates/book/README.md +26 -0
  215. texsmith/templates/book/__init__.py +75 -0
  216. texsmith/templates/book/overrides/codeblock.tex +7 -0
  217. texsmith/templates/book/overrides/codeinline.tex +1 -0
  218. texsmith/templates/book/template/fixtoc.sty +81 -0
  219. texsmith/templates/book/template/manifest.toml +198 -0
  220. texsmith/templates/book/template/template.tex +314 -0
  221. texsmith/templates/common/__init__.py +1 -0
  222. texsmith/templates/common/latexmkrc +46 -0
  223. texsmith/templates/letter/README.md +59 -0
  224. texsmith/templates/letter/__init__.py +495 -0
  225. texsmith/templates/letter/demo.md +31 -0
  226. texsmith/templates/letter/fonts/modernline bold.otf +0 -0
  227. texsmith/templates/letter/fonts/modernline.otf +0 -0
  228. texsmith/templates/letter/manifest.toml +197 -0
  229. texsmith/templates/letter/template/callouts.jinja.sty +290 -0
  230. texsmith/templates/letter/template/template.tex +123 -0
  231. texsmith/templates/snippet/README.md +21 -0
  232. texsmith/templates/snippet/__init__.py +80 -0
  233. texsmith/templates/snippet/template/manifest.toml +66 -0
  234. texsmith/templates/snippet/template/template.tex +47 -0
  235. texsmith/texlogos.py +15 -0
  236. texsmith/ui/__init__.py +6 -0
  237. texsmith/ui/cli/__init__.py +22 -0
  238. texsmith/ui/cli/_options.py +338 -0
  239. texsmith/ui/cli/app.py +65 -0
  240. texsmith/ui/cli/bibliography.py +300 -0
  241. texsmith/ui/cli/commands/__init__.py +14 -0
  242. texsmith/ui/cli/commands/render.py +1128 -0
  243. texsmith/ui/cli/commands/templates.py +397 -0
  244. texsmith/ui/cli/diagnostics.py +36 -0
  245. texsmith/ui/cli/presenter.py +663 -0
  246. texsmith/ui/cli/state.py +263 -0
  247. texsmith/ui/cli/utils.py +235 -0
  248. texsmith-0.0.2.dev0.dist-info/METADATA +187 -0
  249. texsmith-0.0.2.dev0.dist-info/RECORD +252 -0
  250. texsmith-0.0.2.dev0.dist-info/WHEEL +4 -0
  251. texsmith-0.0.2.dev0.dist-info/entry_points.txt +22 -0
  252. texsmith-0.0.2.dev0.dist-info/licenses/LICENSE.md +21 -0
@@ -0,0 +1,229 @@
1
+ """Conversion helpers that expose a friendly façade over the core engine.
2
+
3
+ Architecture
4
+ : `RenderSettings` stores optional overrides for parser selection, asset
5
+ copying, manifest generation, and diagnostic outputs. A dedicated dataclass
6
+ keeps configuration immutable unless explicitly copied.
7
+ : `LaTeXFragment` represents the output of a single document conversion and
8
+ records where it was written on disk, keeping post-processing logic decoupled
9
+ from the rendering pass.
10
+ : `ConversionBundle` collects fragments and offers convenience methods like
11
+ `combined_output` for quick previews or testing.
12
+ : `convert_documents` bridges `Document` inputs, renders them through the
13
+ conversion engine, and yields the populated bundle.
14
+
15
+ Implementation Rationale
16
+ : Splitting settings, fragments, and bundles into separate dataclasses keeps the
17
+ API explicit. Callers can introspect or extend the workflow without threading
18
+ ad-hoc dictionaries through their code.
19
+ : Supporting multiple documents in a single call enables batch rendering and
20
+ powers template sessions. The helper ensures file names are deduplicated and
21
+ optional outputs, such as manifests, remain consistent.
22
+
23
+ Usage Example
24
+ :
25
+ >>> from pathlib import Path
26
+ >>> from tempfile import TemporaryDirectory
27
+ >>> from texsmith.api.document import Document
28
+ >>> from texsmith.api.pipeline import convert_documents
29
+ >>> with TemporaryDirectory() as tmpdir:
30
+ ... source = Path(tmpdir) / "intro.md"
31
+ ... _ = source.write_text("# Intro\\nContent")
32
+ ... output_dir = Path(tmpdir) / "build"
33
+ ... bundle = convert_documents([Document.from_markdown(source)], output_dir=output_dir)
34
+ ... (output_dir / "intro.tex").exists()
35
+ True
36
+ """
37
+
38
+ from __future__ import annotations
39
+
40
+ from collections.abc import Iterable, Mapping, Sequence
41
+ import copy
42
+ from dataclasses import dataclass
43
+ from pathlib import Path
44
+ from typing import TYPE_CHECKING, Any
45
+
46
+ from ..core.bibliography.collection import BibliographyCollection
47
+ from ..core.conversion.core import ConversionResult, convert_document
48
+ from ..core.conversion.renderer import TemplateFragment
49
+ from ..core.diagnostics import DiagnosticEmitter, NullEmitter
50
+ from ._utils import build_unique_stem_map
51
+ from .document import Document
52
+
53
+
54
+ if TYPE_CHECKING: # pragma: no cover - type checking only
55
+ from texsmith.core.context import DocumentState
56
+
57
+ from ..core.templates import TemplateRuntime
58
+
59
+
60
+ __all__ = [
61
+ "ConversionBundle",
62
+ "LaTeXFragment",
63
+ "RenderSettings",
64
+ "convert_documents",
65
+ "to_template_fragments",
66
+ ]
67
+
68
+
69
+ @dataclass(slots=True)
70
+ class RenderSettings:
71
+ """Engine-level knobs applied during conversion."""
72
+
73
+ parser: str | None = None
74
+ disable_fallback_converters: bool = False
75
+ copy_assets: bool = True
76
+ convert_assets: bool = False
77
+ hash_assets: bool = False
78
+ manifest: bool = False
79
+ persist_debug_html: bool = False
80
+ language: str | None = None
81
+ legacy_latex_accents: bool = False
82
+ diagrams_backend: str | None = None
83
+
84
+ def copy(self) -> RenderSettings:
85
+ """Create a deep copy of the settings to avoid cross-run mutations."""
86
+ return copy.deepcopy(self)
87
+
88
+
89
+ @dataclass(slots=True)
90
+ class LaTeXFragment:
91
+ """Represents a rendered LaTeX fragment."""
92
+
93
+ document: Document
94
+ latex: str
95
+ stem: str
96
+ output_path: Path | None = None
97
+ conversion: ConversionResult | None = None
98
+
99
+ def write_to(self, target: Path) -> None:
100
+ """Persist the fragment to disk so later template steps can reference it."""
101
+ target.parent.mkdir(parents=True, exist_ok=True)
102
+ target.write_text(self.latex, encoding="utf-8")
103
+ self.output_path = target
104
+
105
+
106
+ @dataclass(slots=True)
107
+ class ConversionBundle:
108
+ """Collection returned by :func:`convert_documents`."""
109
+
110
+ fragments: list[LaTeXFragment]
111
+
112
+ def combined_output(self) -> str:
113
+ """Concatenate all fragments separated by blank lines for quick previews."""
114
+ return "\n\n".join(fragment.latex for fragment in self.fragments if fragment.latex)
115
+
116
+
117
+ def convert_documents(
118
+ documents: Sequence[Document],
119
+ *,
120
+ output_dir: Path | None = None,
121
+ settings: RenderSettings | None = None,
122
+ emitter: DiagnosticEmitter | None = None,
123
+ bibliography_files: Iterable[Path] | None = None,
124
+ template: str | None = None,
125
+ template_runtime: TemplateRuntime | None = None,
126
+ template_overrides: Mapping[str, Any] | None = None,
127
+ wrap_document: bool = True,
128
+ shared_state: DocumentState | None = None,
129
+ write_fragments: bool | None = None,
130
+ ) -> ConversionBundle:
131
+ """Convert one or more documents into LaTeX fragments while coordinating shared state."""
132
+ if not documents:
133
+ raise ValueError("At least one document is required for conversion.")
134
+
135
+ settings = settings.copy() if settings is not None else RenderSettings()
136
+ unique_stems = build_unique_stem_map([doc.source_path for doc in documents])
137
+
138
+ shared_bibliography: BibliographyCollection | None = None
139
+ seen_bibliography_issues: set[tuple[str, str | None, str | None]] = set()
140
+
141
+ if bibliography_files:
142
+ shared_bibliography = BibliographyCollection()
143
+ shared_bibliography.load_files(bibliography_files)
144
+
145
+ fragments: list[LaTeXFragment] = []
146
+ should_write_fragments = write_fragments if write_fragments is not None else True
147
+ state = shared_state
148
+ active_emitter = emitter or NullEmitter()
149
+
150
+ for _index, document in enumerate(documents):
151
+ context = document.to_context()
152
+ target_dir = Path(output_dir) if output_dir is not None else Path("build")
153
+ slot_overrides = document.slots.selectors()
154
+ result = convert_document(
155
+ document=context,
156
+ output_dir=target_dir,
157
+ parser=settings.parser,
158
+ disable_fallback_converters=settings.disable_fallback_converters,
159
+ copy_assets=settings.copy_assets,
160
+ convert_assets=settings.convert_assets,
161
+ hash_assets=settings.hash_assets,
162
+ manifest=settings.manifest,
163
+ template=template,
164
+ persist_debug_html=settings.persist_debug_html,
165
+ language=settings.language,
166
+ diagrams_backend=settings.diagrams_backend,
167
+ slot_overrides=slot_overrides or None,
168
+ bibliography_files=list(bibliography_files or []),
169
+ legacy_latex_accents=settings.legacy_latex_accents,
170
+ emitter=active_emitter,
171
+ template_overrides=template_overrides,
172
+ state=None if wrap_document else state,
173
+ template_runtime=template_runtime,
174
+ wrap_document=wrap_document,
175
+ preloaded_bibliography=shared_bibliography,
176
+ seen_bibliography_issues=seen_bibliography_issues,
177
+ )
178
+
179
+ if not wrap_document:
180
+ state = result.document_state or state
181
+
182
+ stem = unique_stems[document.source_path]
183
+ fragment = LaTeXFragment(
184
+ document=document,
185
+ latex=result.latex_output,
186
+ stem=stem,
187
+ conversion=result,
188
+ )
189
+ if output_dir is not None and should_write_fragments:
190
+ target = target_dir / f"{stem}.tex"
191
+ fragment.write_to(target)
192
+ fragments.append(fragment)
193
+
194
+ return ConversionBundle(fragments=fragments)
195
+
196
+
197
+ def to_template_fragments(bundle: ConversionBundle) -> list[TemplateFragment]:
198
+ """Convert API fragments into the core template fragment contract to keep template engines decoupled from API types."""
199
+ fragments: list[TemplateFragment] = []
200
+ for fragment in bundle.fragments:
201
+ conversion = fragment.conversion
202
+ if conversion is None:
203
+ raise ValueError("Template rendering requires conversion metadata for each fragment.")
204
+
205
+ document = fragment.document
206
+ slot_includes: set[str] = set()
207
+ if document is not None and hasattr(document, "slots"):
208
+ slot_includes = set(document.slots.includes())
209
+
210
+ fragments.append(
211
+ TemplateFragment(
212
+ stem=fragment.stem,
213
+ latex=conversion.latex_output,
214
+ default_slot=conversion.default_slot or "mainmatter",
215
+ slot_outputs=dict(conversion.slot_outputs),
216
+ slot_includes=slot_includes,
217
+ document_state=conversion.document_state,
218
+ bibliography_path=conversion.bibliography_path,
219
+ template_engine=conversion.template_engine,
220
+ requires_shell_escape=conversion.template_shell_escape,
221
+ template_overrides=dict(conversion.template_overrides),
222
+ output_path=fragment.output_path,
223
+ front_matter=document.front_matter,
224
+ source_path=document.source_path,
225
+ rule_descriptions=list(conversion.rule_descriptions),
226
+ assets=dict(conversion.assets_map),
227
+ )
228
+ )
229
+ return fragments