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,309 @@
1
+ """Code-related handlers for the LaTeX renderer."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Mapping
6
+ import re
7
+
8
+ from bs4.element import NavigableString, Tag
9
+
10
+ from texsmith.core.context import RenderContext
11
+ from texsmith.core.exceptions import InvalidNodeError
12
+ from texsmith.core.rules import RenderPhase, renders
13
+
14
+ from ._helpers import gather_classes, mark_processed
15
+ from ._mermaid import looks_like_mermaid as _looks_like_mermaid
16
+
17
+
18
+ def _extract_language(element: Tag) -> str:
19
+ classes = gather_classes(element.get("class"))
20
+ for cls in classes:
21
+ if cls.startswith("language-"):
22
+ return cls[len("language-") :] or "text"
23
+ if "highlight" in classes:
24
+ return "text"
25
+ return "text"
26
+
27
+
28
+ def _collect_code_listing(code_element: Tag) -> tuple[str, list[int]]:
29
+ listing: list[str] = []
30
+ highlight: list[int] = []
31
+
32
+ spans = code_element.find_all("span", id=lambda value: bool(value and value.startswith("__")))
33
+ if spans:
34
+ for index, span in enumerate(spans, start=1):
35
+ highlight_span = span.find("span", class_="hll")
36
+ current = highlight_span or span
37
+ if highlight_span is not None:
38
+ highlight.append(index)
39
+ listing.append(current.get_text(strip=False))
40
+ code_text = "".join(listing)
41
+ else:
42
+ code_text = code_element.get_text(strip=False)
43
+ return code_text, highlight
44
+
45
+
46
+ def _is_ascii_art(payload: str) -> bool:
47
+ return any(char in payload for char in ("┌", "┬", "─", "┐", "│", "├", "┼", "┤", "└", "┴", "┘"))
48
+
49
+
50
+ _LANGUAGE_TOKEN = re.compile(r"^[A-Za-z0-9_+\-#.]+$")
51
+ _CODE_ENGINES = {"minted", "listings", "verbatim", "pygments"}
52
+
53
+
54
+ def _resolve_code_engine(context: RenderContext) -> str:
55
+ runtime_code = context.runtime.get("code")
56
+ engine: str | None = None
57
+ if isinstance(runtime_code, Mapping):
58
+ raw = runtime_code.get("engine")
59
+ engine = raw if isinstance(raw, str) else None
60
+ elif isinstance(runtime_code, str):
61
+ engine = runtime_code
62
+
63
+ candidate = (engine or "").strip().lower()
64
+ if candidate in _CODE_ENGINES:
65
+ return candidate
66
+ return "pygments"
67
+
68
+
69
+ def _extract_language_hint(text: str) -> tuple[str | None, str]:
70
+ lines = text.splitlines()
71
+ if not lines:
72
+ return None, text
73
+
74
+ first = lines[0].strip()
75
+ remaining = lines[1:]
76
+
77
+ if first.startswith("```"):
78
+ info = first[3:].strip()
79
+ language = info.split()[0] if info else None
80
+ if remaining and remaining[-1].strip().startswith("```"):
81
+ remaining = remaining[:-1]
82
+ payload = "\n".join(remaining)
83
+ return language or None, payload
84
+
85
+ if remaining and _LANGUAGE_TOKEN.match(first):
86
+ payload = "\n".join(remaining)
87
+ if payload.strip():
88
+ return first, payload
89
+
90
+ return None, text
91
+
92
+
93
+ def _is_only_meaningful_child(node: Tag) -> bool:
94
+ parent = getattr(node, "parent", None)
95
+ if parent is None:
96
+ return False
97
+
98
+ for sibling in list(parent.contents):
99
+ if sibling is node:
100
+ continue
101
+ if isinstance(sibling, NavigableString):
102
+ if str(sibling).strip():
103
+ return False
104
+ continue
105
+ if getattr(sibling, "name", None):
106
+ return False
107
+ if str(sibling).strip():
108
+ return False
109
+ return True
110
+
111
+
112
+ @renders("pre", phase=RenderPhase.PRE, priority=45, name="preformatted_code", nestable=True)
113
+ def render_preformatted_code(element: Tag, context: RenderContext) -> None:
114
+ """Render plain <pre> blocks that wrap a <code> element."""
115
+ classes = gather_classes(element.get("class"))
116
+ if "mermaid" in classes:
117
+ return
118
+
119
+ parent = element.parent
120
+ parent_classes = gather_classes(getattr(parent, "get", lambda *_: None)("class"))
121
+ if any(cls in {"highlight", "codehilite"} for cls in parent_classes):
122
+ return
123
+
124
+ code_element = element.find("code", recursive=False)
125
+ code_classes = gather_classes(code_element.get("class")) if code_element else []
126
+ if any(cls in {"language-mermaid", "mermaid"} for cls in code_classes):
127
+ return
128
+
129
+ if code_element is not None and _looks_like_mermaid(code_element.get_text(strip=False)):
130
+ return
131
+
132
+ language = _extract_language(code_element) if code_element else "text"
133
+ engine = _resolve_code_engine(context)
134
+ code_text = (
135
+ code_element.get_text(strip=False) if code_element else element.get_text(strip=False)
136
+ )
137
+
138
+ if not code_text.strip():
139
+ return
140
+
141
+ baselinestretch = 0.5 if _is_ascii_art(code_text) else None
142
+ if not code_text.endswith("\n"):
143
+ code_text += "\n"
144
+
145
+ context.state.requires_shell_escape = context.state.requires_shell_escape or engine == "minted"
146
+ latex = context.formatter.codeblock(
147
+ code=code_text,
148
+ language=language,
149
+ lineno=False,
150
+ filename=None,
151
+ highlight=[],
152
+ baselinestretch=baselinestretch,
153
+ engine=engine,
154
+ state=context.state,
155
+ )
156
+
157
+ context.suppress_children(element)
158
+ element.replace_with(mark_processed(NavigableString(latex)))
159
+
160
+
161
+ @renders("div", phase=RenderPhase.PRE, priority=40, name="code_blocks", nestable=True)
162
+ def render_code_blocks(element: Tag, context: RenderContext) -> None:
163
+ """Render MkDocs-highlighted code blocks."""
164
+ classes = gather_classes(element.get("class"))
165
+ if "highlight" not in classes:
166
+ return
167
+ if "mermaid" in classes:
168
+ return
169
+
170
+ code_element = element.find("code")
171
+ if code_element is None:
172
+ raise InvalidNodeError("Missing <code> element inside highlighted block")
173
+ code_classes = gather_classes(code_element.get("class"))
174
+ if any(cls in {"language-mermaid", "mermaid"} for cls in code_classes):
175
+ return
176
+
177
+ if _looks_like_mermaid(code_element.get_text(strip=False)):
178
+ return
179
+
180
+ language = _extract_language(code_element)
181
+ if language == "text":
182
+ language = _extract_language(element)
183
+ lineno = element.find(class_="linenos") is not None
184
+
185
+ filename = None
186
+ if filename_el := element.find(class_="filename"):
187
+ filename = filename_el.get_text(strip=True)
188
+
189
+ code_text, highlight = _collect_code_listing(code_element)
190
+
191
+ engine = _resolve_code_engine(context)
192
+ baselinestretch = 0.5 if _is_ascii_art(code_text) else None
193
+ if not code_text.endswith("\n"):
194
+ code_text += "\n"
195
+
196
+ context.state.requires_shell_escape = context.state.requires_shell_escape or engine == "minted"
197
+ latex = context.formatter.codeblock(
198
+ code=code_text,
199
+ language=language,
200
+ lineno=lineno,
201
+ filename=filename,
202
+ highlight=highlight,
203
+ baselinestretch=baselinestretch,
204
+ engine=engine,
205
+ state=context.state,
206
+ )
207
+
208
+ context.suppress_children(element)
209
+ element.replace_with(mark_processed(NavigableString(latex)))
210
+
211
+
212
+ @renders(
213
+ "code",
214
+ phase=RenderPhase.PRE,
215
+ priority=40,
216
+ name="standalone_code_blocks",
217
+ auto_mark=False,
218
+ )
219
+ def render_standalone_code_blocks(element: Tag, context: RenderContext) -> None:
220
+ """Render <code> elements that include multiline content as block code."""
221
+ if element.find_parent("pre"):
222
+ return
223
+
224
+ classes = element.get("class") or []
225
+ if any(cls in {"language-mermaid", "mermaid"} for cls in classes):
226
+ return
227
+
228
+ code_text = element.get_text(strip=False)
229
+ if "\n" not in code_text:
230
+ return
231
+
232
+ if _looks_like_mermaid(code_text):
233
+ return
234
+
235
+ language = _extract_language(element)
236
+ if language == "text":
237
+ hint, adjusted = _extract_language_hint(code_text)
238
+ if hint:
239
+ language = hint
240
+ code_text = adjusted
241
+
242
+ engine = _resolve_code_engine(context)
243
+ if engine == "minted":
244
+ code_text = code_text.replace("{", r"\{").replace("}", r"\}")
245
+ if not code_text.strip():
246
+ return
247
+ if not code_text.endswith("\n"):
248
+ code_text += "\n"
249
+
250
+ baselinestretch = 0.5 if _is_ascii_art(code_text) else None
251
+
252
+ context.state.requires_shell_escape = context.state.requires_shell_escape or engine == "minted"
253
+ latex = context.formatter.codeblock(
254
+ code=code_text,
255
+ language=language,
256
+ lineno=False,
257
+ filename=None,
258
+ highlight=[],
259
+ baselinestretch=baselinestretch,
260
+ engine=engine,
261
+ state=context.state,
262
+ )
263
+
264
+ node = mark_processed(NavigableString(latex))
265
+
266
+ if element.parent and element.parent.name == "p" and _is_only_meaningful_child(element):
267
+ element.parent.replace_with(node)
268
+ context.mark_processed(element.parent)
269
+ else:
270
+ element.replace_with(node)
271
+ context.mark_processed(element)
272
+ context.suppress_children(element)
273
+
274
+
275
+ @renders("pre", phase=RenderPhase.PRE, priority=38, name="pre_code_blocks", nestable=False)
276
+ def render_pre_code_blocks(element: Tag, context: RenderContext) -> None:
277
+ """Render <pre><code> blocks that escaped earlier handlers."""
278
+ code_element = element.find("code")
279
+ if code_element is None:
280
+ return
281
+
282
+ code_classes = gather_classes(code_element.get("class"))
283
+ if any(cls in {"language-mermaid", "mermaid"} for cls in code_classes):
284
+ return
285
+
286
+ code_text, highlight = _collect_code_listing(code_element)
287
+ if _looks_like_mermaid(code_text):
288
+ return
289
+
290
+ language = _extract_language(code_element) or _extract_language(element)
291
+ engine = _resolve_code_engine(context)
292
+ baselinestretch = 0.5 if _is_ascii_art(code_text) else None
293
+ if not code_text.endswith("\n"):
294
+ code_text += "\n"
295
+
296
+ context.state.requires_shell_escape = context.state.requires_shell_escape or engine == "minted"
297
+ latex = context.formatter.codeblock(
298
+ code=code_text,
299
+ language=language,
300
+ lineno=False,
301
+ filename=None,
302
+ highlight=highlight,
303
+ baselinestretch=baselinestretch,
304
+ engine=engine,
305
+ state=context.state,
306
+ )
307
+
308
+ context.mark_processed(element)
309
+ element.replace_with(mark_processed(NavigableString(latex)))