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 @@
1
+ \gls{\VAR{key}}
@@ -0,0 +1,14 @@
1
+ \BLOCK{- set number_to_words = {
2
+ -1: 'part',
3
+ 0: 'chapter',
4
+ 1: 'section',
5
+ 2: 'subsection',
6
+ 3: 'subsubsection',
7
+ 4: 'paragraph',
8
+ 5: 'subparagraph'
9
+ } -}
10
+ \BLOCK{- if level in number_to_words -}
11
+ \\VAR{number_to_words[level]}\BLOCK{if not numbered and level <= 5}*\BLOCK{endif}{\VAR{text}}\BLOCK{if ref}\label{\VAR{ref}}\BLOCK{endif}\BLOCK{if level >= 4}\mbox{}\\\BLOCK{endif}
12
+ \BLOCK{else -}
13
+ \textbf{\VAR{text}}\BLOCK{if ref}\label{\VAR{ref}}\BLOCK{endif}
14
+ \BLOCK{endif}
@@ -0,0 +1,25 @@
1
+ \ifXeTeX
2
+ % soul breaks with ucharclasses on XeTeX: fall back to simple colored text.
3
+ \providecommand{\texsmithHighlight}[1]{\textcolor{yellow}{#1}}
4
+ \providecommand{\hl}[1]{\textcolor{yellow}{#1}}
5
+ \else
6
+ \ifLuaTeX
7
+ \RequirePackage{lua-ul}
8
+ \providecommand{\texsmithHighlight}[1]{%
9
+ \begingroup
10
+ \setulcolor{yellow}%
11
+ \ul{#1}%
12
+ \endgroup
13
+ }
14
+ \providecommand{\hl}[1]{\texsmithHighlight{#1}}
15
+ \else
16
+ \providecommand{\texsmithHighlight}[1]{%
17
+ \begingroup
18
+ \setlength{\fboxsep}{0.8pt}%
19
+ \colorbox{yellow}{#1}%
20
+ \endgroup
21
+ }
22
+ \fi
23
+ \fi
24
+
25
+ \texsmithHighlight{\VAR{text}}
@@ -0,0 +1 @@
1
+ \par\noindent\rule{\linewidth}{0.4pt}
@@ -0,0 +1 @@
1
+ \href{\VAR{url}}{\VAR{text}}
@@ -0,0 +1 @@
1
+ \includegraphics[width=1em]{\VAR{text}}
@@ -0,0 +1 @@
1
+ \input{\VAR{text}} % \VAR{title}
@@ -0,0 +1 @@
1
+ \VAR{text}\index{\BLOCK{ if style == 'b' }\textbf{\VAR{entry}}\BLOCK{ elif style == 'i' }\textit{\VAR{entry}}\BLOCK{ elif style in ('bi', 'ib') }\textbf{\textit{\VAR{entry}}}\BLOCK{ else }\VAR{entry}\BLOCK{ endif }}
@@ -0,0 +1 @@
1
+ \emph{\VAR{text}}
@@ -0,0 +1,46 @@
1
+ \BLOCK{set ks = {
2
+ 'control': 'Ctrl',
3
+ 'alt': 'Alt',
4
+ 'delete': 'Del',
5
+ 'enter': '⏎ Enter',
6
+ 'shift': '⇧ Shift',
7
+ 'slash': '/',
8
+ 'comma': ',',
9
+ 'period': '.',
10
+ 'arrow-up': '\\(\\uparrow\\)',
11
+ 'arrow-down': '\\(\\downarrow\\)',
12
+ 'arrow-left': '\\(\\leftarrow\\)',
13
+ 'arrow-right': '\\(\\rightarrow\\)',
14
+ 'backslash': '\\textbackslash',
15
+ 'double-quote': '"',
16
+ 'backspace': '⌫ Delete',
17
+ 'command': '⌘',
18
+ 'tab': 'Tab',
19
+ 'esc': 'Esc',
20
+ 'insert': 'Ins',
21
+ 'home': 'Home',
22
+ 'end': 'End',
23
+ 'page-up': 'PgUp',
24
+ 'page-down': 'PgDn',
25
+ 'space': 'Space',
26
+ 'f1': 'F1',
27
+ 'f2': 'F2',
28
+ 'f3': 'F3',
29
+ 'f4': 'F4',
30
+ 'f5': 'F5',
31
+ 'f6': 'F6',
32
+ 'f7': 'F7',
33
+ 'f8': 'F8',
34
+ 'f9': 'F9',
35
+ 'f10': 'F10',
36
+ 'f11': 'F11',
37
+ 'f12': 'F12',
38
+ }}
39
+ \BLOCK{- for key in text -}
40
+ \BLOCK{- if key in ks -}
41
+ \keystroke{\VAR{ks[key]}}
42
+ \BLOCK{- else -}
43
+ \keystroke{\VAR{key|upper}}
44
+ \BLOCK{- endif -}
45
+ \BLOCK{- if not loop.last -} + \BLOCK{- endif -}
46
+ \BLOCK{- endfor -}
@@ -0,0 +1 @@
1
+ \label{\VAR{text}}
@@ -0,0 +1,5 @@
1
+ % Acronyms
2
+ \BLOCK{for key, entry in text -}
3
+ \BLOCK{ set pair = entry if (entry is sequence and not entry is string) else [key, entry] }
4
+ \newacronym{\VAR{key}}{\VAR{pair[0]|latex_escape}}{\VAR{pair[1]|latex_escape}}
5
+ \BLOCK{endfor}
@@ -0,0 +1,8 @@
1
+ % Glossary entries
2
+ \BLOCK{for key, item in text.items() -}
3
+ \newglossaryentry{\VAR{key}}
4
+ {
5
+ name={\VAR{item['name']}},
6
+ description={\VAR{item['description']}}
7
+ }
8
+ \BLOCK{endfor}
@@ -0,0 +1,3 @@
1
+ \begin{multicols}{\VAR{columns}}
2
+ \VAR{text}
3
+ \end{multicols}
@@ -0,0 +1,5 @@
1
+ \begin{enumerate}
2
+ \BLOCK{for item in items -}
3
+ \item \VAR{item}
4
+ \BLOCK{endfor}
5
+ \end{enumerate}
@@ -0,0 +1 @@
1
+ \thispagestyle{\VAR{text}}
@@ -0,0 +1 @@
1
+ \VAR{text} \ref{\VAR{ref}}
@@ -0,0 +1 @@
1
+ \href{\VAR{url}}{\mintinline{text}{\VAR{text}}}
@@ -0,0 +1 @@
1
+ \textsc{\VAR{text}}
@@ -0,0 +1 @@
1
+ \sout{\VAR{text}}
@@ -0,0 +1 @@
1
+ \textbf{\VAR{text}}
@@ -0,0 +1 @@
1
+ \textsubscript{\VAR{text}}
@@ -0,0 +1 @@
1
+ \xout{\VAR{original}}\ \uline{\VAR{replacement}}
@@ -0,0 +1 @@
1
+ \textsuperscript{\VAR{text}}
@@ -0,0 +1,3 @@
1
+ \textbf{\VAR{title}}
2
+
3
+ \VAR{text}
@@ -0,0 +1,48 @@
1
+ \BLOCK{- set first_style = {
2
+ 'left': 'l',
3
+ 'right': 'r',
4
+ 'center': 'c'
5
+ } -}
6
+ \BLOCK{- set rest_style = {
7
+ 'left': 'X',
8
+ 'right': 'Z',
9
+ 'center': 'Y',
10
+ 'justify': 'X'
11
+ } -}
12
+ \BLOCK{- if caption -}
13
+ \begin{table}[H]
14
+ \centering
15
+ \BLOCK{if label -}
16
+ \label{\VAR{label}}
17
+ \BLOCK{- endif}
18
+ \caption{\VAR{caption}}
19
+ \BLOCK{else}
20
+ \begin{center}
21
+ \BLOCK{- endif}
22
+ \BLOCK{- if is_large -}
23
+ \BLOCK{- endif}
24
+ \begin{tabularx}{\textwidth}{\BLOCK{- for column in columns}\BLOCK{- if loop.first }\VAR{first_style.get(column, 'l')}\BLOCK{- else }\VAR{rest_style.get(column, 'X')}\BLOCK{- endif }\BLOCK{- endfor}}
25
+ \toprule
26
+ \BLOCK{for row in rows -}
27
+ \BLOCK{- if loop.first -}
28
+ \BLOCK{- for col in row -}
29
+ \textbf{\VAR{col}}\BLOCK{- if not loop.last} & \BLOCK{else} \\
30
+ \BLOCK{endif}
31
+ \BLOCK{- endfor}
32
+ \midrule
33
+ \BLOCK{else -}
34
+ \BLOCK{- for col in row -}
35
+ \VAR{col}\BLOCK{- if not loop.last} & \BLOCK{else} \\
36
+ \BLOCK{endif}
37
+ \BLOCK{- endfor -}
38
+ \BLOCK{- endif -}
39
+ \BLOCK{- endfor}
40
+ \bottomrule
41
+ \end{tabularx}
42
+ \BLOCK{- if is_large -}
43
+ \BLOCK{- endif}
44
+ \BLOCK{- if caption -}
45
+ \end{table}
46
+ \BLOCK{- else}
47
+ \end{center}
48
+ \BLOCK{- endif}
@@ -0,0 +1 @@
1
+ \uline{\VAR{text}}
@@ -0,0 +1,5 @@
1
+ \begin{itemize}
2
+ \BLOCK{for item in items -}
3
+ \item{} \VAR{item}
4
+ \BLOCK{endfor}
5
+ \end{itemize}
@@ -0,0 +1 @@
1
+ \href{\VAR{url}}{\VAR{text}}
@@ -0,0 +1,98 @@
1
+ """Pygments integration helpers for LaTeX rendering."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Iterable
6
+
7
+ from pygments import highlight
8
+ from pygments.formatters import LatexFormatter
9
+ from pygments.lexers import ClassNotFound, TextLexer, get_lexer_by_name
10
+
11
+
12
+ class PygmentsLatexHighlighter:
13
+ """Convert source code to LaTeX using Pygments."""
14
+
15
+ def __init__(
16
+ self,
17
+ *,
18
+ commandprefix: str = "PY",
19
+ style: str = "bw",
20
+ verboptions: str | None = None,
21
+ ) -> None:
22
+ self.commandprefix = commandprefix
23
+ self.style = style
24
+ self.verboptions = verboptions or r"breaklines, breakanywhere, commandchars=\\\{\}"
25
+
26
+ @property
27
+ def style_key(self) -> str:
28
+ """Identifier that groups style definitions."""
29
+ return f"{self.style}:{self.commandprefix}"
30
+
31
+ def render(
32
+ self,
33
+ code: str,
34
+ language: str,
35
+ *,
36
+ linenos: bool,
37
+ highlight_lines: Iterable[int] | None = None,
38
+ ) -> tuple[str, str]:
39
+ """Return the LaTeX code and style definitions for a payload."""
40
+ try:
41
+ lexer = get_lexer_by_name(language or "text")
42
+ except ClassNotFound:
43
+ lexer = TextLexer()
44
+
45
+ def _format_ranges(values: Iterable[int]) -> str:
46
+ sorted_vals = sorted(set(values))
47
+ if not sorted_vals:
48
+ return ""
49
+ ranges: list[str] = []
50
+ start = end = sorted_vals[0]
51
+ for num in sorted_vals[1:]:
52
+ if num == end + 1:
53
+ end = num
54
+ else:
55
+ ranges.append(f"{start}-{end}" if start != end else str(start))
56
+ start = end = num
57
+ ranges.append(f"{start}-{end}" if start != end else str(start))
58
+ return ",".join(ranges)
59
+
60
+ verb_options = self.verboptions
61
+ formatted_highlight = _format_ranges(highlight_lines or [])
62
+ if formatted_highlight:
63
+ verb_options = f"{verb_options},highlightlines={{{formatted_highlight}}}"
64
+
65
+ formatter = LatexFormatter(
66
+ full=False,
67
+ linenos=linenos,
68
+ style=self.style,
69
+ commandprefix=self.commandprefix,
70
+ linenostart=1,
71
+ linenostep=1,
72
+ verboptions=verb_options,
73
+ hl_lines=list(highlight_lines or []),
74
+ )
75
+ latex_code = highlight(code, lexer, formatter)
76
+ style_defs = formatter.get_style_defs()
77
+ return latex_code, style_defs
78
+
79
+ def render_inline(self, code: str, language: str) -> tuple[str, str]:
80
+ """Return inline LaTeX macros for a code snippet (no Verbatim env)."""
81
+ try:
82
+ lexer = get_lexer_by_name(language or "text")
83
+ except ClassNotFound:
84
+ lexer = TextLexer()
85
+
86
+ formatter = LatexFormatter(
87
+ full=False,
88
+ linenos=False,
89
+ style=self.style,
90
+ commandprefix=self.commandprefix,
91
+ nowrap=True,
92
+ )
93
+ latex_code = highlight(code, lexer, formatter)
94
+ style_defs = formatter.get_style_defs()
95
+ return latex_code, style_defs
96
+
97
+
98
+ __all__ = ["PygmentsLatexHighlighter"]
@@ -0,0 +1,64 @@
1
+ """Helpers to integrate the PyXindy toolchain (Python port of xindy)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import importlib.util
6
+ from pathlib import Path
7
+ import shlex
8
+ import shutil
9
+ import sys
10
+
11
+
12
+ def _python_module_available() -> bool:
13
+ """Return True when the PyXindy modules are importable."""
14
+ return importlib.util.find_spec("xindy") is not None
15
+
16
+
17
+ def is_available() -> bool:
18
+ """Check whether PyXindy can be invoked (entry points or module)."""
19
+ return _python_module_available() or shutil.which("makeindex-py") is not None
20
+
21
+
22
+ def _script_path(name: str) -> Path | None:
23
+ """Resolve a script living alongside the current interpreter."""
24
+ candidate = Path(sys.executable).with_name(name)
25
+ return candidate if candidate.exists() else None
26
+
27
+
28
+ def index_command_tokens() -> list[str]:
29
+ """Return the argv tokens to invoke the makeindex-compatible wrapper."""
30
+ for candidate in ("makeindex-py", "makeindex4"):
31
+ resolved = shutil.which(candidate) or _script_path(candidate)
32
+ if resolved:
33
+ return [str(resolved)]
34
+ return [sys.executable, "-m", "xindy.tex.makeindex4"]
35
+
36
+
37
+ def glossary_command_tokens() -> list[str]:
38
+ """Return the argv tokens to invoke the makeglossaries helper."""
39
+ for candidate in ("makeglossaries-py",):
40
+ resolved = shutil.which(candidate) or _script_path(candidate)
41
+ if resolved:
42
+ return [str(resolved)]
43
+ return [sys.executable, "-m", "xindy.tex.makeglossaries"]
44
+
45
+
46
+ def latexmk_makeindex_command() -> str:
47
+ """Return a makeindex command string suitable for latexmkrc."""
48
+ tokens = [shlex.quote(token) for token in index_command_tokens()]
49
+ return " ".join([*tokens, "%O", "-o", "%D", "%S"])
50
+
51
+
52
+ def latexmk_makeglossaries_command() -> str:
53
+ """Return a makeglossaries command string suitable for latexmkrc."""
54
+ tokens = [shlex.quote(token) for token in glossary_command_tokens()]
55
+ return " ".join([*tokens, '"$base"'])
56
+
57
+
58
+ __all__ = [
59
+ "glossary_command_tokens",
60
+ "index_command_tokens",
61
+ "is_available",
62
+ "latexmk_makeglossaries_command",
63
+ "latexmk_makeindex_command",
64
+ ]
@@ -0,0 +1,220 @@
1
+ """High-level HTML to LaTeX renderer based on the modular pipeline."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Callable, Iterable, Mapping
6
+ from importlib import metadata
7
+ import inspect
8
+ from pathlib import Path
9
+ from typing import TYPE_CHECKING, Any
10
+
11
+ from bs4 import BeautifulSoup, FeatureNotFound
12
+
13
+ from texsmith.core.config import BookConfig
14
+ from texsmith.core.context import AssetRegistry, DocumentState, RenderContext
15
+ from texsmith.core.diagnostics import DiagnosticEmitter, NullEmitter
16
+ from texsmith.core.exceptions import LatexRenderingError
17
+ from texsmith.core.rules import RenderEngine, RenderPhase
18
+ from texsmith.extensions import register_all_renderers
19
+
20
+ from .formatter import LaTeXFormatter
21
+
22
+
23
+ if TYPE_CHECKING: # pragma: no cover - typing only
24
+ pass
25
+
26
+
27
+ class LaTeXRenderer:
28
+ _ENTRY_POINT_GROUP = "texsmith.renderers"
29
+ _ENTRY_POINT_PAYLOADS: list[Any] | None = None
30
+
31
+ """Convert HTML fragments to LaTeX using a modular pipeline."""
32
+
33
+ def __init__(
34
+ self,
35
+ config: BookConfig | None = None,
36
+ formatter: LaTeXFormatter | None = None,
37
+ output_root: Path | str = Path("build"),
38
+ parser: str = "lxml",
39
+ copy_assets: bool = True,
40
+ convert_assets: bool = False,
41
+ hash_assets: bool = False,
42
+ ) -> None:
43
+ self.config = config or BookConfig()
44
+ self.formatter = formatter or LaTeXFormatter()
45
+ self.parser_backend = parser
46
+ self.copy_assets = copy_assets
47
+ self.convert_assets = convert_assets
48
+ self.hash_assets = hash_assets
49
+
50
+ self.output_root = Path(output_root)
51
+ self.assets_root = (self.output_root / "assets").resolve()
52
+
53
+ self.assets = AssetRegistry(self.assets_root, copy_assets=self.copy_assets)
54
+
55
+ # Keep formatter in sync with runtime environment
56
+ self.formatter.config = self.config # type: ignore[assignment]
57
+ self.formatter.output_path = self.assets_root # type: ignore[assignment]
58
+ self.formatter.legacy_latex_accents = self.config.legacy_latex_accents
59
+
60
+ self.engine = RenderEngine()
61
+ self._register_builtin_handlers()
62
+ self._register_entry_point_handlers()
63
+ register_all_renderers(self)
64
+
65
+ def _register_builtin_handlers(self) -> None:
66
+ """Register the initial set of handlers for the renderer."""
67
+ from ..handlers import (
68
+ admonitions as admonition_handlers,
69
+ basic as basic_handlers,
70
+ blocks as block_handlers,
71
+ code as code_handlers,
72
+ inline as inline_handlers,
73
+ links as link_handlers,
74
+ media as media_handlers,
75
+ )
76
+ from ..plugins import material as material_plugins, snippet as snippet_plugin
77
+
78
+ self.engine.collect_from(basic_handlers)
79
+ self.engine.collect_from(inline_handlers)
80
+ self.engine.collect_from(code_handlers)
81
+ self.engine.collect_from(link_handlers)
82
+ self.engine.collect_from(block_handlers)
83
+ self.engine.collect_from(admonition_handlers)
84
+ self.engine.collect_from(media_handlers)
85
+ self.register(snippet_plugin)
86
+ self.register(material_plugins)
87
+
88
+ def register(self, handler: Any) -> None:
89
+ """Register additional handlers on demand.
90
+
91
+ Arguments can be callables decorated with :func:`renders` or modules/classes
92
+ exposing decorated attributes.
93
+ """
94
+ definition = getattr(handler, "__render_rule__", None)
95
+ if definition is not None:
96
+ self.engine.register(handler)
97
+ return
98
+
99
+ self.engine.collect_from(handler)
100
+
101
+ @classmethod
102
+ def _iter_entry_point_payloads(cls) -> Iterable[Any]:
103
+ if cls._ENTRY_POINT_PAYLOADS is None:
104
+ payloads: list[Any] = []
105
+ try:
106
+ entry_points = metadata.entry_points()
107
+ group = entry_points.select(group=cls._ENTRY_POINT_GROUP)
108
+ except Exception: # pragma: no cover - defensive
109
+ cls._ENTRY_POINT_PAYLOADS = []
110
+ return ()
111
+
112
+ for entry_point in sorted(
113
+ group,
114
+ key=lambda ep: (getattr(ep, "priority", 0), getattr(ep, "name", "")),
115
+ ):
116
+ try:
117
+ payloads.append(entry_point.load())
118
+ except Exception: # pragma: no cover - defensive
119
+ continue
120
+ cls._ENTRY_POINT_PAYLOADS = payloads
121
+ return cls._ENTRY_POINT_PAYLOADS
122
+
123
+ def _register_entry_point_handlers(self) -> None:
124
+ for payload in self._iter_entry_point_payloads():
125
+ self._apply_entry_point(payload)
126
+
127
+ def _apply_entry_point(self, payload: Any) -> None:
128
+ def _accepts_renderer(target: Callable[..., Any]) -> bool:
129
+ try:
130
+ signature = inspect.signature(target)
131
+ except (TypeError, ValueError):
132
+ return False
133
+ for parameter in signature.parameters.values():
134
+ if parameter.kind in (parameter.VAR_POSITIONAL, parameter.VAR_KEYWORD):
135
+ return True
136
+ if parameter.kind in (parameter.POSITIONAL_ONLY, parameter.POSITIONAL_OR_KEYWORD):
137
+ return True
138
+ return False
139
+
140
+ if callable(payload):
141
+ if _accepts_renderer(payload):
142
+ payload(self)
143
+ return
144
+ self.register(payload)
145
+ return
146
+
147
+ register = getattr(payload, "register", None)
148
+ if callable(register):
149
+ register(self)
150
+ return
151
+
152
+ self.register(payload)
153
+
154
+ def render(
155
+ self,
156
+ html: str,
157
+ *,
158
+ runtime: Mapping[str, Any] | None = None,
159
+ state: DocumentState | None = None,
160
+ emitter: DiagnosticEmitter | None = None,
161
+ ) -> str:
162
+ """Render an HTML fragment into LaTeX."""
163
+ active_emitter = emitter or NullEmitter()
164
+ try:
165
+ soup = BeautifulSoup(html, self.parser_backend)
166
+ except FeatureNotFound:
167
+ if self.parser_backend == "html.parser":
168
+ raise
169
+ # Fall back to the built-in parser when the preferred backend is missing.
170
+ from texsmith.core.conversion.debug import record_event
171
+
172
+ record_event(
173
+ active_emitter,
174
+ "parser_fallback",
175
+ {"preferred": self.parser_backend, "fallback": "html.parser"},
176
+ )
177
+ soup = BeautifulSoup(html, "html.parser")
178
+ self.parser_backend = "html.parser"
179
+ document_state = state or DocumentState()
180
+
181
+ context = RenderContext(
182
+ config=self.config,
183
+ formatter=self.formatter,
184
+ document=soup,
185
+ assets=self.assets,
186
+ state=document_state,
187
+ )
188
+
189
+ context.attach_runtime(
190
+ copy_assets=self.copy_assets,
191
+ convert_assets=self.convert_assets,
192
+ hash_assets=self.hash_assets,
193
+ emitter=active_emitter,
194
+ )
195
+ if runtime:
196
+ context.attach_runtime(**runtime)
197
+
198
+ try:
199
+ self.engine.run(soup, context)
200
+ except Exception as exc: # pragma: no cover - defensive
201
+ raise LatexRenderingError("LaTeX rendering failed") from exc
202
+
203
+ return self._collect_output(soup)
204
+
205
+ def _collect_output(self, soup: BeautifulSoup) -> str:
206
+ """Extract the LaTeX output from the transformed soup."""
207
+ return soup.get_text()
208
+
209
+ def iter_registered_rules(self) -> Iterable[tuple[RenderPhase, str]]:
210
+ """Expose currently registered rules for debugging/reporting."""
211
+ for phase in RenderPhase:
212
+ for rule in self.engine.registry.iter_phase(phase):
213
+ yield phase, rule.name
214
+
215
+ def describe_registered_rules(self) -> list[dict[str, object]]:
216
+ """Return detailed metadata about registered rules."""
217
+ return self.engine.registry.describe()
218
+
219
+
220
+ __all__ = ["LaTeXRenderer"]