pytex-preprocessor 0.3.0__tar.gz → 0.4.1__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 → pytex_preprocessor-0.4.1}/PKG-INFO +31 -1
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/README.md +30 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/pyproject.toml +4 -1
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex/__init__.py +9 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex/model/math.py +38 -3
- pytex_preprocessor-0.4.1/src/pytex/template.py +90 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_analyze/optimize.py +21 -3
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_builder/build.py +11 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_preprocessor.egg-info/PKG-INFO +31 -1
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_preprocessor.egg-info/SOURCES.txt +1 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/setup.cfg +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex/commands/__init__.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex/commands/biblatex.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex/commands/builtin.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex/commands/captions.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex/commands/cleveref.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex/commands/colors.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex/commands/conditionals.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex/commands/counters.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex/commands/definitions.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex/commands/floats.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex/commands/font.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex/commands/fontawesome.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex/commands/fontspec.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex/commands/geometry.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex/commands/glossaries.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex/commands/graphics.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex/commands/hooks.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex/commands/hyperref.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex/commands/lengths.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex/commands/listings.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex/commands/mdframed.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex/commands/picture.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex/commands/setspace.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex/commands/tables.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex/helpers/__init__.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex/helpers/coerce.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex/helpers/parenting.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex/helpers/sanitize.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex/helpers/with_package.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex/interface/__init__.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex/interface/control_sequence.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex/interface/package.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex/interface/tex.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex/model/__init__.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex/model/color.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex/model/comment.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex/model/concat.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex/model/control_sequence.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex/model/document.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex/model/document_class.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex/model/empty.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex/model/environment.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex/model/image.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex/model/include.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex/model/length.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex/model/package.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex/model/raw.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex/packages.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex/registry.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_analyze/__init__.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_analyze/analyze.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_builder/__init__.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_builder/console.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_builder/render.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_builder/tectonic.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_builder/tex2py.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_builder/tree.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_builder/variants.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_hsrtreport/__init__.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_hsrtreport/assets/fonts/Blender/Blender-Bold.ttf +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_hsrtreport/assets/fonts/Blender/Blender-BoldItalic.ttf +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_hsrtreport/assets/fonts/Blender/Blender-Book.ttf +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_hsrtreport/assets/fonts/Blender/Blender-BookItalic.ttf +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_hsrtreport/assets/fonts/Blender/Blender-Medium.ttf +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_hsrtreport/assets/fonts/Blender/Blender-MediumItalic.ttf +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_hsrtreport/assets/fonts/Blender/Blender-Strong.ttf +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_hsrtreport/assets/fonts/Blender/Blender-Thin.ttf +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_hsrtreport/assets/fonts/Blender/Blender-ThinItalic.ttf +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_hsrtreport/assets/fonts/DIN/DIN-Black.ttf +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_hsrtreport/assets/fonts/DIN/DIN-Bold.ttf +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_hsrtreport/assets/fonts/DIN/DIN-BoldItalic.ttf +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_hsrtreport/assets/fonts/DIN/DIN-Italic.ttf +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_hsrtreport/assets/fonts/DIN/DIN-Medium.ttf +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_hsrtreport/assets/fonts/DIN/DIN-Regular.ttf +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_hsrtreport/assets/fonts/Times New Roman.ttf +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_hsrtreport/assets/logos/ASTA.svg +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_hsrtreport/assets/logos/DUMMY.png +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_hsrtreport/assets/logos/DUMMY_FOOT.png +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_hsrtreport/assets/logos/ECHO.svg +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_hsrtreport/assets/logos/HSRT.pdf +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_hsrtreport/assets/logos/INF.pdf +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_hsrtreport/assets/logos/STUPA.pdf +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_hsrtreport/assets/logos/Skyline.pdf +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_hsrtreport/boxes.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_hsrtreport/citations.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_hsrtreport/cleveref_names.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_hsrtreport/colors.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_hsrtreport/document.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_hsrtreport/fonts.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_hsrtreport/glossary.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_hsrtreport/hyperref_config.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_hsrtreport/listings.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_hsrtreport/logos.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_hsrtreport/pagebreak.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_hsrtreport/pagesetup.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_hsrtreport/tex/pagesetup.tex +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_hsrtreport/titlepage.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_hsrtreport/variants.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_hsrtreport/voting.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_hsrtreport/watermark.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_hsrtreport/wordcount.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_koma/__init__.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_koma/commands.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_koma/document.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_markdown/__init__.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_markdown/convert.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_markdown/escape.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_preprocessor.egg-info/dependency_links.txt +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_preprocessor.egg-info/entry_points.txt +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_preprocessor.egg-info/requires.txt +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_preprocessor.egg-info/top_level.txt +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_protocol/__init__.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_protocol/convert.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_protocol/document.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_protocol/entries.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_protocol/frontmatter.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_protocol/header.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_protocol/shortcodes.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_protocol/signatures.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_tikz/__init__.py +0 -0
- {pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/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.
|
|
3
|
+
Version: 0.4.1
|
|
4
4
|
Summary: Type-safe LaTeX document generation with Python
|
|
5
5
|
Author-email: Frederik Beimgraben <frederik@beimgraben.net>
|
|
6
6
|
Requires-Python: >=3.13
|
|
@@ -28,6 +28,20 @@ uses.
|
|
|
28
28
|
|
|
29
29
|
## Install
|
|
30
30
|
|
|
31
|
+
### Prebuilt binary
|
|
32
|
+
|
|
33
|
+
Each release attaches standalone `pytex` binaries (Linux/macOS/Windows) — no
|
|
34
|
+
Python or `pip` needed. Download one from the
|
|
35
|
+
[Releases](https://github.com/frederikbeimgraben/PyTeX-Preprocessor/releases)
|
|
36
|
+
page, make it executable, and run it. The binary bundles its own interpreter
|
|
37
|
+
plus common data packages (numpy, pandas, openpyxl/calamine for spreadsheets,
|
|
38
|
+
Pillow, PyYAML), so documents can `import` those without installing anything;
|
|
39
|
+
see [`packaging/`](packaging/). It is built on Python 3.14, so documents may use
|
|
40
|
+
`tex(t"...")` even on machines without 3.14. (`--build` still needs `tectonic`,
|
|
41
|
+
which pytex downloads on first use.)
|
|
42
|
+
|
|
43
|
+
### From PyPI
|
|
44
|
+
|
|
31
45
|
To use the `pytex` command anywhere, install it as an isolated tool with
|
|
32
46
|
[pipx](https://pipx.pypa.io/):
|
|
33
47
|
|
|
@@ -82,6 +96,22 @@ pytex example.tex.py --build # render + compile -> build/example.out.pdf
|
|
|
82
96
|
|
|
83
97
|
Bare strings are coerced to text nodes and LaTeX-escaped.
|
|
84
98
|
|
|
99
|
+
### Template strings (Python 3.14+)
|
|
100
|
+
|
|
101
|
+
On Python 3.14, `pytex.tex` accepts a [PEP 750](https://peps.python.org/pep-0750/)
|
|
102
|
+
template string and builds a `TeX` tree from it. Static parts are literal LaTeX;
|
|
103
|
+
interpolations are LaTeX-escaped when they are plain values and spliced as-is
|
|
104
|
+
when they are `TeX` nodes (nested template strings and lists are handled too):
|
|
105
|
+
|
|
106
|
+
```py
|
|
107
|
+
from pytex import tex
|
|
108
|
+
|
|
109
|
+
name = "Q&A: 50%"
|
|
110
|
+
body = tex(t"{Bold('Heading')} — {name}") # node spliced; name -> "Q\&A: 50\%"
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
`tex` is only exported on 3.14+; the rest of the library runs on 3.13.
|
|
114
|
+
|
|
85
115
|
## The `pytex` command
|
|
86
116
|
|
|
87
117
|
The input file is dispatched by extension:
|
|
@@ -14,6 +14,20 @@ uses.
|
|
|
14
14
|
|
|
15
15
|
## Install
|
|
16
16
|
|
|
17
|
+
### Prebuilt binary
|
|
18
|
+
|
|
19
|
+
Each release attaches standalone `pytex` binaries (Linux/macOS/Windows) — no
|
|
20
|
+
Python or `pip` needed. Download one from the
|
|
21
|
+
[Releases](https://github.com/frederikbeimgraben/PyTeX-Preprocessor/releases)
|
|
22
|
+
page, make it executable, and run it. The binary bundles its own interpreter
|
|
23
|
+
plus common data packages (numpy, pandas, openpyxl/calamine for spreadsheets,
|
|
24
|
+
Pillow, PyYAML), so documents can `import` those without installing anything;
|
|
25
|
+
see [`packaging/`](packaging/). It is built on Python 3.14, so documents may use
|
|
26
|
+
`tex(t"...")` even on machines without 3.14. (`--build` still needs `tectonic`,
|
|
27
|
+
which pytex downloads on first use.)
|
|
28
|
+
|
|
29
|
+
### From PyPI
|
|
30
|
+
|
|
17
31
|
To use the `pytex` command anywhere, install it as an isolated tool with
|
|
18
32
|
[pipx](https://pipx.pypa.io/):
|
|
19
33
|
|
|
@@ -68,6 +82,22 @@ pytex example.tex.py --build # render + compile -> build/example.out.pdf
|
|
|
68
82
|
|
|
69
83
|
Bare strings are coerced to text nodes and LaTeX-escaped.
|
|
70
84
|
|
|
85
|
+
### Template strings (Python 3.14+)
|
|
86
|
+
|
|
87
|
+
On Python 3.14, `pytex.tex` accepts a [PEP 750](https://peps.python.org/pep-0750/)
|
|
88
|
+
template string and builds a `TeX` tree from it. Static parts are literal LaTeX;
|
|
89
|
+
interpolations are LaTeX-escaped when they are plain values and spliced as-is
|
|
90
|
+
when they are `TeX` nodes (nested template strings and lists are handled too):
|
|
91
|
+
|
|
92
|
+
```py
|
|
93
|
+
from pytex import tex
|
|
94
|
+
|
|
95
|
+
name = "Q&A: 50%"
|
|
96
|
+
body = tex(t"{Bold('Heading')} — {name}") # node spliced; name -> "Q\&A: 50\%"
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
`tex` is only exported on 3.14+; the rest of the library runs on 3.13.
|
|
100
|
+
|
|
71
101
|
## The `pytex` command
|
|
72
102
|
|
|
73
103
|
The input file is dispatched by extension:
|
|
@@ -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.4.1"
|
|
8
8
|
authors = [
|
|
9
9
|
{ name="Frederik Beimgraben", email="frederik@beimgraben.net" },
|
|
10
10
|
]
|
|
@@ -37,6 +37,9 @@ addopts = "--import-mode=importlib"
|
|
|
37
37
|
line-length = 88
|
|
38
38
|
target-version = "py313"
|
|
39
39
|
src = ["src", "tests"]
|
|
40
|
+
# Uses Python 3.14 t-string syntax; the py313 target cannot parse it. Excluded
|
|
41
|
+
# from lint/format (it is exercised by pytest on 3.14).
|
|
42
|
+
extend-exclude = ["tests/pytex/test_template.py"]
|
|
40
43
|
|
|
41
44
|
[tool.ruff.lint]
|
|
42
45
|
select = [
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
|
|
1
3
|
from . import packages
|
|
2
4
|
from .commands import (
|
|
3
5
|
biblatex,
|
|
@@ -87,3 +89,10 @@ __all__ = [
|
|
|
87
89
|
"tables",
|
|
88
90
|
"with_package",
|
|
89
91
|
]
|
|
92
|
+
|
|
93
|
+
# `tex(t"...")` needs PEP 750 template strings (Python 3.14+). Exposed only
|
|
94
|
+
# there; the rest of the library stays importable on 3.13.
|
|
95
|
+
if sys.version_info >= (3, 14):
|
|
96
|
+
from .template import tex as tex # pyright: ignore[reportUnreachable]
|
|
97
|
+
|
|
98
|
+
__all__.append("tex")
|
|
@@ -67,11 +67,46 @@ __all__ = [
|
|
|
67
67
|
]
|
|
68
68
|
|
|
69
69
|
|
|
70
|
+
def _restrip(raw: Raw, how: str) -> Raw:
|
|
71
|
+
content = raw.content.lstrip() if how == "l" else raw.content.rstrip()
|
|
72
|
+
return Raw(content, raw.namespace, raw.allow_replacements)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _is_blank(node: TeX | str) -> bool:
|
|
76
|
+
return isinstance(node, Raw) and node.content.strip() == ""
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _trim(body: TeX | str) -> TeX | str:
|
|
80
|
+
"""Strip whitespace just inside the math delimiters.
|
|
81
|
+
|
|
82
|
+
TeX math mode ignores leading/trailing spaces and newlines, so this is
|
|
83
|
+
render-equivalent; it also avoids a blank line (`\\par`) sneaking inside the
|
|
84
|
+
delimiters, which is a hard LaTeX error. Only the outer boundary is touched
|
|
85
|
+
(interior whitespace can be significant, e.g. in `\\text{}` or alignments).
|
|
86
|
+
"""
|
|
87
|
+
if isinstance(body, str):
|
|
88
|
+
return body.strip()
|
|
89
|
+
if isinstance(body, Raw):
|
|
90
|
+
return Raw(body.content.strip(), body.namespace, body.allow_replacements)
|
|
91
|
+
if isinstance(body, Concat):
|
|
92
|
+
elements = list(body.elements)
|
|
93
|
+
while elements and _is_blank(elements[0]):
|
|
94
|
+
elements.pop(0)
|
|
95
|
+
while elements and _is_blank(elements[-1]):
|
|
96
|
+
elements.pop()
|
|
97
|
+
if elements and isinstance(elements[0], Raw):
|
|
98
|
+
elements[0] = _restrip(elements[0], "l")
|
|
99
|
+
if elements and isinstance(elements[-1], Raw):
|
|
100
|
+
elements[-1] = _restrip(elements[-1], "r")
|
|
101
|
+
return Concat(*elements)
|
|
102
|
+
return body
|
|
103
|
+
|
|
104
|
+
|
|
70
105
|
@Registry.add
|
|
71
106
|
def Math(body: TeX | str) -> TeX:
|
|
72
107
|
return Concat(
|
|
73
108
|
ControlSequence("(", ()),
|
|
74
|
-
body,
|
|
109
|
+
_trim(body),
|
|
75
110
|
ControlSequence(")", ()),
|
|
76
111
|
)
|
|
77
112
|
|
|
@@ -79,14 +114,14 @@ def Math(body: TeX | str) -> TeX:
|
|
|
79
114
|
@Registry.add
|
|
80
115
|
def InlineMath(body: TeX | str) -> TeX:
|
|
81
116
|
"""Dollar-delimited inline math: ``$body$``."""
|
|
82
|
-
return Concat(Raw("$"), body, Raw("$"))
|
|
117
|
+
return Concat(Raw("$"), _trim(body), Raw("$"))
|
|
83
118
|
|
|
84
119
|
|
|
85
120
|
@Registry.add
|
|
86
121
|
def DisplayMath(body: TeX | str) -> TeX:
|
|
87
122
|
return Concat(
|
|
88
123
|
ControlSequence("[", ()),
|
|
89
|
-
body,
|
|
124
|
+
_trim(body),
|
|
90
125
|
ControlSequence("]", ()),
|
|
91
126
|
)
|
|
92
127
|
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
r"""`tex(t"...")` — build a `TeX` tree from a template string (PEP 750).
|
|
2
|
+
|
|
3
|
+
Requires Python 3.14 (the ``t"..."`` syntax and :mod:`string.templatelib`).
|
|
4
|
+
This module is import-safe on older versions — it contains no t-string literals
|
|
5
|
+
and guards the runtime import — but :func:`tex` can only be called with a real
|
|
6
|
+
``Template``, which cannot exist before 3.14. ``pytex`` only re-exports it on
|
|
7
|
+
3.14+.
|
|
8
|
+
|
|
9
|
+
The rendering model mirrors the escape boundary a LaTeX document needs:
|
|
10
|
+
|
|
11
|
+
* static template parts are literal LaTeX (author-written, trusted);
|
|
12
|
+
* interpolations are escaped when they are plain values, spliced as-is when
|
|
13
|
+
they are `TeX` nodes, and recursed when they are nested template strings or
|
|
14
|
+
iterables of the above.
|
|
15
|
+
|
|
16
|
+
name = "Q&A: 50%"
|
|
17
|
+
tex(t"{Bold('Heading')} - {name}") # node spliced; name -> "Q\&A: 50\%"
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
# `string.templatelib` has no type stub before 3.14, so a type-checker running
|
|
21
|
+
# on an older Python cannot type the `Template` import; silence that noise for
|
|
22
|
+
# this module only (its logic is exercised on 3.14 in CI / the docker test).
|
|
23
|
+
# pyright: reportMissingImports=false, reportUnknownVariableType=false
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
from typing import TYPE_CHECKING, cast
|
|
27
|
+
|
|
28
|
+
from .helpers.sanitize import escape_latex
|
|
29
|
+
from .interface.tex import TeX
|
|
30
|
+
from .model.concat import Concat
|
|
31
|
+
from .model.empty import Empty
|
|
32
|
+
from .model.raw import Raw
|
|
33
|
+
|
|
34
|
+
if TYPE_CHECKING:
|
|
35
|
+
from collections.abc import Iterator
|
|
36
|
+
from typing import Protocol
|
|
37
|
+
|
|
38
|
+
class _Interpolation(Protocol):
|
|
39
|
+
@property
|
|
40
|
+
def value(self) -> object: ...
|
|
41
|
+
@property
|
|
42
|
+
def conversion(self) -> str | None: ...
|
|
43
|
+
@property
|
|
44
|
+
def format_spec(self) -> str: ...
|
|
45
|
+
|
|
46
|
+
class _Template(Protocol):
|
|
47
|
+
def __iter__(self) -> Iterator[str | _Interpolation]: ...
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# Real `Template` class at runtime (3.14+) for the nested-template check; an
|
|
51
|
+
# empty tuple on older versions makes the isinstance test always False.
|
|
52
|
+
try:
|
|
53
|
+
from string.templatelib import Template
|
|
54
|
+
|
|
55
|
+
_template_classes = (Template,)
|
|
56
|
+
except ImportError: # Python < 3.14
|
|
57
|
+
_template_classes = ()
|
|
58
|
+
|
|
59
|
+
_TEMPLATE_TYPES: tuple[type, ...] = _template_classes
|
|
60
|
+
|
|
61
|
+
__all__ = ["tex"]
|
|
62
|
+
|
|
63
|
+
_CONVERSIONS = {"r": repr, "s": str, "a": ascii}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def tex(template: _Template) -> TeX:
|
|
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)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _coerce(value: object, conversion: str | None = None, spec: object = "") -> TeX:
|
|
78
|
+
if value is None:
|
|
79
|
+
return Empty
|
|
80
|
+
if isinstance(value, TeX):
|
|
81
|
+
return value
|
|
82
|
+
if isinstance(value, _TEMPLATE_TYPES):
|
|
83
|
+
return tex(cast("_Template", value))
|
|
84
|
+
if isinstance(value, (list, tuple)):
|
|
85
|
+
items = cast("tuple[object, ...] | list[object]", value)
|
|
86
|
+
return Concat(*(_coerce(item) for item in items))
|
|
87
|
+
if conversion in _CONVERSIONS:
|
|
88
|
+
value = _CONVERSIONS[conversion](value)
|
|
89
|
+
text = format(value, spec if isinstance(spec, str) else "")
|
|
90
|
+
return Raw(escape_latex(text))
|
|
@@ -130,10 +130,24 @@ def _native(raw: Raw) -> TeX:
|
|
|
130
130
|
return raw
|
|
131
131
|
|
|
132
132
|
|
|
133
|
+
_MATH_OPEN = re.compile(r"(\\[\[(])\s+")
|
|
134
|
+
_MATH_CLOSE = re.compile(r"\s+(\\[\])])")
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _strip_math_ws(text: str) -> str:
|
|
138
|
+
r"""Drop whitespace just inside ``\[..\]`` and ``\(..\)``.
|
|
139
|
+
|
|
140
|
+
TeX math mode ignores it, so two strings that differ only there render the
|
|
141
|
+
same — this lets the tokenizer accept a `DisplayMath`/`Math` candidate
|
|
142
|
+
(which trims that whitespace) as equivalent to the original.
|
|
143
|
+
"""
|
|
144
|
+
return _MATH_CLOSE.sub(r"\1", _MATH_OPEN.sub(r"\1", text))
|
|
145
|
+
|
|
146
|
+
|
|
133
147
|
def _tokenize(raw: Raw) -> TeX | None:
|
|
134
148
|
"""Split a `Raw` into literal text and the native nodes for the constructs
|
|
135
|
-
in `_TOKEN`. Returns `None` when nothing matches or the result would
|
|
136
|
-
|
|
149
|
+
in `_TOKEN`. Returns `None` when nothing matches or the result would change
|
|
150
|
+
the rendered output (math-delimiter whitespace aside — TeX ignores that)."""
|
|
137
151
|
content = raw.content
|
|
138
152
|
namespace = pytex_namespace(raw.namespace or {})
|
|
139
153
|
parts: list[TeX] = []
|
|
@@ -148,7 +162,11 @@ def _tokenize(raw: Raw) -> TeX | None:
|
|
|
148
162
|
if cursor < len(content):
|
|
149
163
|
parts.append(Raw(content[cursor:], allow_replacements=False))
|
|
150
164
|
candidate = Concat(*(_optimize(part) for part in parts))
|
|
151
|
-
|
|
165
|
+
rendered = candidate.rendered
|
|
166
|
+
target = raw.rendered
|
|
167
|
+
if rendered == target or _strip_math_ws(rendered) == _strip_math_ws(target):
|
|
168
|
+
return candidate
|
|
169
|
+
return None
|
|
152
170
|
|
|
153
171
|
|
|
154
172
|
def _token_node(
|
|
@@ -12,6 +12,7 @@ import json
|
|
|
12
12
|
import re
|
|
13
13
|
import sys
|
|
14
14
|
from dataclasses import dataclass
|
|
15
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
15
16
|
from pathlib import Path
|
|
16
17
|
from typing import TYPE_CHECKING, cast
|
|
17
18
|
|
|
@@ -76,11 +77,21 @@ def _slug(name: str) -> str:
|
|
|
76
77
|
return name or "document"
|
|
77
78
|
|
|
78
79
|
|
|
80
|
+
def _version() -> str:
|
|
81
|
+
try:
|
|
82
|
+
return version("pytex-preprocessor")
|
|
83
|
+
except PackageNotFoundError:
|
|
84
|
+
return "unknown"
|
|
85
|
+
|
|
86
|
+
|
|
79
87
|
def _parse_args(argv: list[str]) -> Config:
|
|
80
88
|
parser = argparse.ArgumentParser(
|
|
81
89
|
prog="pytex",
|
|
82
90
|
description="Render PyTeX (.py) or LaTeX (.tex) sources and build PDFs.",
|
|
83
91
|
)
|
|
92
|
+
_ = parser.add_argument(
|
|
93
|
+
"--version", action="version", version=f"pytex {_version()}"
|
|
94
|
+
)
|
|
84
95
|
_ = parser.add_argument(
|
|
85
96
|
"input",
|
|
86
97
|
type=Path,
|
{pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_preprocessor.egg-info/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pytex-preprocessor
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.1
|
|
4
4
|
Summary: Type-safe LaTeX document generation with Python
|
|
5
5
|
Author-email: Frederik Beimgraben <frederik@beimgraben.net>
|
|
6
6
|
Requires-Python: >=3.13
|
|
@@ -28,6 +28,20 @@ uses.
|
|
|
28
28
|
|
|
29
29
|
## Install
|
|
30
30
|
|
|
31
|
+
### Prebuilt binary
|
|
32
|
+
|
|
33
|
+
Each release attaches standalone `pytex` binaries (Linux/macOS/Windows) — no
|
|
34
|
+
Python or `pip` needed. Download one from the
|
|
35
|
+
[Releases](https://github.com/frederikbeimgraben/PyTeX-Preprocessor/releases)
|
|
36
|
+
page, make it executable, and run it. The binary bundles its own interpreter
|
|
37
|
+
plus common data packages (numpy, pandas, openpyxl/calamine for spreadsheets,
|
|
38
|
+
Pillow, PyYAML), so documents can `import` those without installing anything;
|
|
39
|
+
see [`packaging/`](packaging/). It is built on Python 3.14, so documents may use
|
|
40
|
+
`tex(t"...")` even on machines without 3.14. (`--build` still needs `tectonic`,
|
|
41
|
+
which pytex downloads on first use.)
|
|
42
|
+
|
|
43
|
+
### From PyPI
|
|
44
|
+
|
|
31
45
|
To use the `pytex` command anywhere, install it as an isolated tool with
|
|
32
46
|
[pipx](https://pipx.pypa.io/):
|
|
33
47
|
|
|
@@ -82,6 +96,22 @@ pytex example.tex.py --build # render + compile -> build/example.out.pdf
|
|
|
82
96
|
|
|
83
97
|
Bare strings are coerced to text nodes and LaTeX-escaped.
|
|
84
98
|
|
|
99
|
+
### Template strings (Python 3.14+)
|
|
100
|
+
|
|
101
|
+
On Python 3.14, `pytex.tex` accepts a [PEP 750](https://peps.python.org/pep-0750/)
|
|
102
|
+
template string and builds a `TeX` tree from it. Static parts are literal LaTeX;
|
|
103
|
+
interpolations are LaTeX-escaped when they are plain values and spliced as-is
|
|
104
|
+
when they are `TeX` nodes (nested template strings and lists are handled too):
|
|
105
|
+
|
|
106
|
+
```py
|
|
107
|
+
from pytex import tex
|
|
108
|
+
|
|
109
|
+
name = "Q&A: 50%"
|
|
110
|
+
body = tex(t"{Bold('Heading')} — {name}") # node spliced; name -> "Q\&A: 50\%"
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
`tex` is only exported on 3.14+; the rest of the library runs on 3.13.
|
|
114
|
+
|
|
85
115
|
## The `pytex` command
|
|
86
116
|
|
|
87
117
|
The input file is dispatched by extension:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex/interface/control_sequence.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_hsrtreport/assets/logos/ASTA.svg
RENAMED
|
File without changes
|
{pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_hsrtreport/assets/logos/DUMMY.png
RENAMED
|
File without changes
|
|
File without changes
|
{pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_hsrtreport/assets/logos/ECHO.svg
RENAMED
|
File without changes
|
{pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_hsrtreport/assets/logos/HSRT.pdf
RENAMED
|
File without changes
|
{pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_hsrtreport/assets/logos/INF.pdf
RENAMED
|
File without changes
|
{pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_hsrtreport/assets/logos/STUPA.pdf
RENAMED
|
File without changes
|
{pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_hsrtreport/assets/logos/Skyline.pdf
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_hsrtreport/cleveref_names.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_hsrtreport/hyperref_config.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_hsrtreport/tex/pagesetup.tex
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_preprocessor.egg-info/requires.txt
RENAMED
|
File without changes
|
{pytex_preprocessor-0.3.0 → pytex_preprocessor-0.4.1}/src/pytex_preprocessor.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|