pytex-preprocessor 0.4.6__tar.gz → 1.0.0__tar.gz

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 (148) hide show
  1. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/PKG-INFO +79 -14
  2. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/README.md +78 -13
  3. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/pyproject.toml +7 -3
  4. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex/commands/builtin.py +1 -1
  5. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex/commands/font.py +22 -18
  6. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex/commands/lengths.py +20 -2
  7. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex/model/control_sequence.py +0 -2
  8. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex/model/image.py +4 -1
  9. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex/model/package.py +0 -2
  10. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex/template.py +8 -7
  11. pytex_preprocessor-1.0.0/src/pytex_api/__init__.py +197 -0
  12. pytex_preprocessor-1.0.0/src/pytex_api/_compile.py +279 -0
  13. pytex_preprocessor-1.0.0/src/pytex_api/_models.py +125 -0
  14. pytex_preprocessor-1.0.0/src/pytex_api/_policy.py +186 -0
  15. pytex_preprocessor-1.0.0/src/pytex_api/_render.py +86 -0
  16. pytex_preprocessor-1.0.0/src/pytex_api/_sandbox.py +442 -0
  17. pytex_preprocessor-1.0.0/src/pytex_api/_security.py +184 -0
  18. pytex_preprocessor-1.0.0/src/pytex_api/sandbox_init.py +198 -0
  19. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_builder/build.py +133 -2
  20. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_builder/render.py +36 -0
  21. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_builder/tectonic.py +43 -5
  22. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_builder/variants.py +41 -10
  23. pytex_preprocessor-1.0.0/src/pytex_components/__init__.py +58 -0
  24. {pytex_preprocessor-0.4.6/src/pytex_hsrtreport → pytex_preprocessor-1.0.0/src/pytex_components}/boxes.py +14 -5
  25. {pytex_preprocessor-0.4.6/src/pytex_hsrtreport → pytex_preprocessor-1.0.0/src/pytex_components}/watermark.py +1 -1
  26. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_hsrtreport/__init__.py +28 -26
  27. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_hsrtreport/document.py +19 -9
  28. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_hsrtreport/listings.py +9 -9
  29. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_hsrtreport/logos.py +24 -7
  30. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_hsrtreport/tex/pagesetup.tex +11 -1
  31. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_hsrtreport/titlepage.py +20 -10
  32. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_markdown/convert.py +22 -26
  33. pytex_preprocessor-1.0.0/src/pytex_markdown/glyphs.py +257 -0
  34. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_markdown/protocol/convert.py +6 -6
  35. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_markdown/protocol/entries.py +2 -2
  36. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_markdown/protocol/header.py +1 -1
  37. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_markdown/protocol/shortcodes.py +6 -5
  38. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_preprocessor.egg-info/PKG-INFO +79 -14
  39. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_preprocessor.egg-info/SOURCES.txt +17 -7
  40. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_preprocessor.egg-info/entry_points.txt +1 -0
  41. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_preprocessor.egg-info/top_level.txt +2 -0
  42. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/LICENSE +0 -0
  43. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/setup.cfg +0 -0
  44. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex/__init__.py +0 -0
  45. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex/commands/__init__.py +0 -0
  46. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex/commands/biblatex.py +0 -0
  47. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex/commands/captions.py +0 -0
  48. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex/commands/cleveref.py +0 -0
  49. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex/commands/colors.py +0 -0
  50. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex/commands/conditionals.py +0 -0
  51. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex/commands/counters.py +0 -0
  52. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex/commands/definitions.py +0 -0
  53. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex/commands/floats.py +0 -0
  54. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex/commands/fontawesome.py +0 -0
  55. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex/commands/fontspec.py +0 -0
  56. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex/commands/geometry.py +0 -0
  57. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex/commands/glossaries.py +0 -0
  58. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex/commands/graphics.py +0 -0
  59. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex/commands/hooks.py +0 -0
  60. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex/commands/hyperref.py +0 -0
  61. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex/commands/listings.py +0 -0
  62. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex/commands/mdframed.py +0 -0
  63. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex/commands/picture.py +0 -0
  64. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex/commands/setspace.py +0 -0
  65. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex/commands/tables.py +0 -0
  66. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex/helpers/__init__.py +0 -0
  67. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex/helpers/coerce.py +0 -0
  68. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex/helpers/parenting.py +0 -0
  69. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex/helpers/sanitize.py +0 -0
  70. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex/helpers/with_package.py +0 -0
  71. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex/interface/__init__.py +0 -0
  72. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex/interface/control_sequence.py +0 -0
  73. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex/interface/package.py +0 -0
  74. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex/interface/tex.py +0 -0
  75. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex/model/__init__.py +0 -0
  76. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex/model/color.py +0 -0
  77. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex/model/comment.py +0 -0
  78. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex/model/concat.py +0 -0
  79. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex/model/document.py +0 -0
  80. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex/model/document_class.py +0 -0
  81. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex/model/empty.py +0 -0
  82. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex/model/environment.py +0 -0
  83. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex/model/include.py +0 -0
  84. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex/model/length.py +0 -0
  85. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex/model/math.py +0 -0
  86. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex/model/raw.py +0 -0
  87. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex/packages.py +0 -0
  88. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex/registry.py +0 -0
  89. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_analyze/__init__.py +0 -0
  90. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_analyze/analyze.py +0 -0
  91. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_analyze/optimize.py +0 -0
  92. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_builder/__init__.py +0 -0
  93. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_builder/console.py +0 -0
  94. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_builder/tex2py.py +0 -0
  95. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_builder/tree.py +0 -0
  96. {pytex_preprocessor-0.4.6/src/pytex_hsrtreport → pytex_preprocessor-1.0.0/src/pytex_components}/citations.py +0 -0
  97. {pytex_preprocessor-0.4.6/src/pytex_hsrtreport → pytex_preprocessor-1.0.0/src/pytex_components}/cleveref_names.py +0 -0
  98. {pytex_preprocessor-0.4.6/src/pytex_hsrtreport → pytex_preprocessor-1.0.0/src/pytex_components}/pagebreak.py +0 -0
  99. {pytex_preprocessor-0.4.6/src/pytex_hsrtreport → pytex_preprocessor-1.0.0/src/pytex_components}/voting.py +0 -0
  100. {pytex_preprocessor-0.4.6/src/pytex_hsrtreport → pytex_preprocessor-1.0.0/src/pytex_components}/wordcount.py +0 -0
  101. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_hsrtreport/assets/fonts/Blender/Blender-Bold.ttf +0 -0
  102. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_hsrtreport/assets/fonts/Blender/Blender-BoldItalic.ttf +0 -0
  103. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_hsrtreport/assets/fonts/Blender/Blender-Book.ttf +0 -0
  104. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_hsrtreport/assets/fonts/Blender/Blender-BookItalic.ttf +0 -0
  105. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_hsrtreport/assets/fonts/Blender/Blender-Medium.ttf +0 -0
  106. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_hsrtreport/assets/fonts/Blender/Blender-MediumItalic.ttf +0 -0
  107. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_hsrtreport/assets/fonts/Blender/Blender-Strong.ttf +0 -0
  108. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_hsrtreport/assets/fonts/Blender/Blender-Thin.ttf +0 -0
  109. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_hsrtreport/assets/fonts/Blender/Blender-ThinItalic.ttf +0 -0
  110. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_hsrtreport/assets/fonts/DIN/DIN-Black.ttf +0 -0
  111. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_hsrtreport/assets/fonts/DIN/DIN-Bold.ttf +0 -0
  112. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_hsrtreport/assets/fonts/DIN/DIN-BoldItalic.ttf +0 -0
  113. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_hsrtreport/assets/fonts/DIN/DIN-Italic.ttf +0 -0
  114. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_hsrtreport/assets/fonts/DIN/DIN-Medium.ttf +0 -0
  115. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_hsrtreport/assets/fonts/DIN/DIN-Regular.ttf +0 -0
  116. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_hsrtreport/assets/fonts/Times New Roman.ttf +0 -0
  117. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_hsrtreport/assets/logos/ASTA.svg +0 -0
  118. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_hsrtreport/assets/logos/DUMMY.png +0 -0
  119. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_hsrtreport/assets/logos/DUMMY_FOOT.png +0 -0
  120. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_hsrtreport/assets/logos/ECHO.svg +0 -0
  121. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_hsrtreport/assets/logos/HSRT.pdf +0 -0
  122. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_hsrtreport/assets/logos/INF.pdf +0 -0
  123. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_hsrtreport/assets/logos/MAKERS-Icon.svg +0 -0
  124. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_hsrtreport/assets/logos/MAKERS-RAlign.svg +0 -0
  125. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_hsrtreport/assets/logos/MAKERS.svg +0 -0
  126. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_hsrtreport/assets/logos/STUPA.pdf +0 -0
  127. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_hsrtreport/assets/logos/Skyline.pdf +0 -0
  128. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_hsrtreport/colors.py +0 -0
  129. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_hsrtreport/fonts.py +0 -0
  130. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_hsrtreport/glossary.py +0 -0
  131. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_hsrtreport/hyperref_config.py +0 -0
  132. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_hsrtreport/pagesetup.py +0 -0
  133. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_hsrtreport/variants.py +0 -0
  134. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_koma/__init__.py +0 -0
  135. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_koma/commands.py +0 -0
  136. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_koma/document.py +0 -0
  137. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_markdown/__init__.py +0 -0
  138. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_markdown/escape.py +0 -0
  139. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_markdown/frontmatter.py +0 -0
  140. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_markdown/protocol/__init__.py +0 -0
  141. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_markdown/protocol/document.py +0 -0
  142. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_markdown/protocol/signatures.py +0 -0
  143. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_preprocessor.egg-info/dependency_links.txt +0 -0
  144. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_preprocessor.egg-info/requires.txt +0 -0
  145. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_protocol/__init__.py +0 -0
  146. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_protocol/frontmatter.py +0 -0
  147. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_tikz/__init__.py +0 -0
  148. {pytex_preprocessor-0.4.6 → pytex_preprocessor-1.0.0}/src/pytex_tikz/tikz.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytex-preprocessor
3
- Version: 0.4.6
3
+ Version: 1.0.0
4
4
  Summary: Type-safe LaTeX document generation with Python
5
5
  Author-email: Frederik Beimgraben <frederik@beimgraben.net>
6
6
  License: GNU GENERAL PUBLIC LICENSE
@@ -821,13 +821,40 @@ Plain Python works too: $3^2 = \iffalse{pytex(3 ** 2)}\fi$.
821
821
  | `--no-shell-escape` | shell-escape on | disable shell-escape |
822
822
  | `-t`, `--tree` | off | also print the input's `TeX`-node tree (`tree`-style) before rendering/building |
823
823
  | `-f`, `--force` | off | skip the optimize + analysis pass and build even if problems are found |
824
- | `--variant STYLE` | auto-detect | Markdown output style (`plain`, `report`, `protocol-asta`, `protocol-stupa`) |
824
+ | `--variant STYLE` | auto-detect | Markdown output style (`plain`, `report`, `report-makers`, `protocol-asta`, `protocol-stupa`) |
825
825
  | `--config JSON` | none | JSON object of document-class params, merged over the frontmatter |
826
+ | `--untrusted` | off (trusted) | render foreign input through the trust policy (see [Security](#security-and-trust)) |
827
+ | `--trust-level LEVEL` | `trusted` | `trusted`, `sandboxed`, or `untrusted` (see [Security](#security-and-trust)) |
826
828
 
827
829
  Shell-escape is on by default because inline images decode their base64
828
830
  payloads at compile time. The build runs tectonic, then `makeindex` (for
829
831
  `glossaries`/acronyms), then reruns tectonic when an index changed.
830
832
 
833
+ ### Security and trust
834
+
835
+ By default the CLI runs in a **trusted** context: it imports and executes `.py`
836
+ inputs, evaluates `.tex` `pytex(...)` replacements and Markdown `eval`
837
+ comments, and enables shell-escape. That is code execution by design — it is
838
+ how PyTeX documents work — and is safe **only for documents you wrote
839
+ yourself**. Do not run the default CLI on a file from a source you do not
840
+ trust.
841
+
842
+ To render input from a foreign or untrusted source, pass `--untrusted` (or
843
+ `--trust-level {sandboxed,untrusted}`). These route the build through the
844
+ `pytex_api` trust policy, which:
845
+
846
+ - refuses `.py` / `.tex.py` inputs (no Python execution),
847
+ - leaves `.tex` `pytex(...)` markers and Markdown `eval` comments inert,
848
+ - forces shell-escape **off** and rejects code-/file-surface packages
849
+ (`minted`, `shellesc`, `pythontex`, …) and anything off the package
850
+ allowlist,
851
+ - applies CPU/memory/output resource limits, and
852
+ - for `sandboxed`, additionally requires the Podman OS sandbox for PDF builds.
853
+
854
+ `--untrusted` is shorthand for `--trust-level untrusted`. The two flags are
855
+ mutually exclusive; `trusted` is the default, so existing invocations are
856
+ unchanged.
857
+
831
858
  Output is minimal and color-tagged (`==>`, `note:`, `warning:`, `error:`),
832
859
  following tectonic's style; on failure it points at the likely cause and the
833
860
  log file. Set `NO_COLOR` to disable color.
@@ -877,10 +904,11 @@ Document (article)
877
904
  | `pytex` | core node model, `Document`, math, tables, graphics, and factories for the common LaTeX packages (biblatex, cleveref, glossaries, hyperref, listings, ...). |
878
905
  | `pytex_koma` | KOMA-Script classes and commands (`Addchap`, `Minisec`, `KOMAoptions`, ...). |
879
906
  | `pytex_tikz` | TikZ pictures and primitives (`TikzPicture`, `Draw`, `Node`, `Circle`, ...). |
880
- | `pytex_markdown` | Markdown -> native `TeX` conversion (see below). |
907
+ | `pytex_components` | reusable, template-agnostic widgets: colored callout boxes (`ColoredBox` + presets), a voting tally, draft watermark, word-count and smart-pagebreak macros, a clickable author-year citation, German cleveref labels. |
908
+ | `pytex_markdown` | Markdown -> native `TeX` conversion (see below), including `pytex_markdown.protocol` (STUPA/AStA meeting minutes) and `pytex_markdown.frontmatter` (YAML frontmatter parsing). |
881
909
  | `pytex_analyze` | static checks over the node tree (dangling refs, duplicate labels, missing images), plus `Optimize` to simplify a tree render-equivalently. |
882
- | `pytex_hsrtreport` | HSRT report document class, colored callout boxes, title pages, glossary/citation helpers. |
883
- | `pytex_protocol` | STUPA/AStA meeting minutes from Markdown, built on `pytex_hsrtreport`. |
910
+ | `pytex_hsrtreport` | HSRT report document class, title pages, logos, and HSRT colors/fonts/glossary helpers. Builds on `pytex_components` (and re-exports it for compatibility). |
911
+ | `pytex_protocol` | deprecated alias for `pytex_markdown.protocol` (kept as a re-export shim). |
884
912
 
885
913
  ## Markdown
886
914
 
@@ -893,15 +921,23 @@ body = Markdown("# Title\n\nText with **bold**, `code`, [a link](https://x).")
893
921
  body = IncludeMarkdown("notes.md", base_level=-1) # base_level=-1: # -> \chapter
894
922
  ```
895
923
 
896
- Headings, emphasis, inline/fenced code, lists, links, images, block quotes and
897
- thematic breaks map to the standard pytex library; text is LaTeX-escaped.
898
- GitHub-style callouts become HSRT colored boxes (so the module depends on
899
- `pytex_hsrtreport`):
900
-
901
- ```md
902
- > [!NOTE] -> InfoBox > [!IMPORTANT] -> ImportantBox
903
- > [!TIP] -> SuccessBox > [!WARNING] -> WarningBox
904
- ```
924
+ Headings, emphasis, inline/fenced code, lists, links, images, GFM tables, block
925
+ quotes and thematic breaks map to the standard pytex library; text is
926
+ LaTeX-escaped. Some extras on top of plain Markdown:
927
+
928
+ - **GitHub-style callouts** become colored boxes (from `pytex_components`):
929
+ ```md
930
+ > [!NOTE] -> InfoBox > [!IMPORTANT] -> ImportantBox
931
+ > [!TIP] -> SuccessBox > [!WARNING] -> WarningBox
932
+ ```
933
+ - **Citations** in Pandoc syntax: `[@key]` / `[@key, p. 5]` -> `\autocite`,
934
+ `[@a; @b]` -> a combined cite, and a narrative `@key` -> `\textcite`.
935
+ - **Bibliography** from frontmatter — `bibliography:` is either inline BibTeX (a
936
+ `|` block scalar) or a path to a `.bib` file; reports print a numbered
937
+ `\printbibliography`.
938
+ - ASCII **math arrows** (`->`, `=>`, `<->`, ...) become inline math arrows, the
939
+ **euro sign** `€` becomes a font-independent `\euro{}`, and tables get a bit of
940
+ vertical breathing room.
905
941
 
906
942
  Both factories are registered, so they work in `\iffalse{pytex(...)}\fi`
907
943
  replacements in `.tex` sources too.
@@ -915,6 +951,7 @@ document chosen by `--variant`:
915
951
  | --- | --- |
916
952
  | `plain` | a bare `Document` (default class `article`); `#` -> `\section`. |
917
953
  | `report` | an HSRT report with title page and table of contents; `#` -> `\chapter`. |
954
+ | `report-makers` | a `report` branded with the MAKERS logo (title page + footer). |
918
955
  | `protocol-asta` | an AStA meeting protocol (HSRT report, AStA logos). |
919
956
  | `protocol-stupa` | a StuPa meeting protocol (HSRT report, StuPa logos). |
920
957
 
@@ -933,6 +970,13 @@ object. For styles with a title page (`report`), the title is taken from
933
970
  `title:`/`--config` if given, otherwise from the first `#` heading (which is then
934
971
  not also rendered as a chapter).
935
972
 
973
+ The report styles read further frontmatter keys: `author`, `abstract`,
974
+ `keywords`, title-page `datalines` (a list of `"Label: value"` entries),
975
+ `bibliography` (see [Markdown](#markdown)), `logos` (title-page logos — vendored
976
+ names like `INF`/`MAKERS` and/or paths to custom image files), and the labels
977
+ `abstract_heading` / `keywords_heading` to rename the default "Abstract" /
978
+ "Keywords" sections.
979
+
936
980
  ## Converting LaTeX to PyTeX
937
981
 
938
982
  `pytex-tex2py` turns an existing `.tex` file into an equivalent `.tex.py`
@@ -961,6 +1005,27 @@ pytex examples/replacements.py.tex --build
961
1005
  pytex examples/notes.md --build
962
1006
  ```
963
1007
 
1008
+ ## Stability
1009
+
1010
+ From 1.0 the project follows [Semantic Versioning](https://semver.org). The
1011
+ public API is what each package exports through its top-level `__all__`:
1012
+ everything reachable as `from pytex import X` (and the same for `pytex_koma`,
1013
+ `pytex_tikz`, `pytex_components`, `pytex_markdown`, `pytex_analyze`,
1014
+ `pytex_hsrtreport`). Breaking those names needs a major version bump.
1015
+
1016
+ Also part of the contract: the registry keys exposed to `\iffalse{pytex(...)}\fi`
1017
+ markers — they are the factory names, so renaming a registered factory is a
1018
+ breaking change (which is why the `\fill` length is `Fill_len`, leaving the bare
1019
+ `Fill` key to the TikZ path command).
1020
+
1021
+ Internal and not covered by the guarantee: any name with a leading underscore,
1022
+ modules whose name starts with an underscore (e.g. `pytex_api._policy`,
1023
+ `pytex_api._compile`), and anything not listed in a package's `__all__`. Import
1024
+ those at your own risk.
1025
+
1026
+ Deprecated shims (`pytex_protocol`, the `pytex.commands.lengths.Fill` alias) keep
1027
+ working with a `DeprecationWarning` and may be removed in the next major release.
1028
+
964
1029
  ## License
965
1030
 
966
1031
  GNU General Public License v3.0 or later (GPL-3.0-or-later). See
@@ -129,13 +129,40 @@ Plain Python works too: $3^2 = \iffalse{pytex(3 ** 2)}\fi$.
129
129
  | `--no-shell-escape` | shell-escape on | disable shell-escape |
130
130
  | `-t`, `--tree` | off | also print the input's `TeX`-node tree (`tree`-style) before rendering/building |
131
131
  | `-f`, `--force` | off | skip the optimize + analysis pass and build even if problems are found |
132
- | `--variant STYLE` | auto-detect | Markdown output style (`plain`, `report`, `protocol-asta`, `protocol-stupa`) |
132
+ | `--variant STYLE` | auto-detect | Markdown output style (`plain`, `report`, `report-makers`, `protocol-asta`, `protocol-stupa`) |
133
133
  | `--config JSON` | none | JSON object of document-class params, merged over the frontmatter |
134
+ | `--untrusted` | off (trusted) | render foreign input through the trust policy (see [Security](#security-and-trust)) |
135
+ | `--trust-level LEVEL` | `trusted` | `trusted`, `sandboxed`, or `untrusted` (see [Security](#security-and-trust)) |
134
136
 
135
137
  Shell-escape is on by default because inline images decode their base64
136
138
  payloads at compile time. The build runs tectonic, then `makeindex` (for
137
139
  `glossaries`/acronyms), then reruns tectonic when an index changed.
138
140
 
141
+ ### Security and trust
142
+
143
+ By default the CLI runs in a **trusted** context: it imports and executes `.py`
144
+ inputs, evaluates `.tex` `pytex(...)` replacements and Markdown `eval`
145
+ comments, and enables shell-escape. That is code execution by design — it is
146
+ how PyTeX documents work — and is safe **only for documents you wrote
147
+ yourself**. Do not run the default CLI on a file from a source you do not
148
+ trust.
149
+
150
+ To render input from a foreign or untrusted source, pass `--untrusted` (or
151
+ `--trust-level {sandboxed,untrusted}`). These route the build through the
152
+ `pytex_api` trust policy, which:
153
+
154
+ - refuses `.py` / `.tex.py` inputs (no Python execution),
155
+ - leaves `.tex` `pytex(...)` markers and Markdown `eval` comments inert,
156
+ - forces shell-escape **off** and rejects code-/file-surface packages
157
+ (`minted`, `shellesc`, `pythontex`, …) and anything off the package
158
+ allowlist,
159
+ - applies CPU/memory/output resource limits, and
160
+ - for `sandboxed`, additionally requires the Podman OS sandbox for PDF builds.
161
+
162
+ `--untrusted` is shorthand for `--trust-level untrusted`. The two flags are
163
+ mutually exclusive; `trusted` is the default, so existing invocations are
164
+ unchanged.
165
+
139
166
  Output is minimal and color-tagged (`==>`, `note:`, `warning:`, `error:`),
140
167
  following tectonic's style; on failure it points at the likely cause and the
141
168
  log file. Set `NO_COLOR` to disable color.
@@ -185,10 +212,11 @@ Document (article)
185
212
  | `pytex` | core node model, `Document`, math, tables, graphics, and factories for the common LaTeX packages (biblatex, cleveref, glossaries, hyperref, listings, ...). |
186
213
  | `pytex_koma` | KOMA-Script classes and commands (`Addchap`, `Minisec`, `KOMAoptions`, ...). |
187
214
  | `pytex_tikz` | TikZ pictures and primitives (`TikzPicture`, `Draw`, `Node`, `Circle`, ...). |
188
- | `pytex_markdown` | Markdown -> native `TeX` conversion (see below). |
215
+ | `pytex_components` | reusable, template-agnostic widgets: colored callout boxes (`ColoredBox` + presets), a voting tally, draft watermark, word-count and smart-pagebreak macros, a clickable author-year citation, German cleveref labels. |
216
+ | `pytex_markdown` | Markdown -> native `TeX` conversion (see below), including `pytex_markdown.protocol` (STUPA/AStA meeting minutes) and `pytex_markdown.frontmatter` (YAML frontmatter parsing). |
189
217
  | `pytex_analyze` | static checks over the node tree (dangling refs, duplicate labels, missing images), plus `Optimize` to simplify a tree render-equivalently. |
190
- | `pytex_hsrtreport` | HSRT report document class, colored callout boxes, title pages, glossary/citation helpers. |
191
- | `pytex_protocol` | STUPA/AStA meeting minutes from Markdown, built on `pytex_hsrtreport`. |
218
+ | `pytex_hsrtreport` | HSRT report document class, title pages, logos, and HSRT colors/fonts/glossary helpers. Builds on `pytex_components` (and re-exports it for compatibility). |
219
+ | `pytex_protocol` | deprecated alias for `pytex_markdown.protocol` (kept as a re-export shim). |
192
220
 
193
221
  ## Markdown
194
222
 
@@ -201,15 +229,23 @@ body = Markdown("# Title\n\nText with **bold**, `code`, [a link](https://x).")
201
229
  body = IncludeMarkdown("notes.md", base_level=-1) # base_level=-1: # -> \chapter
202
230
  ```
203
231
 
204
- Headings, emphasis, inline/fenced code, lists, links, images, block quotes and
205
- thematic breaks map to the standard pytex library; text is LaTeX-escaped.
206
- GitHub-style callouts become HSRT colored boxes (so the module depends on
207
- `pytex_hsrtreport`):
208
-
209
- ```md
210
- > [!NOTE] -> InfoBox > [!IMPORTANT] -> ImportantBox
211
- > [!TIP] -> SuccessBox > [!WARNING] -> WarningBox
212
- ```
232
+ Headings, emphasis, inline/fenced code, lists, links, images, GFM tables, block
233
+ quotes and thematic breaks map to the standard pytex library; text is
234
+ LaTeX-escaped. Some extras on top of plain Markdown:
235
+
236
+ - **GitHub-style callouts** become colored boxes (from `pytex_components`):
237
+ ```md
238
+ > [!NOTE] -> InfoBox > [!IMPORTANT] -> ImportantBox
239
+ > [!TIP] -> SuccessBox > [!WARNING] -> WarningBox
240
+ ```
241
+ - **Citations** in Pandoc syntax: `[@key]` / `[@key, p. 5]` -> `\autocite`,
242
+ `[@a; @b]` -> a combined cite, and a narrative `@key` -> `\textcite`.
243
+ - **Bibliography** from frontmatter — `bibliography:` is either inline BibTeX (a
244
+ `|` block scalar) or a path to a `.bib` file; reports print a numbered
245
+ `\printbibliography`.
246
+ - ASCII **math arrows** (`->`, `=>`, `<->`, ...) become inline math arrows, the
247
+ **euro sign** `€` becomes a font-independent `\euro{}`, and tables get a bit of
248
+ vertical breathing room.
213
249
 
214
250
  Both factories are registered, so they work in `\iffalse{pytex(...)}\fi`
215
251
  replacements in `.tex` sources too.
@@ -223,6 +259,7 @@ document chosen by `--variant`:
223
259
  | --- | --- |
224
260
  | `plain` | a bare `Document` (default class `article`); `#` -> `\section`. |
225
261
  | `report` | an HSRT report with title page and table of contents; `#` -> `\chapter`. |
262
+ | `report-makers` | a `report` branded with the MAKERS logo (title page + footer). |
226
263
  | `protocol-asta` | an AStA meeting protocol (HSRT report, AStA logos). |
227
264
  | `protocol-stupa` | a StuPa meeting protocol (HSRT report, StuPa logos). |
228
265
 
@@ -241,6 +278,13 @@ object. For styles with a title page (`report`), the title is taken from
241
278
  `title:`/`--config` if given, otherwise from the first `#` heading (which is then
242
279
  not also rendered as a chapter).
243
280
 
281
+ The report styles read further frontmatter keys: `author`, `abstract`,
282
+ `keywords`, title-page `datalines` (a list of `"Label: value"` entries),
283
+ `bibliography` (see [Markdown](#markdown)), `logos` (title-page logos — vendored
284
+ names like `INF`/`MAKERS` and/or paths to custom image files), and the labels
285
+ `abstract_heading` / `keywords_heading` to rename the default "Abstract" /
286
+ "Keywords" sections.
287
+
244
288
  ## Converting LaTeX to PyTeX
245
289
 
246
290
  `pytex-tex2py` turns an existing `.tex` file into an equivalent `.tex.py`
@@ -269,6 +313,27 @@ pytex examples/replacements.py.tex --build
269
313
  pytex examples/notes.md --build
270
314
  ```
271
315
 
316
+ ## Stability
317
+
318
+ From 1.0 the project follows [Semantic Versioning](https://semver.org). The
319
+ public API is what each package exports through its top-level `__all__`:
320
+ everything reachable as `from pytex import X` (and the same for `pytex_koma`,
321
+ `pytex_tikz`, `pytex_components`, `pytex_markdown`, `pytex_analyze`,
322
+ `pytex_hsrtreport`). Breaking those names needs a major version bump.
323
+
324
+ Also part of the contract: the registry keys exposed to `\iffalse{pytex(...)}\fi`
325
+ markers — they are the factory names, so renaming a registered factory is a
326
+ breaking change (which is why the `\fill` length is `Fill_len`, leaving the bare
327
+ `Fill` key to the TikZ path command).
328
+
329
+ Internal and not covered by the guarantee: any name with a leading underscore,
330
+ modules whose name starts with an underscore (e.g. `pytex_api._policy`,
331
+ `pytex_api._compile`), and anything not listed in a package's `__all__`. Import
332
+ those at your own risk.
333
+
334
+ Deprecated shims (`pytex_protocol`, the `pytex.commands.lengths.Fill` alias) keep
335
+ working with a `DeprecationWarning` and may be removed in the next major release.
336
+
272
337
  ## License
273
338
 
274
339
  GNU General Public License v3.0 or later (GPL-3.0-or-later). See
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pytex-preprocessor"
7
- version = "0.4.6"
7
+ version = "1.0.0"
8
8
  authors = [
9
9
  { name="Frederik Beimgraben", email="frederik@beimgraben.net" },
10
10
  ]
@@ -23,6 +23,7 @@ dev = ["pytest", "ruff", "basedpyright"]
23
23
  [project.scripts]
24
24
  pytex = "pytex_builder.build:main"
25
25
  pytex-tex2py = "pytex_builder.tex2py:main"
26
+ pytex-sandbox-init = "pytex_api.sandbox_init:main"
26
27
 
27
28
  [tool.setuptools.packages.find]
28
29
  where = ["src"]
@@ -43,7 +44,10 @@ target-version = "py313"
43
44
  src = ["src", "tests"]
44
45
  # Uses Python 3.14 t-string syntax; the py313 target cannot parse it. Excluded
45
46
  # from lint/format (it is exercised by pytest on 3.14).
46
- extend-exclude = ["tests/pytex/test_template.py"]
47
+ # `examples/` is illustrative-only and not part of the CI lint scope (`ruff
48
+ # {check,format} src tests`); excluding it keeps a bare `ruff check` matching CI
49
+ # instead of choking on example-only files (some also use 3.14 t-strings).
50
+ extend-exclude = ["tests/pytex/test_template.py", "examples"]
47
51
 
48
52
  [tool.ruff.lint]
49
53
  select = [
@@ -85,4 +89,4 @@ ignore = ["N802"]
85
89
  "src/pytex_builder/tectonic.py" = ["E501"]
86
90
 
87
91
  [tool.ruff.lint.isort]
88
- known-first-party = ["pytex", "pytex_builder", "pytex_koma", "pytex_tikz", "pytex_hsrtreport", "pytex_markdown"]
92
+ known-first-party = ["pytex_components", "pytex", "pytex_builder", "pytex_koma", "pytex_tikz", "pytex_hsrtreport", "pytex_markdown"]
@@ -543,7 +543,7 @@ def Verb(body: str, delim: str = "|") -> TeX:
543
543
 
544
544
 
545
545
  # Size switches moved to pytex/commands/font.py (zero-arg ControlSequence wrappers).
546
- # Compose with `Concat(Large(), " body")` for inline use.
546
+ # Compose with `Concat(large(), " body")` for inline use.
547
547
 
548
548
 
549
549
  @Registry.add
@@ -3,28 +3,28 @@ from ..model.control_sequence import ControlSequence, Parameter
3
3
  from ..registry import Registry
4
4
 
5
5
  __all__ = [
6
+ "LARGE",
6
7
  "Bfseries",
7
8
  "Fontsize",
8
- "Footnotesize",
9
9
  "Huge",
10
- "HugeBig",
11
10
  "Itshape",
12
11
  "Large",
13
- "LargeBig",
14
- "LargeMid",
15
12
  "Mdseries",
16
13
  "Normalfont",
17
- "Normalsize",
18
14
  "Rmfamily",
19
- "Scriptsize",
20
15
  "Scshape",
21
16
  "Selectfont",
22
17
  "Sffamily",
23
18
  "Slshape",
24
- "Small",
25
- "Tiny",
26
19
  "Ttfamily",
27
20
  "Upshape",
21
+ "footnotesize",
22
+ "huge",
23
+ "large",
24
+ "normalsize",
25
+ "scriptsize",
26
+ "small",
27
+ "tiny",
28
28
  ]
29
29
 
30
30
 
@@ -88,51 +88,55 @@ def Normalfont() -> TeX:
88
88
  return ControlSequence("normalfont", ())
89
89
 
90
90
 
91
+ # Size switches use the LaTeX command spelling verbatim as the factory name:
92
+ # `\large`/`\Large`/`\LARGE` and `\huge`/`\Huge` differ only by case, which
93
+ # PascalCase cannot encode without a collision. Python identifiers are
94
+ # case-sensitive, so `large`/`Large`/`LARGE` map one-to-one and predictably.
91
95
  @Registry.add
92
- def Tiny() -> TeX:
96
+ def tiny() -> TeX:
93
97
  return ControlSequence("tiny", ())
94
98
 
95
99
 
96
100
  @Registry.add
97
- def Scriptsize() -> TeX:
101
+ def scriptsize() -> TeX:
98
102
  return ControlSequence("scriptsize", ())
99
103
 
100
104
 
101
105
  @Registry.add
102
- def Footnotesize() -> TeX:
106
+ def footnotesize() -> TeX:
103
107
  return ControlSequence("footnotesize", ())
104
108
 
105
109
 
106
110
  @Registry.add
107
- def Small() -> TeX:
111
+ def small() -> TeX:
108
112
  return ControlSequence("small", ())
109
113
 
110
114
 
111
115
  @Registry.add
112
- def Normalsize() -> TeX:
116
+ def normalsize() -> TeX:
113
117
  return ControlSequence("normalsize", ())
114
118
 
115
119
 
116
120
  @Registry.add
117
- def Large() -> TeX:
121
+ def large() -> TeX:
118
122
  return ControlSequence("large", ())
119
123
 
120
124
 
121
125
  @Registry.add
122
- def LargeMid() -> TeX:
126
+ def Large() -> TeX:
123
127
  return ControlSequence("Large", ())
124
128
 
125
129
 
126
130
  @Registry.add
127
- def LargeBig() -> TeX:
131
+ def LARGE() -> TeX:
128
132
  return ControlSequence("LARGE", ())
129
133
 
130
134
 
131
135
  @Registry.add
132
- def Huge() -> TeX:
136
+ def huge() -> TeX:
133
137
  return ControlSequence("huge", ())
134
138
 
135
139
 
136
140
  @Registry.add
137
- def HugeBig() -> TeX:
141
+ def Huge() -> TeX:
138
142
  return ControlSequence("Huge", ())
@@ -1,3 +1,5 @@
1
+ import warnings
2
+
1
3
  from ..interface.tex import TeX
2
4
  from ..model.control_sequence import ControlSequence, Parameter
3
5
  from ..model.length import Length
@@ -11,7 +13,7 @@ __all__ = [
11
13
  "Baselinestretch",
12
14
  "Columnsep",
13
15
  "Columnwidth",
14
- "Fill",
16
+ "Fill_len",
15
17
  "Footskip",
16
18
  "Headheight",
17
19
  "Headsep",
@@ -196,5 +198,21 @@ def Arraystretch_len() -> Length:
196
198
 
197
199
 
198
200
  @Registry.add
199
- def Fill() -> Length:
201
+ def Fill_len() -> Length:
200
202
  return _const("fill")
203
+
204
+
205
+ def __getattr__(name: str) -> object:
206
+ # `Fill` (the ``\fill`` rubber length) was renamed to `Fill_len` so its
207
+ # registry key no longer collides with `pytex_tikz.Fill` (the ``\fill`` path
208
+ # command). The suffix mirrors `Arraystretch_len`, which already dodges the
209
+ # `Arraystretch` table command the same way. Kept as a deprecated alias.
210
+ if name == "Fill":
211
+ warnings.warn(
212
+ "pytex.commands.lengths.Fill was renamed to Fill_len to free the "
213
+ + "'Fill' registry key for pytex_tikz.Fill; import Fill_len instead.",
214
+ DeprecationWarning,
215
+ stacklevel=2,
216
+ )
217
+ return Fill_len
218
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
@@ -36,8 +36,6 @@ class Parameter[T: ParameterType = ParameterType](TeX):
36
36
  @property
37
37
  @override
38
38
  def rendered(self) -> str:
39
- """Render this Node to a valid LaTeX-String"""
40
-
41
39
  content: str | TeX = ""
42
40
 
43
41
  if isinstance(self.value, (TeX, str)):
@@ -59,7 +59,10 @@ class IncludeImage(TeX):
59
59
  if src.suffix.lower() in PDF_COMPAT:
60
60
  return src
61
61
  if src.suffix.lower() == ".svg":
62
- digest = hashlib.sha1(src.resolve().as_posix().encode()).hexdigest()[:10]
62
+ # Content-address the cache: hash the SVG bytes, not its path, so an
63
+ # edited source reconverts instead of reusing a stale PDF that
64
+ # happens to live at the same path-derived name.
65
+ digest = hashlib.sha1(src.read_bytes()).hexdigest()[:10]
63
66
  return Path("build") / f"{src.stem}-{digest}.pdf"
64
67
  raise ValueError(f"unsupported image extension: {src.suffix}")
65
68
 
@@ -87,8 +87,6 @@ class Package(PackageProtocol, TeX):
87
87
  @property
88
88
  @override
89
89
  def rendered(self) -> str:
90
- """Render this object to a valid LaTeX-String"""
91
-
92
90
  return f"\\usepackage{self._options_string}{{{self.name}}}"
93
91
 
94
92
  @property
@@ -65,13 +65,14 @@ _CONVERSIONS = {"r": repr, "s": str, "a": ascii}
65
65
 
66
66
  def tex(template: _Template) -> TeX:
67
67
  """Render a t-string into a `TeX` tree (see the module docstring)."""
68
- parts: list[TeX] = []
69
- for item in template:
70
- if isinstance(item, str):
71
- parts.append(Raw(item)) # literal LaTeX
72
- else:
73
- parts.append(_coerce(item.value, item.conversion, item.format_spec))
74
- return Concat(*parts)
68
+ return Concat(
69
+ *(
70
+ Raw(item) # literal LaTeX
71
+ if isinstance(item, str)
72
+ else _coerce(item.value, item.conversion, item.format_spec)
73
+ for item in template
74
+ )
75
+ )
75
76
 
76
77
 
77
78
  def _coerce(value: object, conversion: str | None = None, spec: object = "") -> TeX: