Metadata-Version: 2.4
Name: pytex-preprocessor
Version: 0.2.0
Summary: Type-safe LaTeX document generation with Python
Author-email: Frederik Beimgraben <frederik@beimgraben.net>
Requires-Python: >=3.13
Description-Content-Type: text/markdown
Requires-Dist: pydantic
Requires-Dist: marko
Provides-Extra: dev
Requires-Dist: pytest; extra == "dev"
Requires-Dist: ruff; extra == "dev"
Requires-Dist: basedpyright; extra == "dev"

# PyTeX

Type-safe LaTeX document generation with Python. Build a document as a tree of
typed `TeX` nodes and render it to a `.tex` file, or drop inline Python
expressions into an existing `.tex` source and have them evaluated at render
time. Requires Python 3.13+.

A `TeX` node is an immutable dataclass with a `.rendered` property. The public
API mirrors LaTeX control sequences as PascalCase factories (`Section`,
`Bold`, `Frac`, `Title`, ...), so a document reads like the LaTeX it produces
while staying checkable by a type checker. Nodes track their package
requirements, so the preamble is assembled automatically from what the body
uses.

## Install

To use the `pytex` command anywhere, install it as an isolated tool with
[pipx](https://pipx.pypa.io/):

```sh
pipx install pytex-preprocessor
```

It is also available via plain `pip install pytex-preprocessor`.

For development, work in a virtualenv with an editable install instead:

```sh
python -m venv venv && . venv/bin/activate
pip install -e .            # add [dev] for pytest, ruff, basedpyright
```

External tools, each needed only for the matching feature:

- `tectonic` — compile to PDF (`--build`). If not on `PATH`, the build
  downloads a self-contained binary into a temp folder and reuses it.
- `inkscape` — `SVG` image conversion.
- `makeindex` (from a TeX distribution, e.g. TeX Live) — resolve
  glossaries/acronyms.

## Quick start

A `.tex.py` file is plain Python exposing a module-level `__pytex__` that holds
a `TeX` node:

```py
from pytex.commands.builtin import Bold, Emph, Section, Title, MakeTitle
from pytex.model.concat import Concat
from pytex.model.document import Document
from pytex.model.math import DisplayMath, Frac

__pytex__ = Document(
    preamble=Title("PyTeX Example"),
    body=Concat(
        MakeTitle(),
        Section("Text"),
        "A paragraph with ", Bold("bold"), " and ", Emph("emphasised"), " words.",
        Section("Math"),
        DisplayMath(Concat("x = ", Frac("-b", "2a"))),
    ),
)
```

```sh
pytex example.tex.py          # render -> build/example.out.tex
pytex example.tex.py --build  # render + compile -> build/example.out.pdf
```

Bare strings are coerced to text nodes and LaTeX-escaped.

## The `pytex` command

The input file is dispatched by extension:

| Extension | Handling |
| --- | --- |
| `.py` | imported as a module; its `__pytex__` node is rendered. Convention: name it `<doc>.tex.py`. |
| `.tex` | wrapped in `IncludeTeX`; inline `\iffalse{pytex(...)}\fi` markers are evaluated, then rendered. Convention: `<doc>.py.tex`. |
| `.md` / `.markdown` | converted to nodes via `IncludeMarkdown`. Frontmatter with `gremium:` or `typ: protokoll` routes to the protocol renderer instead. |

### Inline replacements in `.tex`

Any registered factory is in scope inside a marker. The `\iffalse ... \fi` pair
is a LaTeX no-op, so the source still compiles as-is without PyTeX:

```tex
Today is \iffalse{pytex(Today())}\fi.
A fraction: $\iffalse{pytex(Frac("1", "2"))}\fi$.
Plain Python works too: $3^2 = \iffalse{pytex(3 ** 2)}\fi$.
```

### Options

| Flag | Default | Meaning |
| --- | --- | --- |
| `-o`, `--output` | `<build-dir>/<input>.out.tex` | rendered LaTeX output path |
| `-b`, `--build` | off | compile the rendered `.tex` to PDF with tectonic |
| `--build-dir DIR` | `build` | directory for artifacts and tectonic output |
| `--no-shell-escape` | shell-escape on | disable shell-escape |

Shell-escape is on by default because inline images decode their base64
payloads at compile time. The build runs tectonic, then `makeindex` (for
`glossaries`/acronyms), then reruns tectonic when an index changed.

Output is minimal and color-tagged (`==>`, `note:`, `warning:`, `error:`),
following tectonic's style; on failure it points at the likely cause and the
log file. Set `NO_COLOR` to disable color.

## Packages

`pytex` is the core; the rest are optional and build on it.

| Package | Provides |
| --- | --- |
| `pytex` | core node model, `Document`, math, tables, graphics, and factories for the common LaTeX packages (biblatex, cleveref, glossaries, hyperref, listings, ...). |
| `pytex_koma` | KOMA-Script classes and commands (`Addchap`, `Minisec`, `KOMAoptions`, ...). |
| `pytex_tikz` | TikZ pictures and primitives (`TikzPicture`, `Draw`, `Node`, `Circle`, ...). |
| `pytex_markdown` | Markdown -> native `TeX` conversion (see below). |
| `pytex_hsrtreport` | HSRT report document class, colored callout boxes, title pages, glossary/citation helpers. |
| `pytex_protocol` | STUPA/AStA meeting minutes from Markdown, built on `pytex_hsrtreport`. |

## Markdown

`pytex_markdown` converts Markdown to native `TeX` nodes (via `marko`):

```py
from pytex_markdown import Markdown, IncludeMarkdown

body = Markdown("# Title\n\nText with **bold**, `code`, [a link](https://x).")
body = IncludeMarkdown("notes.md", base_level=-1)   # base_level=-1: # -> \chapter
```

Headings, emphasis, inline/fenced code, lists, links, images, block quotes and
thematic breaks map to the standard pytex library; text is LaTeX-escaped.
GitHub-style callouts become HSRT colored boxes (so the module depends on
`pytex_hsrtreport`):

```md
> [!NOTE]      -> InfoBox        > [!IMPORTANT] -> ImportantBox
> [!TIP]       -> SuccessBox     > [!WARNING]   -> WarningBox
```

Both factories are registered, so they work in `\iffalse{pytex(...)}\fi`
replacements in `.tex` sources too.

## Examples

See `examples/` for one minimal input per kind (`.tex.py`, `.py.tex`, `.md`,
mixed, and a full HSRT report). Run from the repository root so relative paths
resolve:

```sh
pytex examples/document.tex.py --build
pytex examples/replacements.py.tex --build
pytex examples/notes.md --build
```
