pytex-preprocessor 0.1.0rc1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pytex/__init__.py +87 -0
- pytex/commands/__init__.py +51 -0
- pytex/commands/biblatex.py +98 -0
- pytex/commands/builtin.py +598 -0
- pytex/commands/captions.py +56 -0
- pytex/commands/cleveref.py +43 -0
- pytex/commands/colors.py +60 -0
- pytex/commands/conditionals.py +62 -0
- pytex/commands/counters.py +85 -0
- pytex/commands/definitions.py +109 -0
- pytex/commands/floats.py +93 -0
- pytex/commands/font.py +138 -0
- pytex/commands/fontawesome.py +88 -0
- pytex/commands/fontspec.py +75 -0
- pytex/commands/geometry.py +25 -0
- pytex/commands/glossaries.py +126 -0
- pytex/commands/graphics.py +68 -0
- pytex/commands/hooks.py +58 -0
- pytex/commands/hyperref.py +57 -0
- pytex/commands/lengths.py +200 -0
- pytex/commands/listings.py +63 -0
- pytex/commands/mdframed.py +43 -0
- pytex/commands/picture.py +32 -0
- pytex/commands/setspace.py +38 -0
- pytex/commands/tables.py +123 -0
- pytex/helpers/__init__.py +3 -0
- pytex/helpers/coerce.py +13 -0
- pytex/helpers/parenting.py +13 -0
- pytex/helpers/sanitize.py +54 -0
- pytex/helpers/with_package.py +61 -0
- pytex/interface/__init__.py +3 -0
- pytex/interface/control_sequence.py +29 -0
- pytex/interface/package.py +52 -0
- pytex/interface/tex.py +41 -0
- pytex/model/__init__.py +25 -0
- pytex/model/color.py +203 -0
- pytex/model/concat.py +31 -0
- pytex/model/control_sequence.py +72 -0
- pytex/model/document.py +120 -0
- pytex/model/document_class.py +29 -0
- pytex/model/empty.py +19 -0
- pytex/model/environment.py +30 -0
- pytex/model/image.py +137 -0
- pytex/model/include.py +21 -0
- pytex/model/length.py +54 -0
- pytex/model/math.py +401 -0
- pytex/model/package.py +132 -0
- pytex/model/raw.py +61 -0
- pytex/packages.py +221 -0
- pytex/registry.py +49 -0
- pytex_builder/__init__.py +8 -0
- pytex_builder/build.py +175 -0
- pytex_builder/console.py +77 -0
- pytex_builder/render.py +82 -0
- pytex_builder/tectonic.py +307 -0
- pytex_hsrtreport/__init__.py +116 -0
- pytex_hsrtreport/assets/fonts/Blender/Blender-Bold.ttf +0 -0
- pytex_hsrtreport/assets/fonts/Blender/Blender-BoldItalic.ttf +0 -0
- pytex_hsrtreport/assets/fonts/Blender/Blender-Book.ttf +0 -0
- pytex_hsrtreport/assets/fonts/Blender/Blender-BookItalic.ttf +0 -0
- pytex_hsrtreport/assets/fonts/Blender/Blender-Medium.ttf +0 -0
- pytex_hsrtreport/assets/fonts/Blender/Blender-MediumItalic.ttf +0 -0
- pytex_hsrtreport/assets/fonts/Blender/Blender-Strong.ttf +0 -0
- pytex_hsrtreport/assets/fonts/Blender/Blender-Thin.ttf +0 -0
- pytex_hsrtreport/assets/fonts/Blender/Blender-ThinItalic.ttf +0 -0
- pytex_hsrtreport/assets/fonts/DIN/DIN-Black.ttf +0 -0
- pytex_hsrtreport/assets/fonts/DIN/DIN-Bold.ttf +0 -0
- pytex_hsrtreport/assets/fonts/DIN/DIN-BoldItalic.ttf +0 -0
- pytex_hsrtreport/assets/fonts/DIN/DIN-Italic.ttf +0 -0
- pytex_hsrtreport/assets/fonts/DIN/DIN-Medium.ttf +0 -0
- pytex_hsrtreport/assets/fonts/DIN/DIN-Regular.ttf +0 -0
- pytex_hsrtreport/assets/fonts/Times New Roman.ttf +0 -0
- pytex_hsrtreport/assets/logos/ASTA.svg +79 -0
- pytex_hsrtreport/assets/logos/DUMMY.png +0 -0
- pytex_hsrtreport/assets/logos/DUMMY_FOOT.png +0 -0
- pytex_hsrtreport/assets/logos/ECHO.svg +226 -0
- pytex_hsrtreport/assets/logos/HSRT.pdf +0 -0
- pytex_hsrtreport/assets/logos/INF.pdf +0 -0
- pytex_hsrtreport/assets/logos/STUPA.pdf +0 -0
- pytex_hsrtreport/assets/logos/Skyline.pdf +0 -0
- pytex_hsrtreport/boxes.py +215 -0
- pytex_hsrtreport/citations.py +19 -0
- pytex_hsrtreport/cleveref_names.py +47 -0
- pytex_hsrtreport/colors.py +30 -0
- pytex_hsrtreport/document.py +302 -0
- pytex_hsrtreport/fonts.py +66 -0
- pytex_hsrtreport/glossary.py +61 -0
- pytex_hsrtreport/hyperref_config.py +45 -0
- pytex_hsrtreport/listings.py +90 -0
- pytex_hsrtreport/logos.py +234 -0
- pytex_hsrtreport/pagebreak.py +67 -0
- pytex_hsrtreport/pagesetup.py +33 -0
- pytex_hsrtreport/tex/pagesetup.tex +76 -0
- pytex_hsrtreport/titlepage.py +136 -0
- pytex_hsrtreport/variants.py +24 -0
- pytex_hsrtreport/voting.py +96 -0
- pytex_hsrtreport/watermark.py +63 -0
- pytex_hsrtreport/wordcount.py +33 -0
- pytex_koma/__init__.py +90 -0
- pytex_koma/commands.py +296 -0
- pytex_koma/document.py +138 -0
- pytex_markdown/__init__.py +62 -0
- pytex_markdown/convert.py +268 -0
- pytex_markdown/escape.py +11 -0
- pytex_preprocessor-0.1.0rc1.dist-info/METADATA +82 -0
- pytex_preprocessor-0.1.0rc1.dist-info/RECORD +111 -0
- pytex_preprocessor-0.1.0rc1.dist-info/WHEEL +5 -0
- pytex_preprocessor-0.1.0rc1.dist-info/entry_points.txt +2 -0
- pytex_preprocessor-0.1.0rc1.dist-info/top_level.txt +6 -0
- pytex_tikz/__init__.py +25 -0
- pytex_tikz/tikz.py +272 -0
pytex/packages.py
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
from pytex.model.package import DefinePackage, Package
|
|
4
|
+
|
|
5
|
+
__all__ = ["Packages"]
|
|
6
|
+
|
|
7
|
+
AMSMATH: Package = DefinePackage("amsmath")
|
|
8
|
+
AMSSYMB: Package = DefinePackage("amssymb")
|
|
9
|
+
AMSFONTS: Package = DefinePackage("amsfonts")
|
|
10
|
+
MATHTOOLS: Package = DefinePackage("mathtools")
|
|
11
|
+
HYPERREF: Package = DefinePackage("hyperref")
|
|
12
|
+
URL: Package = DefinePackage("url")
|
|
13
|
+
GRAPHICX: Package = DefinePackage("graphicx")
|
|
14
|
+
XCOLOR: Package = DefinePackage("xcolor")
|
|
15
|
+
GEOMETRY: Package = DefinePackage("geometry")
|
|
16
|
+
FONTENC: Package = DefinePackage("fontenc")
|
|
17
|
+
INPUTENC: Package = DefinePackage("inputenc")
|
|
18
|
+
BABEL: Package = DefinePackage("babel", options={"ngerman"})
|
|
19
|
+
MICROTYPE: Package = DefinePackage("microtype")
|
|
20
|
+
CSQUOTES: Package = DefinePackage("csquotes")
|
|
21
|
+
BOOKTABS: Package = DefinePackage("booktabs")
|
|
22
|
+
ARRAY: Package = DefinePackage("array")
|
|
23
|
+
TABULARX: Package = DefinePackage("tabularx")
|
|
24
|
+
LONGTABLE: Package = DefinePackage("longtable")
|
|
25
|
+
MULTIROW: Package = DefinePackage("multirow")
|
|
26
|
+
CAPTION: Package = DefinePackage("caption")
|
|
27
|
+
SUBCAPTION: Package = DefinePackage("subcaption")
|
|
28
|
+
FLOAT: Package = DefinePackage("float")
|
|
29
|
+
FANCYHDR: Package = DefinePackage("fancyhdr")
|
|
30
|
+
TITLESEC: Package = DefinePackage("titlesec")
|
|
31
|
+
ENUMITEM: Package = DefinePackage("enumitem")
|
|
32
|
+
LISTINGS: Package = DefinePackage("listings")
|
|
33
|
+
MINTED: Package = DefinePackage("minted")
|
|
34
|
+
TIKZ: Package = DefinePackage("tikz")
|
|
35
|
+
PGFPLOTS: Package = DefinePackage("pgfplots")
|
|
36
|
+
TCOLORBOX: Package = DefinePackage("tcolorbox")
|
|
37
|
+
SIUNITX: Package = DefinePackage("siunitx")
|
|
38
|
+
GLOSSARIES: Package = DefinePackage("glossaries", options={"acronym"})
|
|
39
|
+
SVG: Package = DefinePackage("svg")
|
|
40
|
+
SOUL: Package = DefinePackage("soul")
|
|
41
|
+
ULEM: Package = DefinePackage("ulem")
|
|
42
|
+
WRAPFIG: Package = DefinePackage("wrapfig")
|
|
43
|
+
PARSKIP: Package = DefinePackage("parskip")
|
|
44
|
+
SETSPACE: Package = DefinePackage("setspace")
|
|
45
|
+
TIKZ_LIB_ARROWS: Package = DefinePackage("tikz-arrows", after={TIKZ})
|
|
46
|
+
PGF: Package = DefinePackage("pgf")
|
|
47
|
+
LIPSUM: Package = DefinePackage("lipsum")
|
|
48
|
+
BLINDTEXT: Package = DefinePackage("blindtext")
|
|
49
|
+
TODONOTES: Package = DefinePackage("todonotes")
|
|
50
|
+
COMMENT: Package = DefinePackage("comment")
|
|
51
|
+
IFTHEN: Package = DefinePackage("ifthen")
|
|
52
|
+
ETOOLBOX: Package = DefinePackage("etoolbox")
|
|
53
|
+
XPARSE: Package = DefinePackage("xparse")
|
|
54
|
+
CALC: Package = DefinePackage("calc")
|
|
55
|
+
MAKEIDX: Package = DefinePackage("makeidx")
|
|
56
|
+
APPENDIX: Package = DefinePackage("appendix")
|
|
57
|
+
TITLING: Package = DefinePackage("titling")
|
|
58
|
+
LASTPAGE: Package = DefinePackage("lastpage")
|
|
59
|
+
CLEVEREF: Package = DefinePackage("cleveref", after={HYPERREF, AMSMATH})
|
|
60
|
+
VARIOREF: Package = DefinePackage("varioref")
|
|
61
|
+
FRAMED: Package = DefinePackage("framed")
|
|
62
|
+
# framemethod=tikz must be set at load time (preamble) for mdframed's rounded
|
|
63
|
+
# backgrounds; it cannot be switched on per-environment mid-document.
|
|
64
|
+
MDFRAMED: Package = DefinePackage("mdframed", options={("framemethod", "tikz")})
|
|
65
|
+
PDFPAGES: Package = DefinePackage("pdfpages")
|
|
66
|
+
FONTSPEC: Package = DefinePackage("fontspec")
|
|
67
|
+
UNICODE_MATH: Package = DefinePackage("unicode-math")
|
|
68
|
+
BIBLATEX: Package = DefinePackage("biblatex")
|
|
69
|
+
TOCLOFT: Package = DefinePackage("tocloft")
|
|
70
|
+
NOTOCCITE: Package = DefinePackage("notoccite")
|
|
71
|
+
NATBIB: Package = DefinePackage("natbib")
|
|
72
|
+
ACRONYM: Package = DefinePackage("acronym")
|
|
73
|
+
NOMENCL: Package = DefinePackage("nomencl")
|
|
74
|
+
ALGORITHM2E: Package = DefinePackage("algorithm2e")
|
|
75
|
+
ALGORITHMS: Package = DefinePackage("algorithms")
|
|
76
|
+
ALGORITHMICX: Package = DefinePackage("algorithmicx")
|
|
77
|
+
LSTLISTING: Package = LISTINGS # alias
|
|
78
|
+
TBL_THEOREM: Package = DefinePackage("amsthm", after={AMSMATH})
|
|
79
|
+
AMSTHM: Package = TBL_THEOREM
|
|
80
|
+
THMTOOLS: Package = DefinePackage("thmtools")
|
|
81
|
+
ENUMERATE: Package = DefinePackage("enumerate")
|
|
82
|
+
PARALIST: Package = DefinePackage("paralist")
|
|
83
|
+
TYPEAREA: Package = DefinePackage("typearea")
|
|
84
|
+
SCRLAYER_SCRPAGE: Package = DefinePackage("scrlayer-scrpage")
|
|
85
|
+
KOMA_SCRIPT: Package = DefinePackage("koma-script")
|
|
86
|
+
ADJUSTBOX: Package = DefinePackage("adjustbox")
|
|
87
|
+
RAGGED2E: Package = DefinePackage("ragged2e")
|
|
88
|
+
NEEDSPACE: Package = DefinePackage("needspace")
|
|
89
|
+
PLACEINS: Package = DefinePackage("placeins")
|
|
90
|
+
FOOTMISC: Package = DefinePackage("footmisc")
|
|
91
|
+
ROTATING: Package = DefinePackage("rotating")
|
|
92
|
+
CHANGEPAGE: Package = DefinePackage("changepage")
|
|
93
|
+
EVERYPAGE: Package = DefinePackage("everypage")
|
|
94
|
+
EVERYSHI: Package = DefinePackage("everyshi")
|
|
95
|
+
TIKZ_LIB_POSITIONING: Package = DefinePackage("tikz-positioning", after={TIKZ})
|
|
96
|
+
TIKZPAGENODES: Package = DefinePackage("tikzpagenodes", after={TIKZ})
|
|
97
|
+
PGFFOR: Package = DefinePackage("pgffor")
|
|
98
|
+
ARRAYJOBX: Package = DefinePackage("arrayjobx")
|
|
99
|
+
BOPHOOK: Package = DefinePackage("bophook")
|
|
100
|
+
ENVIRON: Package = DefinePackage("environ")
|
|
101
|
+
EXPL3: Package = DefinePackage("expl3")
|
|
102
|
+
L3KEYS2E: Package = DefinePackage("l3keys2e")
|
|
103
|
+
PDFTEXCMDS: Package = DefinePackage("pdftexcmds")
|
|
104
|
+
XFP: Package = DefinePackage("xfp")
|
|
105
|
+
KEYVAL: Package = DefinePackage("keyval")
|
|
106
|
+
PIFONT: Package = DefinePackage("pifont")
|
|
107
|
+
FONTAWESOME: Package = DefinePackage("fontawesome")
|
|
108
|
+
FLOATROW: Package = DefinePackage("floatrow")
|
|
109
|
+
CHNGCNTR: Package = DefinePackage("chngcntr")
|
|
110
|
+
ACCSUPP: Package = DefinePackage("accsupp")
|
|
111
|
+
AFTERPAGE: Package = DefinePackage("afterpage")
|
|
112
|
+
ARYDSHLN: Package = DefinePackage("arydshln")
|
|
113
|
+
LMODERN: Package = DefinePackage("lmodern")
|
|
114
|
+
HYPHENAT: Package = DefinePackage("hyphenat")
|
|
115
|
+
FP: Package = DefinePackage("fp")
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class Packages(Enum):
|
|
119
|
+
AMSMATH = AMSMATH
|
|
120
|
+
AMSSYMB = AMSSYMB
|
|
121
|
+
AMSFONTS = AMSFONTS
|
|
122
|
+
MATHTOOLS = MATHTOOLS
|
|
123
|
+
HYPERREF = HYPERREF
|
|
124
|
+
URL = URL
|
|
125
|
+
GRAPHICX = GRAPHICX
|
|
126
|
+
XCOLOR = XCOLOR
|
|
127
|
+
GEOMETRY = GEOMETRY
|
|
128
|
+
FONTENC = FONTENC
|
|
129
|
+
INPUTENC = INPUTENC
|
|
130
|
+
BABEL = BABEL
|
|
131
|
+
MICROTYPE = MICROTYPE
|
|
132
|
+
CSQUOTES = CSQUOTES
|
|
133
|
+
BOOKTABS = BOOKTABS
|
|
134
|
+
ARRAY = ARRAY
|
|
135
|
+
TABULARX = TABULARX
|
|
136
|
+
LONGTABLE = LONGTABLE
|
|
137
|
+
MULTIROW = MULTIROW
|
|
138
|
+
CAPTION = CAPTION
|
|
139
|
+
SUBCAPTION = SUBCAPTION
|
|
140
|
+
FLOAT = FLOAT
|
|
141
|
+
FANCYHDR = FANCYHDR
|
|
142
|
+
TITLESEC = TITLESEC
|
|
143
|
+
ENUMITEM = ENUMITEM
|
|
144
|
+
LISTINGS = LISTINGS
|
|
145
|
+
MINTED = MINTED
|
|
146
|
+
TIKZ = TIKZ
|
|
147
|
+
PGFPLOTS = PGFPLOTS
|
|
148
|
+
TCOLORBOX = TCOLORBOX
|
|
149
|
+
SIUNITX = SIUNITX
|
|
150
|
+
BIBLATEX = BIBLATEX
|
|
151
|
+
GLOSSARIES = GLOSSARIES
|
|
152
|
+
SVG = SVG
|
|
153
|
+
SOUL = SOUL
|
|
154
|
+
ULEM = ULEM
|
|
155
|
+
WRAPFIG = WRAPFIG
|
|
156
|
+
PARSKIP = PARSKIP
|
|
157
|
+
SETSPACE = SETSPACE
|
|
158
|
+
PGF = PGF
|
|
159
|
+
LIPSUM = LIPSUM
|
|
160
|
+
BLINDTEXT = BLINDTEXT
|
|
161
|
+
TODONOTES = TODONOTES
|
|
162
|
+
COMMENT = COMMENT
|
|
163
|
+
IFTHEN = IFTHEN
|
|
164
|
+
ETOOLBOX = ETOOLBOX
|
|
165
|
+
XPARSE = XPARSE
|
|
166
|
+
CALC = CALC
|
|
167
|
+
MAKEIDX = MAKEIDX
|
|
168
|
+
APPENDIX = APPENDIX
|
|
169
|
+
TITLING = TITLING
|
|
170
|
+
LASTPAGE = LASTPAGE
|
|
171
|
+
CLEVEREF = CLEVEREF
|
|
172
|
+
VARIOREF = VARIOREF
|
|
173
|
+
FRAMED = FRAMED
|
|
174
|
+
MDFRAMED = MDFRAMED
|
|
175
|
+
PDFPAGES = PDFPAGES
|
|
176
|
+
FONTSPEC = FONTSPEC
|
|
177
|
+
UNICODE_MATH = UNICODE_MATH
|
|
178
|
+
TOCLOFT = TOCLOFT
|
|
179
|
+
NOTOCCITE = NOTOCCITE
|
|
180
|
+
NATBIB = NATBIB
|
|
181
|
+
ACRONYM = ACRONYM
|
|
182
|
+
NOMENCL = NOMENCL
|
|
183
|
+
ALGORITHM2E = ALGORITHM2E
|
|
184
|
+
ALGORITHMS = ALGORITHMS
|
|
185
|
+
ALGORITHMICX = ALGORITHMICX
|
|
186
|
+
AMSTHM = AMSTHM
|
|
187
|
+
THMTOOLS = THMTOOLS
|
|
188
|
+
ENUMERATE = ENUMERATE
|
|
189
|
+
PARALIST = PARALIST
|
|
190
|
+
TYPEAREA = TYPEAREA
|
|
191
|
+
SCRLAYER_SCRPAGE = SCRLAYER_SCRPAGE
|
|
192
|
+
KOMA_SCRIPT = KOMA_SCRIPT
|
|
193
|
+
ADJUSTBOX = ADJUSTBOX
|
|
194
|
+
RAGGED2E = RAGGED2E
|
|
195
|
+
NEEDSPACE = NEEDSPACE
|
|
196
|
+
PLACEINS = PLACEINS
|
|
197
|
+
FOOTMISC = FOOTMISC
|
|
198
|
+
ROTATING = ROTATING
|
|
199
|
+
CHANGEPAGE = CHANGEPAGE
|
|
200
|
+
EVERYPAGE = EVERYPAGE
|
|
201
|
+
EVERYSHI = EVERYSHI
|
|
202
|
+
TIKZPAGENODES = TIKZPAGENODES
|
|
203
|
+
PGFFOR = PGFFOR
|
|
204
|
+
ARRAYJOBX = ARRAYJOBX
|
|
205
|
+
BOPHOOK = BOPHOOK
|
|
206
|
+
ENVIRON = ENVIRON
|
|
207
|
+
EXPL3 = EXPL3
|
|
208
|
+
L3KEYS2E = L3KEYS2E
|
|
209
|
+
PDFTEXCMDS = PDFTEXCMDS
|
|
210
|
+
XFP = XFP
|
|
211
|
+
KEYVAL = KEYVAL
|
|
212
|
+
PIFONT = PIFONT
|
|
213
|
+
FONTAWESOME = FONTAWESOME
|
|
214
|
+
FLOATROW = FLOATROW
|
|
215
|
+
CHNGCNTR = CHNGCNTR
|
|
216
|
+
ACCSUPP = ACCSUPP
|
|
217
|
+
AFTERPAGE = AFTERPAGE
|
|
218
|
+
ARYDSHLN = ARYDSHLN
|
|
219
|
+
LMODERN = LMODERN
|
|
220
|
+
HYPHENAT = HYPHENAT
|
|
221
|
+
FP = FP
|
pytex/registry.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
from logging import Logger
|
|
3
|
+
from typing import ClassVar, Self
|
|
4
|
+
|
|
5
|
+
from pytex.interface.tex import TeX
|
|
6
|
+
|
|
7
|
+
__all__ = ["Registry"]
|
|
8
|
+
|
|
9
|
+
type TeXFactory = Callable[..., TeX]
|
|
10
|
+
|
|
11
|
+
type RegistryType = type[TeX] | TeXFactory
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Registry:
|
|
15
|
+
types: ClassVar[dict[str, RegistryType]] = {}
|
|
16
|
+
instance: ClassVar[Self | None] = None
|
|
17
|
+
|
|
18
|
+
def __new__(cls) -> Self:
|
|
19
|
+
if cls.instance is None:
|
|
20
|
+
cls.instance = super().__new__(cls)
|
|
21
|
+
|
|
22
|
+
return cls.instance
|
|
23
|
+
|
|
24
|
+
@classmethod
|
|
25
|
+
def add[O: RegistryType](cls, obj: O) -> O:
|
|
26
|
+
if (key := obj.__name__) in cls.types:
|
|
27
|
+
Logger(cls.__name__).warning(
|
|
28
|
+
f"Duplicate key in registry (overwritten): {key}"
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
cls.types[key] = obj
|
|
32
|
+
|
|
33
|
+
return obj
|
|
34
|
+
|
|
35
|
+
@classmethod
|
|
36
|
+
def get(cls, name: str) -> RegistryType:
|
|
37
|
+
return cls.types[name]
|
|
38
|
+
|
|
39
|
+
@classmethod
|
|
40
|
+
def has(cls, name: str) -> bool:
|
|
41
|
+
return name in cls.types
|
|
42
|
+
|
|
43
|
+
@classmethod
|
|
44
|
+
def names(cls) -> frozenset[str]:
|
|
45
|
+
return frozenset(cls.types.keys())
|
|
46
|
+
|
|
47
|
+
@classmethod
|
|
48
|
+
def namespace(cls) -> dict[str, RegistryType]:
|
|
49
|
+
return dict(cls.types)
|
pytex_builder/build.py
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"""``pytex`` command-line entry point.
|
|
2
|
+
|
|
3
|
+
Renders a ``.tex`` or ``.py`` input to a LaTeX file and, optionally, compiles
|
|
4
|
+
it with tectonic - running ``makeindex`` between passes so ``glossaries`` and
|
|
5
|
+
acronyms resolve.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import argparse
|
|
11
|
+
import sys
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import TYPE_CHECKING, cast
|
|
15
|
+
|
|
16
|
+
from .console import Console
|
|
17
|
+
from .render import get_tex_node
|
|
18
|
+
from .tectonic import BuildError, ensure_tectonic, run_makeindex, run_tectonic
|
|
19
|
+
|
|
20
|
+
__all__ = ["Config", "main"]
|
|
21
|
+
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
from pytex_hsrtreport.document import HSRTReport
|
|
24
|
+
|
|
25
|
+
MAX_PASSES = 3
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass(frozen=True)
|
|
29
|
+
class Config:
|
|
30
|
+
input: Path
|
|
31
|
+
output: Path
|
|
32
|
+
build: bool
|
|
33
|
+
build_dir: Path
|
|
34
|
+
shell_escape: bool
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _default_output(inp: Path, build_dir: Path) -> Path:
|
|
38
|
+
"""Default rendered-output path inside the build directory.
|
|
39
|
+
|
|
40
|
+
The driver extension is dropped, plus a trailing ``.tex`` if the source is
|
|
41
|
+
named after its target (the ``name.tex.py`` convention). The result lives in
|
|
42
|
+
``build_dir`` so the ``.out.tex`` and its inline assets (fonts, logos,
|
|
43
|
+
images) stay out of the source tree:
|
|
44
|
+
|
|
45
|
+
* ``example.tex.py`` -> ``<build_dir>/example.out.tex``
|
|
46
|
+
* ``report.py`` -> ``<build_dir>/report.out.tex``
|
|
47
|
+
* ``paper.tex`` -> ``<build_dir>/paper.out.tex``
|
|
48
|
+
"""
|
|
49
|
+
base = inp
|
|
50
|
+
if base.suffix.lower() in {".py", ".tex"}:
|
|
51
|
+
base = base.with_suffix("")
|
|
52
|
+
if base.suffix.lower() == ".tex":
|
|
53
|
+
base = base.with_suffix("")
|
|
54
|
+
return build_dir / f"{base.name}.out.tex"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _parse_args(argv: list[str]) -> Config:
|
|
58
|
+
parser = argparse.ArgumentParser(
|
|
59
|
+
prog="pytex",
|
|
60
|
+
description="Render PyTeX (.py) or LaTeX (.tex) sources and build PDFs.",
|
|
61
|
+
)
|
|
62
|
+
_ = parser.add_argument(
|
|
63
|
+
"input",
|
|
64
|
+
type=Path,
|
|
65
|
+
help=(
|
|
66
|
+
"input file: a .tex file (wrapped in IncludeTeX) or a .py file"
|
|
67
|
+
+ " exposing a '__pytex__' TeX node"
|
|
68
|
+
),
|
|
69
|
+
)
|
|
70
|
+
_ = parser.add_argument(
|
|
71
|
+
"-o",
|
|
72
|
+
"--output",
|
|
73
|
+
type=Path,
|
|
74
|
+
default=None,
|
|
75
|
+
help="rendered .tex output path (default: <build-dir>/<input>.out.tex)",
|
|
76
|
+
)
|
|
77
|
+
_ = parser.add_argument(
|
|
78
|
+
"-b",
|
|
79
|
+
"--build",
|
|
80
|
+
action="store_true",
|
|
81
|
+
help="compile the rendered .tex to PDF with tectonic",
|
|
82
|
+
)
|
|
83
|
+
_ = parser.add_argument(
|
|
84
|
+
"--build-dir",
|
|
85
|
+
type=Path,
|
|
86
|
+
default=Path("build"),
|
|
87
|
+
help="directory for build artifacts and tectonic output (default: build)",
|
|
88
|
+
)
|
|
89
|
+
_ = parser.add_argument(
|
|
90
|
+
"--no-shell-escape",
|
|
91
|
+
dest="shell_escape",
|
|
92
|
+
action="store_false",
|
|
93
|
+
help="disable shell-escape (on by default; needed for inline images)",
|
|
94
|
+
)
|
|
95
|
+
ns = parser.parse_args(argv)
|
|
96
|
+
inp = cast("Path", ns.input)
|
|
97
|
+
build_dir = cast("Path", ns.build_dir)
|
|
98
|
+
return Config(
|
|
99
|
+
input=inp,
|
|
100
|
+
output=cast("Path | None", ns.output) or _default_output(inp, build_dir),
|
|
101
|
+
build=cast("bool", ns.build),
|
|
102
|
+
build_dir=build_dir,
|
|
103
|
+
shell_escape=cast("bool", ns.shell_escape),
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _run(cfg: Config, console: Console) -> None:
|
|
108
|
+
if not cfg.input.exists():
|
|
109
|
+
raise BuildError(f"input file does not exist: {cfg.input}")
|
|
110
|
+
|
|
111
|
+
output = cfg.output
|
|
112
|
+
build_dir = cfg.build_dir
|
|
113
|
+
|
|
114
|
+
console.step(f"Rendering {cfg.input.name}")
|
|
115
|
+
tex_node = get_tex_node(cfg.input)
|
|
116
|
+
source = tex_node.rendered
|
|
117
|
+
output.parent.mkdir(parents=True, exist_ok=True)
|
|
118
|
+
_ = output.write_text(source)
|
|
119
|
+
console.detail(f"wrote {output} ({len(source):,} bytes)")
|
|
120
|
+
|
|
121
|
+
if not cfg.build:
|
|
122
|
+
return
|
|
123
|
+
|
|
124
|
+
build_dir.mkdir(parents=True, exist_ok=True)
|
|
125
|
+
|
|
126
|
+
# Materialise inline assets (fonts, logos, images) alongside the .tex (in
|
|
127
|
+
# the build dir by default) so the TeX engine can locate them by the
|
|
128
|
+
# relative paths in the preamble.
|
|
129
|
+
if hasattr(tex_node, "write_inline_fonts"):
|
|
130
|
+
cast("HSRTReport", tex_node).write_inline_fonts(str(output.parent))
|
|
131
|
+
if hasattr(tex_node, "write_inline_logos"):
|
|
132
|
+
cast("HSRTReport", tex_node).write_inline_logos(str(output.parent))
|
|
133
|
+
if hasattr(tex_node, "write_inline_images"):
|
|
134
|
+
cast("HSRTReport", tex_node).write_inline_images(str(output.parent))
|
|
135
|
+
|
|
136
|
+
binary = ensure_tectonic(console)
|
|
137
|
+
|
|
138
|
+
job = output.stem
|
|
139
|
+
for pass_no in range(1, MAX_PASSES + 1):
|
|
140
|
+
console.step(f"Compiling (pass {pass_no})")
|
|
141
|
+
run_tectonic(
|
|
142
|
+
binary, output, build_dir, shell_escape=cfg.shell_escape, console=console
|
|
143
|
+
)
|
|
144
|
+
# Resolve glossaries after the first pass; rerun only if it changed.
|
|
145
|
+
if pass_no == 1 and run_makeindex(job, build_dir, console=console):
|
|
146
|
+
continue
|
|
147
|
+
break
|
|
148
|
+
|
|
149
|
+
pdf = build_dir / f"{job}.pdf"
|
|
150
|
+
if pdf.exists():
|
|
151
|
+
console.success(f"Built {pdf}")
|
|
152
|
+
else:
|
|
153
|
+
console.warn("tectonic reported success but produced no PDF")
|
|
154
|
+
console.hint(f"check the log in {build_dir}")
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def main(argv: list[str] | None = None) -> int:
|
|
158
|
+
cfg = _parse_args(sys.argv[1:] if argv is None else argv)
|
|
159
|
+
console = Console()
|
|
160
|
+
try:
|
|
161
|
+
_run(cfg, console)
|
|
162
|
+
except BuildError as exc:
|
|
163
|
+
head, *rest = str(exc).splitlines() or [""]
|
|
164
|
+
console.error(head)
|
|
165
|
+
for line in rest:
|
|
166
|
+
console.detail(line)
|
|
167
|
+
return 1
|
|
168
|
+
except KeyboardInterrupt:
|
|
169
|
+
console.error("interrupted")
|
|
170
|
+
return 130
|
|
171
|
+
return 0
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
if __name__ == "__main__":
|
|
175
|
+
raise SystemExit(main())
|
pytex_builder/console.py
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""Minimal, tectonic-style terminal output.
|
|
2
|
+
|
|
3
|
+
No emojis, no progress bars - just level-tagged lines with restrained color.
|
|
4
|
+
Color is disabled automatically when stderr is not a TTY or ``NO_COLOR`` is set.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
import sys
|
|
11
|
+
from typing import Final, TextIO
|
|
12
|
+
|
|
13
|
+
__all__ = ["Console"]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _color_enabled(stream: TextIO) -> bool:
|
|
17
|
+
if os.environ.get("NO_COLOR"):
|
|
18
|
+
return False
|
|
19
|
+
if os.environ.get("PYTEX_FORCE_COLOR"):
|
|
20
|
+
return True
|
|
21
|
+
return bool(getattr(stream, "isatty", lambda: False)())
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class _Style:
|
|
25
|
+
RESET: Final = "\033[0m"
|
|
26
|
+
BOLD: Final = "\033[1m"
|
|
27
|
+
DIM: Final = "\033[2m"
|
|
28
|
+
RED: Final = "\033[31m"
|
|
29
|
+
GREEN: Final = "\033[32m"
|
|
30
|
+
YELLOW: Final = "\033[33m"
|
|
31
|
+
BLUE: Final = "\033[34m"
|
|
32
|
+
CYAN: Final = "\033[36m"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class Console:
|
|
36
|
+
"""Writes level-tagged status lines to ``stderr``."""
|
|
37
|
+
|
|
38
|
+
def __init__(self, stream: TextIO | None = None) -> None:
|
|
39
|
+
self.stream: TextIO = stream or sys.stderr
|
|
40
|
+
self.color: bool = _color_enabled(self.stream)
|
|
41
|
+
|
|
42
|
+
def _paint(self, text: str, *codes: str) -> str:
|
|
43
|
+
if not self.color or not codes:
|
|
44
|
+
return text
|
|
45
|
+
return f"{''.join(codes)}{text}{_Style.RESET}"
|
|
46
|
+
|
|
47
|
+
def _emit(self, tag: str, message: str, *codes: str) -> None:
|
|
48
|
+
prefix = self._paint(tag, _Style.BOLD, *codes)
|
|
49
|
+
self.stream.write(f"{prefix} {message}\n")
|
|
50
|
+
self.stream.flush()
|
|
51
|
+
|
|
52
|
+
def step(self, message: str) -> None:
|
|
53
|
+
"""A high-level pipeline stage (e.g. 'Rendering', 'Compiling')."""
|
|
54
|
+
self._emit("==>", message, _Style.GREEN)
|
|
55
|
+
|
|
56
|
+
def note(self, message: str) -> None:
|
|
57
|
+
self._emit("note:", message, _Style.CYAN)
|
|
58
|
+
|
|
59
|
+
def warn(self, message: str) -> None:
|
|
60
|
+
self._emit("warning:", message, _Style.YELLOW)
|
|
61
|
+
|
|
62
|
+
def error(self, message: str) -> None:
|
|
63
|
+
self._emit("error:", message, _Style.RED)
|
|
64
|
+
|
|
65
|
+
def hint(self, message: str) -> None:
|
|
66
|
+
"""A follow-up suggestion attached to a preceding warning/error."""
|
|
67
|
+
bullet = self._paint(" cause:", _Style.DIM)
|
|
68
|
+
self.stream.write(f"{bullet} {message}\n")
|
|
69
|
+
self.stream.flush()
|
|
70
|
+
|
|
71
|
+
def detail(self, message: str) -> None:
|
|
72
|
+
"""An indented, dimmed continuation line."""
|
|
73
|
+
self.stream.write(f"{self._paint(' ' + message, _Style.DIM)}\n")
|
|
74
|
+
self.stream.flush()
|
|
75
|
+
|
|
76
|
+
def success(self, message: str) -> None:
|
|
77
|
+
self._emit("==>", message, _Style.GREEN)
|
pytex_builder/render.py
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""Turn an input file into a rendered LaTeX string.
|
|
2
|
+
|
|
3
|
+
Two input kinds are supported:
|
|
4
|
+
|
|
5
|
+
* ``.tex`` - wrapped in :func:`pytex.model.include.IncludeTeX` so ``\\iffalse``
|
|
6
|
+
``pytex(...)`` replacements are evaluated, then rendered.
|
|
7
|
+
* ``.py`` - imported as a module; its module-level ``__pytex__`` value is
|
|
8
|
+
rendered. It must implement the :class:`pytex.interface.tex.TeX` protocol.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import importlib.util
|
|
14
|
+
import sys
|
|
15
|
+
from typing import TYPE_CHECKING, cast
|
|
16
|
+
|
|
17
|
+
from pytex.interface.tex import TeX
|
|
18
|
+
from pytex.model.include import IncludeTeX
|
|
19
|
+
|
|
20
|
+
from .tectonic import BuildError
|
|
21
|
+
|
|
22
|
+
__all__ = ["get_tex_node", "render_input"]
|
|
23
|
+
|
|
24
|
+
if TYPE_CHECKING:
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
|
|
27
|
+
PYTEX_VAR = "__pytex__"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _render_python(path: Path) -> TeX:
|
|
31
|
+
spec = importlib.util.spec_from_file_location(path.stem, path)
|
|
32
|
+
if spec is None or spec.loader is None:
|
|
33
|
+
raise BuildError(f"could not load Python module from {path}")
|
|
34
|
+
|
|
35
|
+
module = importlib.util.module_from_spec(spec)
|
|
36
|
+
# Let the module import siblings relative to its own directory.
|
|
37
|
+
sys.path.insert(0, str(path.resolve().parent))
|
|
38
|
+
try:
|
|
39
|
+
spec.loader.exec_module(module)
|
|
40
|
+
except Exception as exc:
|
|
41
|
+
raise BuildError(f"error while importing {path.name}: {exc}") from exc
|
|
42
|
+
finally:
|
|
43
|
+
sys.path.pop(0)
|
|
44
|
+
|
|
45
|
+
if not hasattr(module, PYTEX_VAR):
|
|
46
|
+
raise BuildError(f"{path.name} defines no '{PYTEX_VAR}' variable to render")
|
|
47
|
+
|
|
48
|
+
value = cast("object", getattr(module, PYTEX_VAR))
|
|
49
|
+
if not isinstance(value, TeX):
|
|
50
|
+
raise BuildError(
|
|
51
|
+
f"'{PYTEX_VAR}' in {path.name} is {type(value).__name__};"
|
|
52
|
+
+ " expected a TeX node (with a '.rendered' property)"
|
|
53
|
+
)
|
|
54
|
+
return value
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _render_markdown(path: Path) -> TeX:
|
|
58
|
+
# Imported lazily so plain .tex/.py builds need neither marko nor hsrt.
|
|
59
|
+
from pytex.model.document import Document
|
|
60
|
+
from pytex_markdown import IncludeMarkdown
|
|
61
|
+
|
|
62
|
+
return Document(IncludeMarkdown(path))
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def get_tex_node(path: Path) -> TeX:
|
|
66
|
+
"""Load ``path`` and return the TeX node without rendering."""
|
|
67
|
+
suffix = path.suffix.lower()
|
|
68
|
+
if suffix == ".tex":
|
|
69
|
+
return IncludeTeX(path)
|
|
70
|
+
if suffix == ".py":
|
|
71
|
+
return _render_python(path)
|
|
72
|
+
if suffix in (".md", ".markdown"):
|
|
73
|
+
return _render_markdown(path)
|
|
74
|
+
raise BuildError(
|
|
75
|
+
f"unsupported input type '{suffix or path.name}'; "
|
|
76
|
+
+ "expected .tex, .py or .md"
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def render_input(path: Path) -> str:
|
|
81
|
+
"""Render ``path`` to a LaTeX source string."""
|
|
82
|
+
return get_tex_node(path).rendered
|