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,65 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Mapping
4
+ from dataclasses import dataclass
5
+ from pathlib import Path
6
+ from typing import Any, ClassVar
7
+
8
+ from texsmith.core.fragments.base import BaseFragment, FragmentPiece
9
+
10
+
11
+ @dataclass(frozen=True)
12
+ class BibliographyConfig:
13
+ has_citations: bool
14
+
15
+ @classmethod
16
+ def from_context(cls, context: Mapping[str, Any]) -> BibliographyConfig:
17
+ return cls(has_citations=bool(context.get("citations")))
18
+
19
+ def inject_into(self, context: dict[str, Any]) -> None:
20
+ context["ts_bibliography_enabled"] = self.has_citations
21
+
22
+
23
+ class BibliographyFragment(BaseFragment[BibliographyConfig]):
24
+ name: ClassVar[str] = "ts-bibliography"
25
+ description: ClassVar[str] = (
26
+ "Bibliography helpers (packages + backmatter) loaded when citations are present."
27
+ )
28
+ pieces: ClassVar[list[FragmentPiece]] = [
29
+ FragmentPiece(
30
+ template_path=Path(__file__).with_name("ts-bibliography.jinja.tex"),
31
+ kind="inline",
32
+ slot="extra_packages",
33
+ ),
34
+ FragmentPiece(
35
+ template_path=Path(__file__).with_name("ts-bibliography-backmatter.jinja.tex"),
36
+ kind="inline",
37
+ slot="fragment_backmatter",
38
+ ),
39
+ ]
40
+ attributes: ClassVar[dict[str, Any]] = {}
41
+ config_cls: ClassVar[type[BibliographyConfig]] = BibliographyConfig
42
+ source: ClassVar[Path] = Path(__file__).with_name("ts-bibliography.jinja.tex")
43
+
44
+ def build_config(
45
+ self, context: Mapping[str, Any], overrides: Mapping[str, Any] | None = None
46
+ ) -> BibliographyConfig:
47
+ _ = overrides
48
+ return self.config_cls.from_context(context)
49
+
50
+ def inject(
51
+ self,
52
+ config: BibliographyConfig,
53
+ context: dict[str, Any],
54
+ overrides: Mapping[str, Any] | None = None,
55
+ ) -> None:
56
+ _ = overrides
57
+ config.inject_into(context)
58
+
59
+ def should_render(self, config: BibliographyConfig) -> bool:
60
+ return config.has_citations
61
+
62
+
63
+ fragment = BibliographyFragment()
64
+
65
+ __all__ = ["BibliographyConfig", "BibliographyFragment", "fragment"]
@@ -0,0 +1,3 @@
1
+ name = "ts-bibliography"
2
+ description = "Conditional bibliography support (packages + backmatter)."
3
+ entrypoint = "texsmith.fragments.bibliography:fragment"
@@ -0,0 +1,35 @@
1
+ \BLOCK{ if citations }
2
+ \BLOCK{ if bibliography_resource }
3
+ \printbibliography
4
+ \BLOCK{ else }
5
+ \begin{thebibliography}{\VAR{citations|length}}
6
+ \BLOCK{ for key in citations }
7
+ \BLOCK{ set entry = bibliography_entries.get(key) }
8
+ \bibitem{\VAR{key}}%
9
+ \BLOCK{ if entry }
10
+ \BLOCK{ set fields = entry['fields'] }
11
+ \BLOCK{ set persons = entry['persons'] }
12
+ \BLOCK{ set authors = persons.get('author', []) }
13
+ \BLOCK{ if authors }
14
+ \VAR{ authors | map(attribute='text') | join(', ') }
15
+ \BLOCK{ endif }
16
+ \BLOCK{ if fields.get('title') }
17
+ \newblock \textit{\VAR{fields['title']}}
18
+ \BLOCK{ endif }
19
+ \BLOCK{ if fields.get('publisher') }
20
+ \newblock \VAR{fields['publisher']}
21
+ \BLOCK{ endif }
22
+ \BLOCK{ if fields.get('year') }
23
+ \newblock (\VAR{fields['year']})
24
+ \BLOCK{ endif }
25
+ \BLOCK{ if fields.get('url') }
26
+ \newblock \url{\VAR{fields['url']}}
27
+ \BLOCK{ endif }
28
+ \par\smallskip
29
+ \BLOCK{ else }
30
+ Missing bibliography entry for \VAR{key}
31
+ \BLOCK{ endif }
32
+ \BLOCK{ endfor }
33
+ \end{thebibliography}
34
+ \BLOCK{ endif }
35
+ \BLOCK{ endif }
@@ -0,0 +1,10 @@
1
+ \BLOCK{ if citations }
2
+ \usepackage{csquotes}
3
+ \BLOCK{ if bibliography_resource }
4
+ \usepackage[
5
+ backend=biber,
6
+ style=\VAR{bibliography_style|default('numeric')},
7
+ ]{biblatex}
8
+ \addbibresource{\VAR{bibliography_resource}}
9
+ \BLOCK{ endif }
10
+ \BLOCK{ endif }
@@ -0,0 +1,88 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Mapping
4
+ from dataclasses import dataclass
5
+ from pathlib import Path
6
+ from typing import Any, ClassVar
7
+
8
+ from texsmith.core.fragments.base import BaseFragment, FragmentPiece
9
+ from texsmith.core.templates.manifest import TemplateAttributeSpec
10
+
11
+
12
+ @dataclass(frozen=True)
13
+ class CalloutsConfig:
14
+ style: str | None
15
+ uses_callouts: bool
16
+
17
+ @classmethod
18
+ def from_context(cls, context: Mapping[str, Any]) -> CalloutsConfig:
19
+ return cls(
20
+ style=context.get("callout_style"),
21
+ uses_callouts=_detect_callouts(context),
22
+ )
23
+
24
+ def inject_into(self, context: dict[str, Any]) -> None:
25
+ context["ts_callouts_style"] = self.style
26
+
27
+
28
+ class CalloutsFragment(BaseFragment[CalloutsConfig]):
29
+ name: ClassVar[str] = "ts-callouts"
30
+ description: ClassVar[str] = "Reusable callout styles shared by built-in templates."
31
+ pieces: ClassVar[list[FragmentPiece]] = [
32
+ FragmentPiece(
33
+ template_path=Path(__file__).with_name("ts-callouts.jinja.sty"),
34
+ kind="package",
35
+ slot="extra_packages",
36
+ )
37
+ ]
38
+ attributes: ClassVar[dict[str, TemplateAttributeSpec]] = {
39
+ "callout_style": TemplateAttributeSpec(
40
+ default="fancy",
41
+ type="string",
42
+ choices=["fancy", "classic", "minimal"],
43
+ sources=[
44
+ "callouts.style",
45
+ "callout_style",
46
+ ],
47
+ normaliser="callout_style",
48
+ )
49
+ }
50
+ config_cls: ClassVar[type[CalloutsConfig]] = CalloutsConfig
51
+ source: ClassVar[Path] = Path(__file__).with_name("ts-callouts.jinja.sty")
52
+ context_defaults: ClassVar[dict[str, Any]] = {"extra_packages": ""}
53
+
54
+ def build_config(
55
+ self, context: Mapping[str, Any], overrides: Mapping[str, Any] | None = None
56
+ ) -> CalloutsConfig:
57
+ _ = overrides
58
+ return self.config_cls.from_context(context)
59
+
60
+ def inject(
61
+ self,
62
+ config: CalloutsConfig,
63
+ context: dict[str, Any],
64
+ overrides: Mapping[str, Any] | None = None,
65
+ ) -> None:
66
+ _ = overrides
67
+ config.inject_into(context)
68
+
69
+ def should_render(self, config: CalloutsConfig) -> bool:
70
+ return config.uses_callouts
71
+
72
+
73
+ def _detect_callouts(context: Mapping[str, Any]) -> bool:
74
+ uses_flag = context.get("ts_uses_callouts")
75
+ if isinstance(uses_flag, bool) and uses_flag:
76
+ return True
77
+
78
+ for value in context.values():
79
+ if not isinstance(value, str):
80
+ continue
81
+ if "\\begin{callout" in value:
82
+ return True
83
+ return False
84
+
85
+
86
+ fragment = CalloutsFragment()
87
+
88
+ __all__ = ["CalloutsConfig", "CalloutsFragment", "fragment"]
@@ -0,0 +1,3 @@
1
+ name = "ts-callouts"
2
+ description = "Reusable callout styles shared by built-in templates."
3
+ entrypoint = "texsmith.fragments.callouts:fragment"
@@ -0,0 +1,129 @@
1
+ \ProvidesPackage{ts-callouts}[2025/11/21 TeXSmith Callouts (generated)]
2
+
3
+ \RequirePackage{xcolor}
4
+
5
+ \makeatletter
6
+ \@ifpackageloaded{tcolorbox}{}{%
7
+ \RequirePackage{tcolorbox}%
8
+ }
9
+
10
+ \@ifundefined{tcbmany}{%
11
+ \tcbuselibrary{many}%
12
+ }{}
13
+
14
+ \@ifundefined{tcbbreakable}{%
15
+ \tcbuselibrary{breakable}%
16
+ }{}
17
+
18
+ \@ifundefined{tcbskins}{%
19
+ \tcbuselibrary{skins}%
20
+ }{}
21
+
22
+ \makeatother
23
+
24
+ \newcommand{\texsmithCalloutStyleDefault}{\VAR{callout_style|default('fancy')}}
25
+ \providecommand{\texsmithCalloutStyle}{\texsmithCalloutStyleDefault}
26
+ \providecommand{\texsmithCalloutIcon}[1]{\texsmithEmoji{#1}}
27
+ \providecommand{\texsmithCalloutTitleFormat}[1]{\bfseries ~#1}
28
+
29
+ \BLOCK{ set style = callout_style | default("fancy") }
30
+ \BLOCK{ set callouts = callouts_definitions | default({}) }
31
+
32
+ \BLOCK{ for name, cfg in callouts.items() }
33
+ \definecolor{callout\VAR{name|capitalize}Bg}{HTML}{\VAR{cfg.get("background_color", "F0F0F0")}}
34
+ \definecolor{callout\VAR{name|capitalize}Frame}{HTML}{\VAR{cfg.get("border_color", "808080")}}
35
+ \definecolor{callout\VAR{name|capitalize}Title}{HTML}{\VAR{cfg.get("title_color", cfg.get("border_color", "808080"))}}
36
+ \BLOCK{ endfor }
37
+
38
+ \BLOCK{ if style == "classic" }
39
+ % Classic (monochrome) style
40
+ \tcbset{callout base/.style={
41
+ breakable,
42
+ enhanced,
43
+ title filled=false,
44
+ arc=0mm,
45
+ boxrule=0mm,
46
+ titlerule=0mm,
47
+ lefttitle=0mm,
48
+ leftupper=1mm,
49
+ left=2mm,
50
+ colback=white,
51
+ colbacktitle=white,
52
+ colframe=black!80,
53
+ coltitle=black!80,
54
+ parbox=false,
55
+ frame hidden,
56
+ borderline west={1mm}{0mm}{black!60},
57
+ }}
58
+ \BLOCK{ for name, cfg in callouts.items() }
59
+ \tcbset{callout \VAR{name}/.style={callout base,title={\makebox[1.6em][c]{\texsmithCalloutIcon{\VAR{cfg.get("icon","ℹ")}}}}}}
60
+ \BLOCK{ endfor }
61
+ \newtcolorbox{callout}[2][]{%
62
+ callout base,
63
+ #1,
64
+ after title={\texsmithCalloutTitleFormat{#2}}%
65
+ }
66
+ \BLOCK{ elif style == "minimal" }
67
+ % Minimal style (borders only, no icons). Prefix title with bold label.
68
+ \renewcommand{\texsmithCalloutTitleFormat}[1]{\noindent\MakeUppercase{#1}:~}
69
+ \tcbset{callout base/.style={
70
+ breakable,
71
+ enhanced,
72
+ title filled=false,
73
+ arc=0.4mm,
74
+ boxrule=0.3mm,
75
+ titlerule=0mm,
76
+ lefttitle=0.2em,
77
+ leftupper=1mm,
78
+ colback=white,
79
+ colframe=black,
80
+ colbacktitle=white,
81
+ coltitle=black,
82
+ titlerule=0.2mm,
83
+ parbox=false,
84
+ }}
85
+ \BLOCK{ for name, cfg in callouts.items() }
86
+ \tcbset{callout \VAR{name}/.style={callout base}}
87
+ \BLOCK{ endfor }
88
+ \newtcolorbox{callout}[2][]{%
89
+ callout base,
90
+ title={\bfseries #2},
91
+ #1,
92
+ after title={}%
93
+ }
94
+ \BLOCK{ else }
95
+ % Fancy style (colored, icons)
96
+ \renewcommand{\texsmithCalloutTitleFormat}[1]{\raisebox{0.2em}{\bfseries ~#1}}
97
+ \tcbset{callout base/.style={
98
+ breakable,
99
+ enhanced,
100
+ arc=0.7mm,
101
+ boxrule=0.1mm,
102
+ titlerule=0mm,
103
+ left=3mm,
104
+ lefttitle=0.8mm,
105
+ leftupper=1.5mm,
106
+ coltitle=black,
107
+ fonttitle=\bfseries\sffamily,
108
+ parbox=false,
109
+ }}
110
+ \BLOCK{ for name, cfg in callouts.items() }
111
+ \tcbset{
112
+ callout \VAR{name}/.style={
113
+ callout base,
114
+ colback=white,
115
+ colframe=callout\VAR{name|capitalize}Frame,
116
+ colbacktitle=callout\VAR{name|capitalize}Frame!5!white,
117
+ coltitle=callout\VAR{name|capitalize}Title,
118
+ borderline west={1.5mm}{0mm}{callout\VAR{name|capitalize}Frame},
119
+ title={\makebox[1.6em][c]{\raisebox{0.05em}{\scalebox{1.1}{\texsmithCalloutIcon{\VAR{cfg.get("icon","ℹ")}}}}}},
120
+ }
121
+ }
122
+ \BLOCK{ endfor }
123
+
124
+ \newtcolorbox{callout}[2][]{%
125
+ callout base,
126
+ #1,
127
+ after title={\texsmithCalloutTitleFormat{#2}}%
128
+ }
129
+ \BLOCK{ endif }
@@ -0,0 +1,82 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Mapping
4
+ from dataclasses import dataclass
5
+ from pathlib import Path
6
+ from typing import Any, ClassVar
7
+
8
+ from texsmith.core.fragments.base import BaseFragment, FragmentPiece
9
+ from texsmith.core.templates.manifest import TemplateAttributeSpec
10
+
11
+
12
+ @dataclass(frozen=True)
13
+ class CodeConfig:
14
+ options: dict[str, Any]
15
+ uses_code: bool
16
+
17
+ @classmethod
18
+ def from_context(cls, context: Mapping[str, Any]) -> CodeConfig:
19
+ options = context.get("code") or {}
20
+ return cls(options=dict(options), uses_code=_detect_code(context))
21
+
22
+ def inject_into(self, context: dict[str, Any]) -> None:
23
+ context["ts_code_options"] = self.options
24
+ context["ts_code_enabled"] = self.uses_code
25
+
26
+
27
+ class CodeFragment(BaseFragment[CodeConfig]):
28
+ name: ClassVar[str] = "ts-code"
29
+ description: ClassVar[str] = "Configurable code listings used by Markdown code blocks."
30
+ pieces: ClassVar[list[FragmentPiece]] = [
31
+ FragmentPiece(
32
+ template_path=Path(__file__).with_name("ts-code.jinja.sty"),
33
+ kind="package",
34
+ slot="extra_packages",
35
+ )
36
+ ]
37
+ attributes: ClassVar[dict[str, TemplateAttributeSpec]] = {
38
+ "code": TemplateAttributeSpec(
39
+ default={"engine": "pygments", "style": "bw"},
40
+ type="mapping",
41
+ normaliser="code_options",
42
+ sources=[
43
+ "code",
44
+ ],
45
+ )
46
+ }
47
+ config_cls: ClassVar[type[CodeConfig]] = CodeConfig
48
+ source: ClassVar[Path] = Path(__file__).with_name("ts-code.jinja.sty")
49
+ context_defaults: ClassVar[dict[str, Any]] = {"extra_packages": ""}
50
+
51
+ def build_config(
52
+ self, context: Mapping[str, Any], overrides: Mapping[str, Any] | None = None
53
+ ) -> CodeConfig:
54
+ _ = overrides
55
+ return self.config_cls.from_context(context)
56
+
57
+ def inject(
58
+ self,
59
+ config: CodeConfig,
60
+ context: dict[str, Any],
61
+ overrides: Mapping[str, Any] | None = None,
62
+ ) -> None:
63
+ _ = overrides
64
+ config.inject_into(context)
65
+
66
+ def should_render(self, config: CodeConfig) -> bool:
67
+ return config.uses_code
68
+
69
+
70
+ def _detect_code(context: Mapping[str, Any]) -> bool:
71
+ for value in context.values():
72
+ if not isinstance(value, str):
73
+ continue
74
+ lowered = value.lower()
75
+ if "\\begin{code" in lowered or "\\begin{minted" in lowered or "\\py{" in lowered:
76
+ return True
77
+ return False
78
+
79
+
80
+ fragment = CodeFragment()
81
+
82
+ __all__ = ["CodeConfig", "CodeFragment", "fragment"]
@@ -0,0 +1,3 @@
1
+ name = "ts-code"
2
+ description = "Minted-based code listings used by Markdown code blocks."
3
+ entrypoint = "texsmith.fragments.code:fragment"
@@ -0,0 +1,140 @@
1
+ \BLOCK{ set _code_cfg = code if code is not none else {} }
2
+ \BLOCK{ if _code_cfg is mapping }
3
+ \BLOCK{ set _code_engine = (_code_cfg.get("engine") or "pygments") | lower }
4
+ \BLOCK{ set _code_style = _code_cfg.get("style") or "bw" }
5
+ \BLOCK{ else }
6
+ \BLOCK{ set _code_engine = (_code_cfg or "pygments") | string | lower }
7
+ \BLOCK{ set _code_style = "bw" }
8
+ \BLOCK{ endif }
9
+
10
+ \ProvidesPackage{ts-code}[2025/11/21 TeXSmith Code listings (generated)]
11
+
12
+ \RequirePackage{xparse}
13
+
14
+ \makeatletter
15
+ \@ifpackageloaded{tcolorbox}{}{%
16
+ \RequirePackage{tcolorbox}%
17
+ }
18
+ \makeatother
19
+
20
+ \tcbuselibrary{many}
21
+
22
+ \BLOCK{ if _code_engine == "minted" }
23
+ \makeatletter
24
+ \@ifpackageloaded{minted}{}{%
25
+ \RequirePackage{minted}%
26
+ }
27
+ \makeatother
28
+ \tcbuselibrary{minted}
29
+ \usemintedstyle{\VAR{_code_style}}
30
+
31
+ \newtcblisting[auto counter,number within=section,
32
+ list inside=mypyg]{code}[4][]{%
33
+ breakable,
34
+ listing only,
35
+ enhanced,
36
+ colback=white,
37
+ colframe=black!90,
38
+ colbacktitle=white,
39
+ coltitle=black,
40
+ pad at break*=1mm,
41
+ toptitle=0.15em,
42
+ bottomtitle=0.1em,
43
+ boxrule=0.07em,
44
+ fonttitle=\bfseries,
45
+ minted language=#2,
46
+ top=1mm,
47
+ arc=0.5mm,
48
+ bottom=1mm,
49
+ minted options={%
50
+ tabsize=4,
51
+ breaklines,
52
+ xleftmargin=-0.75em,
53
+ fontsize=\footnotesize,
54
+ highlightcolor=gray!30,
55
+ ignorelexererrors,
56
+ #4
57
+ },
58
+ before title={\hspace*{-0.9em}},
59
+ title={#3},
60
+ list entry={\protect\numberline{\thetcbcounter}#3},
61
+ #1
62
+ }
63
+ \BLOCK{ elif _code_engine == "listings" }
64
+ \RequirePackage{xcolor}
65
+ \makeatletter
66
+ \@ifpackageloaded{listings}{}{%
67
+ \RequirePackage{listings}%
68
+ }
69
+ \makeatother
70
+ \tcbuselibrary{listings,skins}
71
+
72
+ \lstset{
73
+ basicstyle=\ttfamily\footnotesize,
74
+ breaklines=true,
75
+ columns=fullflexible,
76
+ }
77
+
78
+ \newtcblisting[auto counter,number within=section,
79
+ list inside=mypyg]{code}[4][]{%
80
+ breakable,
81
+ enhanced,
82
+ listing only,
83
+ colback=white,
84
+ colframe=black!90,
85
+ colbacktitle=white,
86
+ coltitle=black,
87
+ pad at break*=1mm,
88
+ toptitle=0.15em,
89
+ bottomtitle=0.1em,
90
+ boxrule=0.07em,
91
+ fonttitle=\bfseries,
92
+ top=1mm,
93
+ left=1mm,
94
+ arc=0.5mm,
95
+ bottom=1mm,
96
+ title={#3},
97
+ list entry={\protect\numberline{\thetcbcounter}#3},
98
+ listing options={language=#2, breaklines=true, columns=fullflexible #4},
99
+ #1
100
+ }
101
+ \BLOCK{ else }
102
+ \RequirePackage{fancyvrb}
103
+ \RequirePackage{fvextra}
104
+ \BLOCK{ if _code_engine == "pygments" }
105
+ \RequirePackage{color}
106
+ \BLOCK{ endif }
107
+
108
+ \newtcolorbox[auto counter,number within=section,
109
+ list inside=mypyg]{code}[4][]{%
110
+ breakable,
111
+ enhanced,
112
+ colback=white,
113
+ colframe=black!90,
114
+ colbacktitle=white,
115
+ coltitle=black,
116
+ pad at break*=1mm,
117
+ toptitle=0.15em,
118
+ bottomtitle=0.1em,
119
+ boxrule=0.07em,
120
+ fonttitle=\bfseries,
121
+ top=1mm,
122
+ left=1mm,
123
+ arc=0.5mm,
124
+ bottom=1mm,
125
+ title={#3},
126
+ list entry={\protect\numberline{\thetcbcounter}#3},
127
+ #1
128
+ }
129
+
130
+ \RecustomVerbatimEnvironment{Verbatim}{Verbatim}{
131
+ breaklines,
132
+ breakanywhere,
133
+ commandchars=\\\{\},
134
+ highlightcolor=gray!20,
135
+ }
136
+
137
+ \BLOCK{ if _code_engine == "pygments" }
138
+ \VAR{pygments_style_defs|default('')}
139
+ \BLOCK{ endif }
140
+ \BLOCK{ endif }