pytex-preprocessor 0.1.3__tar.gz → 0.2.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 (127) hide show
  1. pytex_preprocessor-0.2.0/PKG-INFO +170 -0
  2. pytex_preprocessor-0.2.0/README.md +156 -0
  3. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/pyproject.toml +1 -1
  4. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex/commands/builtin.py +16 -13
  5. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex/commands/definitions.py +47 -8
  6. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex/commands/glossaries.py +4 -4
  7. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex_hsrtreport/boxes.py +3 -3
  8. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex_hsrtreport/titlepage.py +4 -3
  9. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex_hsrtreport/voting.py +2 -2
  10. pytex_preprocessor-0.2.0/src/pytex_preprocessor.egg-info/PKG-INFO +170 -0
  11. pytex_preprocessor-0.1.3/PKG-INFO +0 -82
  12. pytex_preprocessor-0.1.3/README.md +0 -68
  13. pytex_preprocessor-0.1.3/src/pytex_preprocessor.egg-info/PKG-INFO +0 -82
  14. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/setup.cfg +0 -0
  15. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex/__init__.py +0 -0
  16. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex/commands/__init__.py +0 -0
  17. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex/commands/biblatex.py +0 -0
  18. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex/commands/captions.py +0 -0
  19. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex/commands/cleveref.py +0 -0
  20. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex/commands/colors.py +0 -0
  21. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex/commands/conditionals.py +0 -0
  22. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex/commands/counters.py +0 -0
  23. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex/commands/floats.py +0 -0
  24. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex/commands/font.py +0 -0
  25. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex/commands/fontawesome.py +0 -0
  26. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex/commands/fontspec.py +0 -0
  27. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex/commands/geometry.py +0 -0
  28. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex/commands/graphics.py +0 -0
  29. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex/commands/hooks.py +0 -0
  30. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex/commands/hyperref.py +0 -0
  31. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex/commands/lengths.py +0 -0
  32. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex/commands/listings.py +0 -0
  33. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex/commands/mdframed.py +0 -0
  34. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex/commands/picture.py +0 -0
  35. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex/commands/setspace.py +0 -0
  36. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex/commands/tables.py +0 -0
  37. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex/helpers/__init__.py +0 -0
  38. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex/helpers/coerce.py +0 -0
  39. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex/helpers/parenting.py +0 -0
  40. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex/helpers/sanitize.py +0 -0
  41. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex/helpers/with_package.py +0 -0
  42. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex/interface/__init__.py +0 -0
  43. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex/interface/control_sequence.py +0 -0
  44. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex/interface/package.py +0 -0
  45. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex/interface/tex.py +0 -0
  46. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex/model/__init__.py +0 -0
  47. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex/model/color.py +0 -0
  48. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex/model/concat.py +0 -0
  49. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex/model/control_sequence.py +0 -0
  50. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex/model/document.py +0 -0
  51. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex/model/document_class.py +0 -0
  52. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex/model/empty.py +0 -0
  53. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex/model/environment.py +0 -0
  54. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex/model/image.py +0 -0
  55. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex/model/include.py +0 -0
  56. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex/model/length.py +0 -0
  57. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex/model/math.py +0 -0
  58. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex/model/package.py +0 -0
  59. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex/model/raw.py +0 -0
  60. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex/packages.py +0 -0
  61. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex/registry.py +0 -0
  62. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex_builder/__init__.py +0 -0
  63. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex_builder/build.py +0 -0
  64. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex_builder/console.py +0 -0
  65. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex_builder/render.py +0 -0
  66. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex_builder/tectonic.py +0 -0
  67. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex_hsrtreport/__init__.py +0 -0
  68. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex_hsrtreport/assets/fonts/Blender/Blender-Bold.ttf +0 -0
  69. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex_hsrtreport/assets/fonts/Blender/Blender-BoldItalic.ttf +0 -0
  70. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex_hsrtreport/assets/fonts/Blender/Blender-Book.ttf +0 -0
  71. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex_hsrtreport/assets/fonts/Blender/Blender-BookItalic.ttf +0 -0
  72. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex_hsrtreport/assets/fonts/Blender/Blender-Medium.ttf +0 -0
  73. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex_hsrtreport/assets/fonts/Blender/Blender-MediumItalic.ttf +0 -0
  74. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex_hsrtreport/assets/fonts/Blender/Blender-Strong.ttf +0 -0
  75. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex_hsrtreport/assets/fonts/Blender/Blender-Thin.ttf +0 -0
  76. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex_hsrtreport/assets/fonts/Blender/Blender-ThinItalic.ttf +0 -0
  77. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex_hsrtreport/assets/fonts/DIN/DIN-Black.ttf +0 -0
  78. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex_hsrtreport/assets/fonts/DIN/DIN-Bold.ttf +0 -0
  79. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex_hsrtreport/assets/fonts/DIN/DIN-BoldItalic.ttf +0 -0
  80. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex_hsrtreport/assets/fonts/DIN/DIN-Italic.ttf +0 -0
  81. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex_hsrtreport/assets/fonts/DIN/DIN-Medium.ttf +0 -0
  82. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex_hsrtreport/assets/fonts/DIN/DIN-Regular.ttf +0 -0
  83. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex_hsrtreport/assets/fonts/Times New Roman.ttf +0 -0
  84. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex_hsrtreport/assets/logos/ASTA.svg +0 -0
  85. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex_hsrtreport/assets/logos/DUMMY.png +0 -0
  86. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex_hsrtreport/assets/logos/DUMMY_FOOT.png +0 -0
  87. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex_hsrtreport/assets/logos/ECHO.svg +0 -0
  88. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex_hsrtreport/assets/logos/HSRT.pdf +0 -0
  89. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex_hsrtreport/assets/logos/INF.pdf +0 -0
  90. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex_hsrtreport/assets/logos/STUPA.pdf +0 -0
  91. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex_hsrtreport/assets/logos/Skyline.pdf +0 -0
  92. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex_hsrtreport/citations.py +0 -0
  93. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex_hsrtreport/cleveref_names.py +0 -0
  94. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex_hsrtreport/colors.py +0 -0
  95. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex_hsrtreport/document.py +0 -0
  96. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex_hsrtreport/fonts.py +0 -0
  97. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex_hsrtreport/glossary.py +0 -0
  98. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex_hsrtreport/hyperref_config.py +0 -0
  99. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex_hsrtreport/listings.py +0 -0
  100. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex_hsrtreport/logos.py +0 -0
  101. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex_hsrtreport/pagebreak.py +0 -0
  102. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex_hsrtreport/pagesetup.py +0 -0
  103. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex_hsrtreport/tex/pagesetup.tex +0 -0
  104. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex_hsrtreport/variants.py +0 -0
  105. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex_hsrtreport/watermark.py +0 -0
  106. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex_hsrtreport/wordcount.py +0 -0
  107. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex_koma/__init__.py +0 -0
  108. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex_koma/commands.py +0 -0
  109. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex_koma/document.py +0 -0
  110. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex_markdown/__init__.py +0 -0
  111. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex_markdown/convert.py +0 -0
  112. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex_markdown/escape.py +0 -0
  113. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex_preprocessor.egg-info/SOURCES.txt +0 -0
  114. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex_preprocessor.egg-info/dependency_links.txt +0 -0
  115. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex_preprocessor.egg-info/entry_points.txt +0 -0
  116. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex_preprocessor.egg-info/requires.txt +0 -0
  117. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex_preprocessor.egg-info/top_level.txt +0 -0
  118. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex_protocol/__init__.py +0 -0
  119. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex_protocol/convert.py +0 -0
  120. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex_protocol/document.py +0 -0
  121. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex_protocol/entries.py +0 -0
  122. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex_protocol/frontmatter.py +0 -0
  123. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex_protocol/header.py +0 -0
  124. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex_protocol/shortcodes.py +0 -0
  125. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex_protocol/signatures.py +0 -0
  126. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex_tikz/__init__.py +0 -0
  127. {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.2.0}/src/pytex_tikz/tikz.py +0 -0
@@ -0,0 +1,170 @@
1
+ Metadata-Version: 2.4
2
+ Name: pytex-preprocessor
3
+ Version: 0.2.0
4
+ Summary: Type-safe LaTeX document generation with Python
5
+ Author-email: Frederik Beimgraben <frederik@beimgraben.net>
6
+ Requires-Python: >=3.13
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: pydantic
9
+ Requires-Dist: marko
10
+ Provides-Extra: dev
11
+ Requires-Dist: pytest; extra == "dev"
12
+ Requires-Dist: ruff; extra == "dev"
13
+ Requires-Dist: basedpyright; extra == "dev"
14
+
15
+ # PyTeX
16
+
17
+ Type-safe LaTeX document generation with Python. Build a document as a tree of
18
+ typed `TeX` nodes and render it to a `.tex` file, or drop inline Python
19
+ expressions into an existing `.tex` source and have them evaluated at render
20
+ time. Requires Python 3.13+.
21
+
22
+ A `TeX` node is an immutable dataclass with a `.rendered` property. The public
23
+ API mirrors LaTeX control sequences as PascalCase factories (`Section`,
24
+ `Bold`, `Frac`, `Title`, ...), so a document reads like the LaTeX it produces
25
+ while staying checkable by a type checker. Nodes track their package
26
+ requirements, so the preamble is assembled automatically from what the body
27
+ uses.
28
+
29
+ ## Install
30
+
31
+ To use the `pytex` command anywhere, install it as an isolated tool with
32
+ [pipx](https://pipx.pypa.io/):
33
+
34
+ ```sh
35
+ pipx install pytex-preprocessor
36
+ ```
37
+
38
+ It is also available via plain `pip install pytex-preprocessor`.
39
+
40
+ For development, work in a virtualenv with an editable install instead:
41
+
42
+ ```sh
43
+ python -m venv venv && . venv/bin/activate
44
+ pip install -e . # add [dev] for pytest, ruff, basedpyright
45
+ ```
46
+
47
+ External tools, each needed only for the matching feature:
48
+
49
+ - `tectonic` — compile to PDF (`--build`). If not on `PATH`, the build
50
+ downloads a self-contained binary into a temp folder and reuses it.
51
+ - `inkscape` — `SVG` image conversion.
52
+ - `makeindex` (from a TeX distribution, e.g. TeX Live) — resolve
53
+ glossaries/acronyms.
54
+
55
+ ## Quick start
56
+
57
+ A `.tex.py` file is plain Python exposing a module-level `__pytex__` that holds
58
+ a `TeX` node:
59
+
60
+ ```py
61
+ from pytex.commands.builtin import Bold, Emph, Section, Title, MakeTitle
62
+ from pytex.model.concat import Concat
63
+ from pytex.model.document import Document
64
+ from pytex.model.math import DisplayMath, Frac
65
+
66
+ __pytex__ = Document(
67
+ preamble=Title("PyTeX Example"),
68
+ body=Concat(
69
+ MakeTitle(),
70
+ Section("Text"),
71
+ "A paragraph with ", Bold("bold"), " and ", Emph("emphasised"), " words.",
72
+ Section("Math"),
73
+ DisplayMath(Concat("x = ", Frac("-b", "2a"))),
74
+ ),
75
+ )
76
+ ```
77
+
78
+ ```sh
79
+ pytex example.tex.py # render -> build/example.out.tex
80
+ pytex example.tex.py --build # render + compile -> build/example.out.pdf
81
+ ```
82
+
83
+ Bare strings are coerced to text nodes and LaTeX-escaped.
84
+
85
+ ## The `pytex` command
86
+
87
+ The input file is dispatched by extension:
88
+
89
+ | Extension | Handling |
90
+ | --- | --- |
91
+ | `.py` | imported as a module; its `__pytex__` node is rendered. Convention: name it `<doc>.tex.py`. |
92
+ | `.tex` | wrapped in `IncludeTeX`; inline `\iffalse{pytex(...)}\fi` markers are evaluated, then rendered. Convention: `<doc>.py.tex`. |
93
+ | `.md` / `.markdown` | converted to nodes via `IncludeMarkdown`. Frontmatter with `gremium:` or `typ: protokoll` routes to the protocol renderer instead. |
94
+
95
+ ### Inline replacements in `.tex`
96
+
97
+ Any registered factory is in scope inside a marker. The `\iffalse ... \fi` pair
98
+ is a LaTeX no-op, so the source still compiles as-is without PyTeX:
99
+
100
+ ```tex
101
+ Today is \iffalse{pytex(Today())}\fi.
102
+ A fraction: $\iffalse{pytex(Frac("1", "2"))}\fi$.
103
+ Plain Python works too: $3^2 = \iffalse{pytex(3 ** 2)}\fi$.
104
+ ```
105
+
106
+ ### Options
107
+
108
+ | Flag | Default | Meaning |
109
+ | --- | --- | --- |
110
+ | `-o`, `--output` | `<build-dir>/<input>.out.tex` | rendered LaTeX output path |
111
+ | `-b`, `--build` | off | compile the rendered `.tex` to PDF with tectonic |
112
+ | `--build-dir DIR` | `build` | directory for artifacts and tectonic output |
113
+ | `--no-shell-escape` | shell-escape on | disable shell-escape |
114
+
115
+ Shell-escape is on by default because inline images decode their base64
116
+ payloads at compile time. The build runs tectonic, then `makeindex` (for
117
+ `glossaries`/acronyms), then reruns tectonic when an index changed.
118
+
119
+ Output is minimal and color-tagged (`==>`, `note:`, `warning:`, `error:`),
120
+ following tectonic's style; on failure it points at the likely cause and the
121
+ log file. Set `NO_COLOR` to disable color.
122
+
123
+ ## Packages
124
+
125
+ `pytex` is the core; the rest are optional and build on it.
126
+
127
+ | Package | Provides |
128
+ | --- | --- |
129
+ | `pytex` | core node model, `Document`, math, tables, graphics, and factories for the common LaTeX packages (biblatex, cleveref, glossaries, hyperref, listings, ...). |
130
+ | `pytex_koma` | KOMA-Script classes and commands (`Addchap`, `Minisec`, `KOMAoptions`, ...). |
131
+ | `pytex_tikz` | TikZ pictures and primitives (`TikzPicture`, `Draw`, `Node`, `Circle`, ...). |
132
+ | `pytex_markdown` | Markdown -> native `TeX` conversion (see below). |
133
+ | `pytex_hsrtreport` | HSRT report document class, colored callout boxes, title pages, glossary/citation helpers. |
134
+ | `pytex_protocol` | STUPA/AStA meeting minutes from Markdown, built on `pytex_hsrtreport`. |
135
+
136
+ ## Markdown
137
+
138
+ `pytex_markdown` converts Markdown to native `TeX` nodes (via `marko`):
139
+
140
+ ```py
141
+ from pytex_markdown import Markdown, IncludeMarkdown
142
+
143
+ body = Markdown("# Title\n\nText with **bold**, `code`, [a link](https://x).")
144
+ body = IncludeMarkdown("notes.md", base_level=-1) # base_level=-1: # -> \chapter
145
+ ```
146
+
147
+ Headings, emphasis, inline/fenced code, lists, links, images, block quotes and
148
+ thematic breaks map to the standard pytex library; text is LaTeX-escaped.
149
+ GitHub-style callouts become HSRT colored boxes (so the module depends on
150
+ `pytex_hsrtreport`):
151
+
152
+ ```md
153
+ > [!NOTE] -> InfoBox > [!IMPORTANT] -> ImportantBox
154
+ > [!TIP] -> SuccessBox > [!WARNING] -> WarningBox
155
+ ```
156
+
157
+ Both factories are registered, so they work in `\iffalse{pytex(...)}\fi`
158
+ replacements in `.tex` sources too.
159
+
160
+ ## Examples
161
+
162
+ See `examples/` for one minimal input per kind (`.tex.py`, `.py.tex`, `.md`,
163
+ mixed, and a full HSRT report). Run from the repository root so relative paths
164
+ resolve:
165
+
166
+ ```sh
167
+ pytex examples/document.tex.py --build
168
+ pytex examples/replacements.py.tex --build
169
+ pytex examples/notes.md --build
170
+ ```
@@ -0,0 +1,156 @@
1
+ # PyTeX
2
+
3
+ Type-safe LaTeX document generation with Python. Build a document as a tree of
4
+ typed `TeX` nodes and render it to a `.tex` file, or drop inline Python
5
+ expressions into an existing `.tex` source and have them evaluated at render
6
+ time. Requires Python 3.13+.
7
+
8
+ A `TeX` node is an immutable dataclass with a `.rendered` property. The public
9
+ API mirrors LaTeX control sequences as PascalCase factories (`Section`,
10
+ `Bold`, `Frac`, `Title`, ...), so a document reads like the LaTeX it produces
11
+ while staying checkable by a type checker. Nodes track their package
12
+ requirements, so the preamble is assembled automatically from what the body
13
+ uses.
14
+
15
+ ## Install
16
+
17
+ To use the `pytex` command anywhere, install it as an isolated tool with
18
+ [pipx](https://pipx.pypa.io/):
19
+
20
+ ```sh
21
+ pipx install pytex-preprocessor
22
+ ```
23
+
24
+ It is also available via plain `pip install pytex-preprocessor`.
25
+
26
+ For development, work in a virtualenv with an editable install instead:
27
+
28
+ ```sh
29
+ python -m venv venv && . venv/bin/activate
30
+ pip install -e . # add [dev] for pytest, ruff, basedpyright
31
+ ```
32
+
33
+ External tools, each needed only for the matching feature:
34
+
35
+ - `tectonic` — compile to PDF (`--build`). If not on `PATH`, the build
36
+ downloads a self-contained binary into a temp folder and reuses it.
37
+ - `inkscape` — `SVG` image conversion.
38
+ - `makeindex` (from a TeX distribution, e.g. TeX Live) — resolve
39
+ glossaries/acronyms.
40
+
41
+ ## Quick start
42
+
43
+ A `.tex.py` file is plain Python exposing a module-level `__pytex__` that holds
44
+ a `TeX` node:
45
+
46
+ ```py
47
+ from pytex.commands.builtin import Bold, Emph, Section, Title, MakeTitle
48
+ from pytex.model.concat import Concat
49
+ from pytex.model.document import Document
50
+ from pytex.model.math import DisplayMath, Frac
51
+
52
+ __pytex__ = Document(
53
+ preamble=Title("PyTeX Example"),
54
+ body=Concat(
55
+ MakeTitle(),
56
+ Section("Text"),
57
+ "A paragraph with ", Bold("bold"), " and ", Emph("emphasised"), " words.",
58
+ Section("Math"),
59
+ DisplayMath(Concat("x = ", Frac("-b", "2a"))),
60
+ ),
61
+ )
62
+ ```
63
+
64
+ ```sh
65
+ pytex example.tex.py # render -> build/example.out.tex
66
+ pytex example.tex.py --build # render + compile -> build/example.out.pdf
67
+ ```
68
+
69
+ Bare strings are coerced to text nodes and LaTeX-escaped.
70
+
71
+ ## The `pytex` command
72
+
73
+ The input file is dispatched by extension:
74
+
75
+ | Extension | Handling |
76
+ | --- | --- |
77
+ | `.py` | imported as a module; its `__pytex__` node is rendered. Convention: name it `<doc>.tex.py`. |
78
+ | `.tex` | wrapped in `IncludeTeX`; inline `\iffalse{pytex(...)}\fi` markers are evaluated, then rendered. Convention: `<doc>.py.tex`. |
79
+ | `.md` / `.markdown` | converted to nodes via `IncludeMarkdown`. Frontmatter with `gremium:` or `typ: protokoll` routes to the protocol renderer instead. |
80
+
81
+ ### Inline replacements in `.tex`
82
+
83
+ Any registered factory is in scope inside a marker. The `\iffalse ... \fi` pair
84
+ is a LaTeX no-op, so the source still compiles as-is without PyTeX:
85
+
86
+ ```tex
87
+ Today is \iffalse{pytex(Today())}\fi.
88
+ A fraction: $\iffalse{pytex(Frac("1", "2"))}\fi$.
89
+ Plain Python works too: $3^2 = \iffalse{pytex(3 ** 2)}\fi$.
90
+ ```
91
+
92
+ ### Options
93
+
94
+ | Flag | Default | Meaning |
95
+ | --- | --- | --- |
96
+ | `-o`, `--output` | `<build-dir>/<input>.out.tex` | rendered LaTeX output path |
97
+ | `-b`, `--build` | off | compile the rendered `.tex` to PDF with tectonic |
98
+ | `--build-dir DIR` | `build` | directory for artifacts and tectonic output |
99
+ | `--no-shell-escape` | shell-escape on | disable shell-escape |
100
+
101
+ Shell-escape is on by default because inline images decode their base64
102
+ payloads at compile time. The build runs tectonic, then `makeindex` (for
103
+ `glossaries`/acronyms), then reruns tectonic when an index changed.
104
+
105
+ Output is minimal and color-tagged (`==>`, `note:`, `warning:`, `error:`),
106
+ following tectonic's style; on failure it points at the likely cause and the
107
+ log file. Set `NO_COLOR` to disable color.
108
+
109
+ ## Packages
110
+
111
+ `pytex` is the core; the rest are optional and build on it.
112
+
113
+ | Package | Provides |
114
+ | --- | --- |
115
+ | `pytex` | core node model, `Document`, math, tables, graphics, and factories for the common LaTeX packages (biblatex, cleveref, glossaries, hyperref, listings, ...). |
116
+ | `pytex_koma` | KOMA-Script classes and commands (`Addchap`, `Minisec`, `KOMAoptions`, ...). |
117
+ | `pytex_tikz` | TikZ pictures and primitives (`TikzPicture`, `Draw`, `Node`, `Circle`, ...). |
118
+ | `pytex_markdown` | Markdown -> native `TeX` conversion (see below). |
119
+ | `pytex_hsrtreport` | HSRT report document class, colored callout boxes, title pages, glossary/citation helpers. |
120
+ | `pytex_protocol` | STUPA/AStA meeting minutes from Markdown, built on `pytex_hsrtreport`. |
121
+
122
+ ## Markdown
123
+
124
+ `pytex_markdown` converts Markdown to native `TeX` nodes (via `marko`):
125
+
126
+ ```py
127
+ from pytex_markdown import Markdown, IncludeMarkdown
128
+
129
+ body = Markdown("# Title\n\nText with **bold**, `code`, [a link](https://x).")
130
+ body = IncludeMarkdown("notes.md", base_level=-1) # base_level=-1: # -> \chapter
131
+ ```
132
+
133
+ Headings, emphasis, inline/fenced code, lists, links, images, block quotes and
134
+ thematic breaks map to the standard pytex library; text is LaTeX-escaped.
135
+ GitHub-style callouts become HSRT colored boxes (so the module depends on
136
+ `pytex_hsrtreport`):
137
+
138
+ ```md
139
+ > [!NOTE] -> InfoBox > [!IMPORTANT] -> ImportantBox
140
+ > [!TIP] -> SuccessBox > [!WARNING] -> WarningBox
141
+ ```
142
+
143
+ Both factories are registered, so they work in `\iffalse{pytex(...)}\fi`
144
+ replacements in `.tex` sources too.
145
+
146
+ ## Examples
147
+
148
+ See `examples/` for one minimal input per kind (`.tex.py`, `.py.tex`, `.md`,
149
+ mixed, and a full HSRT report). Run from the repository root so relative paths
150
+ resolve:
151
+
152
+ ```sh
153
+ pytex examples/document.tex.py --build
154
+ pytex examples/replacements.py.tex --build
155
+ pytex examples/notes.md --build
156
+ ```
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pytex-preprocessor"
7
- version = "0.1.3"
7
+ version = "0.2.0"
8
8
  authors = [
9
9
  { name="Frederik Beimgraben", email="frederik@beimgraben.net" },
10
10
  ]
@@ -33,12 +33,12 @@ __all__ = [
33
33
  "Group",
34
34
  "Hfill",
35
35
  "Hspace",
36
+ "HspaceStar",
36
37
  "Immediate",
37
38
  "Include",
38
39
  "IncludeOnly",
39
40
  "Indent",
40
41
  "Input",
41
- "Inputfile",
42
42
  "Italic",
43
43
  "Item",
44
44
  "Itemize",
@@ -93,6 +93,7 @@ __all__ = [
93
93
  "Verse",
94
94
  "Vfill",
95
95
  "Vspace",
96
+ "VspaceStar",
96
97
  "Whiledo",
97
98
  "Write18",
98
99
  ]
@@ -264,15 +265,23 @@ def Pagebreak(n: int | None = None) -> TeX:
264
265
 
265
266
 
266
267
  @Registry.add
267
- def Hspace(amount: str, star: bool = False) -> TeX:
268
- name = "hspace*" if star else "hspace"
269
- return ControlSequence(name, (Parameter(amount),))
268
+ def Hspace(amount: str) -> TeX:
269
+ return ControlSequence("hspace", (Parameter(amount),))
270
270
 
271
271
 
272
272
  @Registry.add
273
- def Vspace(amount: str, star: bool = False) -> TeX:
274
- name = "vspace*" if star else "vspace"
275
- return ControlSequence(name, (Parameter(amount),))
273
+ def HspaceStar(amount: str) -> TeX:
274
+ return ControlSequence("hspace*", (Parameter(amount),))
275
+
276
+
277
+ @Registry.add
278
+ def Vspace(amount: str) -> TeX:
279
+ return ControlSequence("vspace", (Parameter(amount),))
280
+
281
+
282
+ @Registry.add
283
+ def VspaceStar(amount: str) -> TeX:
284
+ return ControlSequence("vspace*", (Parameter(amount),))
276
285
 
277
286
 
278
287
  @Registry.add
@@ -552,12 +561,6 @@ def Verbatiminput(path: str) -> TeX:
552
561
  return ControlSequence("verbatiminput", (Parameter(path),))
553
562
 
554
563
 
555
- @Registry.add
556
- def Inputfile(path: str) -> TeX:
557
- """Alias for `\\input{path}` — `Input` already defined above as Input(path)."""
558
- return ControlSequence("input", (Parameter(path),))
559
-
560
-
561
564
  @Registry.add
562
565
  def Whiledo(condition: TeX | str, body: TeX | str) -> TeX:
563
566
  return ControlSequence("whiledo", (Parameter(condition), Parameter(body)))
@@ -5,11 +5,15 @@ from ..registry import Registry
5
5
 
6
6
  __all__ = [
7
7
  "DeclareRobustCommand",
8
+ "DeclareRobustCommandStar",
8
9
  "Def",
9
10
  "Newcommand",
11
+ "NewcommandStar",
10
12
  "Newenvironment",
11
13
  "Providecommand",
14
+ "ProvidecommandStar",
12
15
  "Renewcommand",
16
+ "RenewcommandStar",
13
17
  "Renewenvironment",
14
18
  ]
15
19
 
@@ -38,9 +42,18 @@ def Newcommand(
38
42
  body: TeX | str,
39
43
  nargs: int | None = None,
40
44
  default: str | None = None,
41
- star: bool = False,
42
45
  ) -> TeX:
43
- return _cmd("newcommand", cs, nargs, default, body, star)
46
+ return _cmd("newcommand", cs, nargs, default, body, star=False)
47
+
48
+
49
+ @Registry.add
50
+ def NewcommandStar(
51
+ cs: str,
52
+ body: TeX | str,
53
+ nargs: int | None = None,
54
+ default: str | None = None,
55
+ ) -> TeX:
56
+ return _cmd("newcommand", cs, nargs, default, body, star=True)
44
57
 
45
58
 
46
59
  @Registry.add
@@ -49,9 +62,18 @@ def Renewcommand(
49
62
  body: TeX | str,
50
63
  nargs: int | None = None,
51
64
  default: str | None = None,
52
- star: bool = False,
53
65
  ) -> TeX:
54
- return _cmd("renewcommand", cs, nargs, default, body, star)
66
+ return _cmd("renewcommand", cs, nargs, default, body, star=False)
67
+
68
+
69
+ @Registry.add
70
+ def RenewcommandStar(
71
+ cs: str,
72
+ body: TeX | str,
73
+ nargs: int | None = None,
74
+ default: str | None = None,
75
+ ) -> TeX:
76
+ return _cmd("renewcommand", cs, nargs, default, body, star=True)
55
77
 
56
78
 
57
79
  @Registry.add
@@ -60,9 +82,18 @@ def Providecommand(
60
82
  body: TeX | str,
61
83
  nargs: int | None = None,
62
84
  default: str | None = None,
63
- star: bool = False,
64
85
  ) -> TeX:
65
- return _cmd("providecommand", cs, nargs, default, body, star)
86
+ return _cmd("providecommand", cs, nargs, default, body, star=False)
87
+
88
+
89
+ @Registry.add
90
+ def ProvidecommandStar(
91
+ cs: str,
92
+ body: TeX | str,
93
+ nargs: int | None = None,
94
+ default: str | None = None,
95
+ ) -> TeX:
96
+ return _cmd("providecommand", cs, nargs, default, body, star=True)
66
97
 
67
98
 
68
99
  @Registry.add
@@ -70,9 +101,17 @@ def DeclareRobustCommand(
70
101
  cs: str,
71
102
  body: TeX | str,
72
103
  nargs: int | None = None,
73
- star: bool = False,
74
104
  ) -> TeX:
75
- return _cmd("DeclareRobustCommand", cs, nargs, None, body, star)
105
+ return _cmd("DeclareRobustCommand", cs, nargs, None, body, star=False)
106
+
107
+
108
+ @Registry.add
109
+ def DeclareRobustCommandStar(
110
+ cs: str,
111
+ body: TeX | str,
112
+ nargs: int | None = None,
113
+ ) -> TeX:
114
+ return _cmd("DeclareRobustCommand", cs, nargs, None, body, star=True)
76
115
 
77
116
 
78
117
  @Registry.add
@@ -10,10 +10,10 @@ __all__ = [
10
10
  "Acrlong",
11
11
  "Acrshort",
12
12
  "Gls",
13
+ "GlsUpper",
13
14
  "Glsenablehyper",
14
15
  "Glspl",
15
- "Glsplupper",
16
- "Glsupper",
16
+ "GlsplUpper",
17
17
  "Makeglossaries",
18
18
  "Newacronym",
19
19
  "Newglossaryentry",
@@ -80,7 +80,7 @@ def Gls(label: str) -> TeX:
80
80
 
81
81
  @Registry.add
82
82
  @with_package(GLOSSARIES)
83
- def Glsupper(label: str) -> TeX:
83
+ def GlsUpper(label: str) -> TeX:
84
84
  return ControlSequence("Gls", (Parameter(label),))
85
85
 
86
86
 
@@ -92,7 +92,7 @@ def Glspl(label: str) -> TeX:
92
92
 
93
93
  @Registry.add
94
94
  @with_package(GLOSSARIES)
95
- def Glsplupper(label: str) -> TeX:
95
+ def GlsplUpper(label: str) -> TeX:
96
96
  return ControlSequence("Glspl", (Parameter(label),))
97
97
 
98
98
 
@@ -1,7 +1,7 @@
1
1
  from dataclasses import dataclass, field
2
2
  from typing import Final, override
3
3
 
4
- from pytex.commands.builtin import Hspace, Noindent, Vspace
4
+ from pytex.commands.builtin import HspaceStar, Noindent, VspaceStar
5
5
  from pytex.commands.colors import SelectColor
6
6
  from pytex.commands.floats import Minipage
7
7
  from pytex.commands.font import Fontsize, Selectfont
@@ -105,7 +105,7 @@ class ColoredBox(TeX):
105
105
  bg = round((BASE_OPACITY + PER_LEVEL * level) * 100)
106
106
  icon_op = bg + ICON_BOOST
107
107
  return Concat(
108
- Vspace(r"0.5\baselineskip", star=True),
108
+ VspaceStar(r"0.5\baselineskip"),
109
109
  Raw("~\\\\"),
110
110
  Noindent(),
111
111
  Minipage(
@@ -135,7 +135,7 @@ class ColoredBox(TeX):
135
135
  ),
136
136
  ),
137
137
  ),
138
- Hspace("0.25cm+2pt", star=True),
138
+ HspaceStar("0.25cm+2pt"),
139
139
  Minipage(
140
140
  r"\linewidth-0.5cm-2pt",
141
141
  self.body,
@@ -4,7 +4,7 @@ from typing import Final, override
4
4
 
5
5
  from pytex.commands.builtin import (
6
6
  Blenderfont,
7
- Hspace,
7
+ HspaceStar,
8
8
  Newline,
9
9
  Noindent,
10
10
  Rule,
@@ -12,6 +12,7 @@ from pytex.commands.builtin import (
12
12
  Textbf,
13
13
  Vfill,
14
14
  Vspace,
15
+ VspaceStar,
15
16
  )
16
17
  from pytex.commands.colors import SelectColor
17
18
  from pytex.commands.floats import Titlepage as TitlepageEnv
@@ -97,7 +98,7 @@ class TitlePage(TeX):
97
98
  Concat(
98
99
  Blenderfont(),
99
100
  HugeBig(),
100
- Hspace("-2.5pt", star=True),
101
+ HspaceStar("-2.5pt"),
101
102
  self.title,
102
103
  )
103
104
  ),
@@ -121,7 +122,7 @@ class TitlePage(TeX):
121
122
  yield SectionStar("Abstract")
122
123
  yield Vspace("-1em")
123
124
  yield self.abstract
124
- yield Vspace("1em", star=True)
125
+ yield VspaceStar("1em")
125
126
  if not _is_blank(self.keywords):
126
127
  yield Raw(r"\par\noindent ")
127
128
  yield Textbf("Keywords")
@@ -1,7 +1,7 @@
1
1
  from dataclasses import dataclass, field
2
2
  from typing import Final, override
3
3
 
4
- from pytex.commands.builtin import Textbf, Vspace
4
+ from pytex.commands.builtin import Textbf, VspaceStar
5
5
  from pytex.commands.floats import Columnbreak, Multicols
6
6
  from pytex.commands.fontawesome import FaIcon
7
7
  from pytex.helpers.parenting import attach
@@ -67,7 +67,7 @@ class VotingResults(TeX):
67
67
  return ColoredBox(
68
68
  body=Concat(
69
69
  self.body,
70
- Vspace("-2em", star=True),
70
+ VspaceStar("-2em"),
71
71
  Multicols(
72
72
  3,
73
73
  Concat(