pytex-preprocessor 0.1.3__tar.gz → 0.3.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.
- pytex_preprocessor-0.3.0/PKG-INFO +254 -0
- pytex_preprocessor-0.3.0/README.md +240 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/pyproject.toml +2 -1
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex/__init__.py +2 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex/commands/builtin.py +16 -13
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex/commands/definitions.py +47 -8
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex/commands/glossaries.py +4 -4
- pytex_preprocessor-0.3.0/src/pytex/model/comment.py +26 -0
- pytex_preprocessor-0.3.0/src/pytex/model/concat.py +55 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex/model/control_sequence.py +5 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex/model/document.py +5 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex/model/math.py +7 -0
- pytex_preprocessor-0.3.0/src/pytex_analyze/__init__.py +16 -0
- pytex_preprocessor-0.3.0/src/pytex_analyze/analyze.py +106 -0
- pytex_preprocessor-0.3.0/src/pytex_analyze/optimize.py +181 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex_builder/build.py +111 -2
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex_builder/console.py +3 -3
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex_builder/render.py +24 -16
- pytex_preprocessor-0.3.0/src/pytex_builder/tex2py.py +159 -0
- pytex_preprocessor-0.3.0/src/pytex_builder/tree.py +197 -0
- pytex_preprocessor-0.3.0/src/pytex_builder/variants.py +178 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex_hsrtreport/boxes.py +3 -3
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex_hsrtreport/document.py +5 -2
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex_hsrtreport/titlepage.py +4 -3
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex_hsrtreport/voting.py +2 -2
- pytex_preprocessor-0.3.0/src/pytex_preprocessor.egg-info/PKG-INFO +254 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex_preprocessor.egg-info/SOURCES.txt +7 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex_preprocessor.egg-info/entry_points.txt +1 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex_preprocessor.egg-info/top_level.txt +1 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex_protocol/__init__.py +2 -1
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex_protocol/document.py +24 -6
- pytex_preprocessor-0.1.3/PKG-INFO +0 -82
- pytex_preprocessor-0.1.3/README.md +0 -68
- pytex_preprocessor-0.1.3/src/pytex/model/concat.py +0 -31
- pytex_preprocessor-0.1.3/src/pytex_preprocessor.egg-info/PKG-INFO +0 -82
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/setup.cfg +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex/commands/__init__.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex/commands/biblatex.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex/commands/captions.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex/commands/cleveref.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex/commands/colors.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex/commands/conditionals.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex/commands/counters.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex/commands/floats.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex/commands/font.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex/commands/fontawesome.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex/commands/fontspec.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex/commands/geometry.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex/commands/graphics.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex/commands/hooks.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex/commands/hyperref.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex/commands/lengths.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex/commands/listings.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex/commands/mdframed.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex/commands/picture.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex/commands/setspace.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex/commands/tables.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex/helpers/__init__.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex/helpers/coerce.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex/helpers/parenting.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex/helpers/sanitize.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex/helpers/with_package.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex/interface/__init__.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex/interface/control_sequence.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex/interface/package.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex/interface/tex.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex/model/__init__.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex/model/color.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex/model/document_class.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex/model/empty.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex/model/environment.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex/model/image.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex/model/include.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex/model/length.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex/model/package.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex/model/raw.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex/packages.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex/registry.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex_builder/__init__.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex_builder/tectonic.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex_hsrtreport/__init__.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex_hsrtreport/assets/fonts/Blender/Blender-Bold.ttf +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex_hsrtreport/assets/fonts/Blender/Blender-BoldItalic.ttf +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex_hsrtreport/assets/fonts/Blender/Blender-Book.ttf +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex_hsrtreport/assets/fonts/Blender/Blender-BookItalic.ttf +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex_hsrtreport/assets/fonts/Blender/Blender-Medium.ttf +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex_hsrtreport/assets/fonts/Blender/Blender-MediumItalic.ttf +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex_hsrtreport/assets/fonts/Blender/Blender-Strong.ttf +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex_hsrtreport/assets/fonts/Blender/Blender-Thin.ttf +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex_hsrtreport/assets/fonts/Blender/Blender-ThinItalic.ttf +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex_hsrtreport/assets/fonts/DIN/DIN-Black.ttf +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex_hsrtreport/assets/fonts/DIN/DIN-Bold.ttf +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex_hsrtreport/assets/fonts/DIN/DIN-BoldItalic.ttf +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex_hsrtreport/assets/fonts/DIN/DIN-Italic.ttf +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex_hsrtreport/assets/fonts/DIN/DIN-Medium.ttf +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex_hsrtreport/assets/fonts/DIN/DIN-Regular.ttf +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex_hsrtreport/assets/fonts/Times New Roman.ttf +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex_hsrtreport/assets/logos/ASTA.svg +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex_hsrtreport/assets/logos/DUMMY.png +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex_hsrtreport/assets/logos/DUMMY_FOOT.png +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex_hsrtreport/assets/logos/ECHO.svg +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex_hsrtreport/assets/logos/HSRT.pdf +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex_hsrtreport/assets/logos/INF.pdf +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex_hsrtreport/assets/logos/STUPA.pdf +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex_hsrtreport/assets/logos/Skyline.pdf +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex_hsrtreport/citations.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex_hsrtreport/cleveref_names.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex_hsrtreport/colors.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex_hsrtreport/fonts.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex_hsrtreport/glossary.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex_hsrtreport/hyperref_config.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex_hsrtreport/listings.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex_hsrtreport/logos.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex_hsrtreport/pagebreak.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex_hsrtreport/pagesetup.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex_hsrtreport/tex/pagesetup.tex +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex_hsrtreport/variants.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex_hsrtreport/watermark.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex_hsrtreport/wordcount.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex_koma/__init__.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex_koma/commands.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex_koma/document.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex_markdown/__init__.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex_markdown/convert.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex_markdown/escape.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex_preprocessor.egg-info/dependency_links.txt +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex_preprocessor.egg-info/requires.txt +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex_protocol/convert.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex_protocol/entries.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex_protocol/frontmatter.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex_protocol/header.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex_protocol/shortcodes.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex_protocol/signatures.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex_tikz/__init__.py +0 -0
- {pytex_preprocessor-0.1.3 → pytex_preprocessor-0.3.0}/src/pytex_tikz/tikz.py +0 -0
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pytex-preprocessor
|
|
3
|
+
Version: 0.3.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 and wrapped in a document according to `--variant` (see below). Without `--variant` the style is auto-detected. |
|
|
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
|
+
| `-t`, `--tree` | off | also print the input's `TeX`-node tree (`tree`-style) before rendering/building |
|
|
115
|
+
| `-f`, `--force` | off | skip the optimize + analysis pass and build even if problems are found |
|
|
116
|
+
| `--variant STYLE` | auto-detect | Markdown output style (`plain`, `report`, `protocol-asta`, `protocol-stupa`) |
|
|
117
|
+
| `--config JSON` | none | JSON object of document-class params, merged over the frontmatter |
|
|
118
|
+
|
|
119
|
+
Shell-escape is on by default because inline images decode their base64
|
|
120
|
+
payloads at compile time. The build runs tectonic, then `makeindex` (for
|
|
121
|
+
`glossaries`/acronyms), then reruns tectonic when an index changed.
|
|
122
|
+
|
|
123
|
+
Output is minimal and color-tagged (`==>`, `note:`, `warning:`, `error:`),
|
|
124
|
+
following tectonic's style; on failure it points at the likely cause and the
|
|
125
|
+
log file. Set `NO_COLOR` to disable color.
|
|
126
|
+
|
|
127
|
+
### Pre-flight optimize + analysis
|
|
128
|
+
|
|
129
|
+
Before rendering, the builder runs two render-equivalent passes over the node
|
|
130
|
+
tree. First `Optimize` tidies the tree (flatten nested `Concat`s, drop empty
|
|
131
|
+
nodes, turn whole-`Raw` LaTeX constructs into native nodes) without changing
|
|
132
|
+
the output (it also expands inline `pytex(...)` markers and turns `Raw`
|
|
133
|
+
comments and math — `\[...\]`, `\(...\)`, `$...$` — into native nodes). Then
|
|
134
|
+
`pytex_analyze` checks for problems that LaTeX would only surface later (or
|
|
135
|
+
silently):
|
|
136
|
+
|
|
137
|
+
- references (`\ref`, `\cref`, `\autoref`, ...) to a label that is never
|
|
138
|
+
defined,
|
|
139
|
+
- labels defined more than once,
|
|
140
|
+
- `\includegraphics` paths that do not exist on disk.
|
|
141
|
+
|
|
142
|
+
Missing-image issues are errors and abort the build; the rest are warnings.
|
|
143
|
+
Pass `-f`/`--force` to skip both passes and build regardless.
|
|
144
|
+
|
|
145
|
+
### Inspecting the node tree
|
|
146
|
+
|
|
147
|
+
`--tree` prints the parsed `TeX`-node tree (then renders/builds as usual),
|
|
148
|
+
useful for debugging how an input maps to nodes. Nodes that require a package
|
|
149
|
+
are tagged with it (`[+package]`):
|
|
150
|
+
|
|
151
|
+
```
|
|
152
|
+
$ pytex example.tex.py --tree
|
|
153
|
+
Document (article)
|
|
154
|
+
├── ControlSequence \title
|
|
155
|
+
│ └── Parameter { }
|
|
156
|
+
│ └── Raw "PyTeX Example"
|
|
157
|
+
└── Concat
|
|
158
|
+
├── ControlSequence \maketitle
|
|
159
|
+
├── ControlSequence \cref [+cleveref]
|
|
160
|
+
└── ...
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Packages
|
|
164
|
+
|
|
165
|
+
`pytex` is the core; the rest are optional and build on it.
|
|
166
|
+
|
|
167
|
+
| Package | Provides |
|
|
168
|
+
| --- | --- |
|
|
169
|
+
| `pytex` | core node model, `Document`, math, tables, graphics, and factories for the common LaTeX packages (biblatex, cleveref, glossaries, hyperref, listings, ...). |
|
|
170
|
+
| `pytex_koma` | KOMA-Script classes and commands (`Addchap`, `Minisec`, `KOMAoptions`, ...). |
|
|
171
|
+
| `pytex_tikz` | TikZ pictures and primitives (`TikzPicture`, `Draw`, `Node`, `Circle`, ...). |
|
|
172
|
+
| `pytex_markdown` | Markdown -> native `TeX` conversion (see below). |
|
|
173
|
+
| `pytex_analyze` | static checks over the node tree (dangling refs, duplicate labels, missing images), plus `Optimize` to simplify a tree render-equivalently. |
|
|
174
|
+
| `pytex_hsrtreport` | HSRT report document class, colored callout boxes, title pages, glossary/citation helpers. |
|
|
175
|
+
| `pytex_protocol` | STUPA/AStA meeting minutes from Markdown, built on `pytex_hsrtreport`. |
|
|
176
|
+
|
|
177
|
+
## Markdown
|
|
178
|
+
|
|
179
|
+
`pytex_markdown` converts Markdown to native `TeX` nodes (via `marko`):
|
|
180
|
+
|
|
181
|
+
```py
|
|
182
|
+
from pytex_markdown import Markdown, IncludeMarkdown
|
|
183
|
+
|
|
184
|
+
body = Markdown("# Title\n\nText with **bold**, `code`, [a link](https://x).")
|
|
185
|
+
body = IncludeMarkdown("notes.md", base_level=-1) # base_level=-1: # -> \chapter
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Headings, emphasis, inline/fenced code, lists, links, images, block quotes and
|
|
189
|
+
thematic breaks map to the standard pytex library; text is LaTeX-escaped.
|
|
190
|
+
GitHub-style callouts become HSRT colored boxes (so the module depends on
|
|
191
|
+
`pytex_hsrtreport`):
|
|
192
|
+
|
|
193
|
+
```md
|
|
194
|
+
> [!NOTE] -> InfoBox > [!IMPORTANT] -> ImportantBox
|
|
195
|
+
> [!TIP] -> SuccessBox > [!WARNING] -> WarningBox
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Both factories are registered, so they work in `\iffalse{pytex(...)}\fi`
|
|
199
|
+
replacements in `.tex` sources too.
|
|
200
|
+
|
|
201
|
+
### Output variants
|
|
202
|
+
|
|
203
|
+
When the `pytex` command renders a `.md` file it wraps the converted nodes in a
|
|
204
|
+
document chosen by `--variant`:
|
|
205
|
+
|
|
206
|
+
| Variant | Document |
|
|
207
|
+
| --- | --- |
|
|
208
|
+
| `plain` | a bare `Document` (default class `article`); `#` -> `\section`. |
|
|
209
|
+
| `report` | an HSRT report with title page and table of contents; `#` -> `\chapter`. |
|
|
210
|
+
| `protocol-asta` | an AStA meeting protocol (HSRT report, AStA logos). |
|
|
211
|
+
| `protocol-stupa` | a StuPa meeting protocol (HSRT report, StuPa logos). |
|
|
212
|
+
|
|
213
|
+
Without `--variant`, protocol frontmatter (`gremium:` or `typ: protokoll`) picks
|
|
214
|
+
a protocol style and everything else falls back to `plain`.
|
|
215
|
+
|
|
216
|
+
Document-class parameters come from the YAML frontmatter and from `--config`
|
|
217
|
+
(a JSON object that overrides the frontmatter), e.g.:
|
|
218
|
+
|
|
219
|
+
```sh
|
|
220
|
+
pytex notes.md --variant plain --config '{"documentclass": "scrartcl", "classoptions": ["11pt", "twocolumn"]}'
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
`classoptions` accepts a list (`"twocolumn"`, `"DIV=12"`) or a `{key: value}`
|
|
224
|
+
object. For styles with a title page (`report`), the title is taken from
|
|
225
|
+
`title:`/`--config` if given, otherwise from the first `#` heading (which is then
|
|
226
|
+
not also rendered as a chapter).
|
|
227
|
+
|
|
228
|
+
## Converting LaTeX to PyTeX
|
|
229
|
+
|
|
230
|
+
`pytex-tex2py` turns an existing `.tex` file into an equivalent `.tex.py`
|
|
231
|
+
source. It reads the file, runs `Optimize` over it (expanding inline
|
|
232
|
+
`pytex(...)` markers and recognising comments and math), and serialises the
|
|
233
|
+
result to Python that rebuilds the same tree:
|
|
234
|
+
|
|
235
|
+
```sh
|
|
236
|
+
pytex-tex2py paper.tex # -> paper.tex.py
|
|
237
|
+
pytex-tex2py paper.tex -o out.py
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
Rendering the generated `.tex.py` reproduces the original output byte-for-byte;
|
|
241
|
+
nodes the serialiser does not special-case fall back to a literal `Raw`, so the
|
|
242
|
+
conversion always round-trips.
|
|
243
|
+
|
|
244
|
+
## Examples
|
|
245
|
+
|
|
246
|
+
See `examples/` for one minimal input per kind (`.tex.py`, `.py.tex`, `.md`,
|
|
247
|
+
mixed, and a full HSRT report). Run from the repository root so relative paths
|
|
248
|
+
resolve:
|
|
249
|
+
|
|
250
|
+
```sh
|
|
251
|
+
pytex examples/document.tex.py --build
|
|
252
|
+
pytex examples/replacements.py.tex --build
|
|
253
|
+
pytex examples/notes.md --build
|
|
254
|
+
```
|
|
@@ -0,0 +1,240 @@
|
|
|
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 and wrapped in a document according to `--variant` (see below). Without `--variant` the style is auto-detected. |
|
|
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
|
+
| `-t`, `--tree` | off | also print the input's `TeX`-node tree (`tree`-style) before rendering/building |
|
|
101
|
+
| `-f`, `--force` | off | skip the optimize + analysis pass and build even if problems are found |
|
|
102
|
+
| `--variant STYLE` | auto-detect | Markdown output style (`plain`, `report`, `protocol-asta`, `protocol-stupa`) |
|
|
103
|
+
| `--config JSON` | none | JSON object of document-class params, merged over the frontmatter |
|
|
104
|
+
|
|
105
|
+
Shell-escape is on by default because inline images decode their base64
|
|
106
|
+
payloads at compile time. The build runs tectonic, then `makeindex` (for
|
|
107
|
+
`glossaries`/acronyms), then reruns tectonic when an index changed.
|
|
108
|
+
|
|
109
|
+
Output is minimal and color-tagged (`==>`, `note:`, `warning:`, `error:`),
|
|
110
|
+
following tectonic's style; on failure it points at the likely cause and the
|
|
111
|
+
log file. Set `NO_COLOR` to disable color.
|
|
112
|
+
|
|
113
|
+
### Pre-flight optimize + analysis
|
|
114
|
+
|
|
115
|
+
Before rendering, the builder runs two render-equivalent passes over the node
|
|
116
|
+
tree. First `Optimize` tidies the tree (flatten nested `Concat`s, drop empty
|
|
117
|
+
nodes, turn whole-`Raw` LaTeX constructs into native nodes) without changing
|
|
118
|
+
the output (it also expands inline `pytex(...)` markers and turns `Raw`
|
|
119
|
+
comments and math — `\[...\]`, `\(...\)`, `$...$` — into native nodes). Then
|
|
120
|
+
`pytex_analyze` checks for problems that LaTeX would only surface later (or
|
|
121
|
+
silently):
|
|
122
|
+
|
|
123
|
+
- references (`\ref`, `\cref`, `\autoref`, ...) to a label that is never
|
|
124
|
+
defined,
|
|
125
|
+
- labels defined more than once,
|
|
126
|
+
- `\includegraphics` paths that do not exist on disk.
|
|
127
|
+
|
|
128
|
+
Missing-image issues are errors and abort the build; the rest are warnings.
|
|
129
|
+
Pass `-f`/`--force` to skip both passes and build regardless.
|
|
130
|
+
|
|
131
|
+
### Inspecting the node tree
|
|
132
|
+
|
|
133
|
+
`--tree` prints the parsed `TeX`-node tree (then renders/builds as usual),
|
|
134
|
+
useful for debugging how an input maps to nodes. Nodes that require a package
|
|
135
|
+
are tagged with it (`[+package]`):
|
|
136
|
+
|
|
137
|
+
```
|
|
138
|
+
$ pytex example.tex.py --tree
|
|
139
|
+
Document (article)
|
|
140
|
+
├── ControlSequence \title
|
|
141
|
+
│ └── Parameter { }
|
|
142
|
+
│ └── Raw "PyTeX Example"
|
|
143
|
+
└── Concat
|
|
144
|
+
├── ControlSequence \maketitle
|
|
145
|
+
├── ControlSequence \cref [+cleveref]
|
|
146
|
+
└── ...
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Packages
|
|
150
|
+
|
|
151
|
+
`pytex` is the core; the rest are optional and build on it.
|
|
152
|
+
|
|
153
|
+
| Package | Provides |
|
|
154
|
+
| --- | --- |
|
|
155
|
+
| `pytex` | core node model, `Document`, math, tables, graphics, and factories for the common LaTeX packages (biblatex, cleveref, glossaries, hyperref, listings, ...). |
|
|
156
|
+
| `pytex_koma` | KOMA-Script classes and commands (`Addchap`, `Minisec`, `KOMAoptions`, ...). |
|
|
157
|
+
| `pytex_tikz` | TikZ pictures and primitives (`TikzPicture`, `Draw`, `Node`, `Circle`, ...). |
|
|
158
|
+
| `pytex_markdown` | Markdown -> native `TeX` conversion (see below). |
|
|
159
|
+
| `pytex_analyze` | static checks over the node tree (dangling refs, duplicate labels, missing images), plus `Optimize` to simplify a tree render-equivalently. |
|
|
160
|
+
| `pytex_hsrtreport` | HSRT report document class, colored callout boxes, title pages, glossary/citation helpers. |
|
|
161
|
+
| `pytex_protocol` | STUPA/AStA meeting minutes from Markdown, built on `pytex_hsrtreport`. |
|
|
162
|
+
|
|
163
|
+
## Markdown
|
|
164
|
+
|
|
165
|
+
`pytex_markdown` converts Markdown to native `TeX` nodes (via `marko`):
|
|
166
|
+
|
|
167
|
+
```py
|
|
168
|
+
from pytex_markdown import Markdown, IncludeMarkdown
|
|
169
|
+
|
|
170
|
+
body = Markdown("# Title\n\nText with **bold**, `code`, [a link](https://x).")
|
|
171
|
+
body = IncludeMarkdown("notes.md", base_level=-1) # base_level=-1: # -> \chapter
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Headings, emphasis, inline/fenced code, lists, links, images, block quotes and
|
|
175
|
+
thematic breaks map to the standard pytex library; text is LaTeX-escaped.
|
|
176
|
+
GitHub-style callouts become HSRT colored boxes (so the module depends on
|
|
177
|
+
`pytex_hsrtreport`):
|
|
178
|
+
|
|
179
|
+
```md
|
|
180
|
+
> [!NOTE] -> InfoBox > [!IMPORTANT] -> ImportantBox
|
|
181
|
+
> [!TIP] -> SuccessBox > [!WARNING] -> WarningBox
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Both factories are registered, so they work in `\iffalse{pytex(...)}\fi`
|
|
185
|
+
replacements in `.tex` sources too.
|
|
186
|
+
|
|
187
|
+
### Output variants
|
|
188
|
+
|
|
189
|
+
When the `pytex` command renders a `.md` file it wraps the converted nodes in a
|
|
190
|
+
document chosen by `--variant`:
|
|
191
|
+
|
|
192
|
+
| Variant | Document |
|
|
193
|
+
| --- | --- |
|
|
194
|
+
| `plain` | a bare `Document` (default class `article`); `#` -> `\section`. |
|
|
195
|
+
| `report` | an HSRT report with title page and table of contents; `#` -> `\chapter`. |
|
|
196
|
+
| `protocol-asta` | an AStA meeting protocol (HSRT report, AStA logos). |
|
|
197
|
+
| `protocol-stupa` | a StuPa meeting protocol (HSRT report, StuPa logos). |
|
|
198
|
+
|
|
199
|
+
Without `--variant`, protocol frontmatter (`gremium:` or `typ: protokoll`) picks
|
|
200
|
+
a protocol style and everything else falls back to `plain`.
|
|
201
|
+
|
|
202
|
+
Document-class parameters come from the YAML frontmatter and from `--config`
|
|
203
|
+
(a JSON object that overrides the frontmatter), e.g.:
|
|
204
|
+
|
|
205
|
+
```sh
|
|
206
|
+
pytex notes.md --variant plain --config '{"documentclass": "scrartcl", "classoptions": ["11pt", "twocolumn"]}'
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
`classoptions` accepts a list (`"twocolumn"`, `"DIV=12"`) or a `{key: value}`
|
|
210
|
+
object. For styles with a title page (`report`), the title is taken from
|
|
211
|
+
`title:`/`--config` if given, otherwise from the first `#` heading (which is then
|
|
212
|
+
not also rendered as a chapter).
|
|
213
|
+
|
|
214
|
+
## Converting LaTeX to PyTeX
|
|
215
|
+
|
|
216
|
+
`pytex-tex2py` turns an existing `.tex` file into an equivalent `.tex.py`
|
|
217
|
+
source. It reads the file, runs `Optimize` over it (expanding inline
|
|
218
|
+
`pytex(...)` markers and recognising comments and math), and serialises the
|
|
219
|
+
result to Python that rebuilds the same tree:
|
|
220
|
+
|
|
221
|
+
```sh
|
|
222
|
+
pytex-tex2py paper.tex # -> paper.tex.py
|
|
223
|
+
pytex-tex2py paper.tex -o out.py
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Rendering the generated `.tex.py` reproduces the original output byte-for-byte;
|
|
227
|
+
nodes the serialiser does not special-case fall back to a literal `Raw`, so the
|
|
228
|
+
conversion always round-trips.
|
|
229
|
+
|
|
230
|
+
## Examples
|
|
231
|
+
|
|
232
|
+
See `examples/` for one minimal input per kind (`.tex.py`, `.py.tex`, `.md`,
|
|
233
|
+
mixed, and a full HSRT report). Run from the repository root so relative paths
|
|
234
|
+
resolve:
|
|
235
|
+
|
|
236
|
+
```sh
|
|
237
|
+
pytex examples/document.tex.py --build
|
|
238
|
+
pytex examples/replacements.py.tex --build
|
|
239
|
+
pytex examples/notes.md --build
|
|
240
|
+
```
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pytex-preprocessor"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.3.0"
|
|
8
8
|
authors = [
|
|
9
9
|
{ name="Frederik Beimgraben", email="frederik@beimgraben.net" },
|
|
10
10
|
]
|
|
@@ -18,6 +18,7 @@ dev = ["pytest", "ruff", "basedpyright"]
|
|
|
18
18
|
|
|
19
19
|
[project.scripts]
|
|
20
20
|
pytex = "pytex_builder.build:main"
|
|
21
|
+
pytex-tex2py = "pytex_builder.tex2py:main"
|
|
21
22
|
|
|
22
23
|
[tool.setuptools.packages.find]
|
|
23
24
|
where = ["src"]
|
|
@@ -27,6 +27,7 @@ from .commands import (
|
|
|
27
27
|
from .helpers import coerce, sanitize, with_package
|
|
28
28
|
from .model import (
|
|
29
29
|
color,
|
|
30
|
+
comment,
|
|
30
31
|
concat,
|
|
31
32
|
control_sequence,
|
|
32
33
|
document,
|
|
@@ -51,6 +52,7 @@ __all__ = [
|
|
|
51
52
|
"coerce",
|
|
52
53
|
"color",
|
|
53
54
|
"colors",
|
|
55
|
+
"comment",
|
|
54
56
|
"concat",
|
|
55
57
|
"conditionals",
|
|
56
58
|
"control_sequence",
|
|
@@ -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
|
|
268
|
-
|
|
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
|
|
274
|
-
|
|
275
|
-
|
|
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
|