🐾 PET — Program Enhanced Text
Write docs once. Automate the rest. Never copy-paste again.
PET turns your documentation into a living document.
Instead of manually copying version numbers, pasting code samples, and updating
chapter headings by hand, you write a .md.pet template and let PET generate
the final Markdown. When your source changes, regenerate — done.
pet README.md.pet README.md
Not just Markdown. PET works with any text-based format — AsciiDoc, reStructuredText, LaTeX, HTML, plain text, or anything else. The template extension and output format are entirely up to you. Markdown is the most common use case, which is why the examples here use it.
1 The Idea in 30 Seconds
A .md.pet file is regular Markdown with two kinds of Python blocks:
| Syntax | Purpose |
|---|---|
{% statements %} |
Execute Python — output via print, doc |, or out() |
`{{` expr `}}` |
Evaluate an expression and write its value directly |
Template (docs.md.pet):
{%
use('number')
use('snippet')
n = number(fmt="{:3d} ")
src = snippet("src")
%}
The core loop:
{{ n(src('main_loop')) }}
Output (docs.md):
The core loop:
1 for item in queue:
2 process(item)
3 log(item)
{% ... %} blocks run arbitrary Python statements.
{{ expr }} is shorthand for single expressions — no doc | needed.
Everything else passes through untouched.
2 Installation
pip install pet-doc
Initialise a project (copies the macro library into .pet/):
cd my-project
pet init # first-time setup
pet init # safe to re-run — regenerates if macros unchanged, skips if you've edited them
pet init -f # force regeneration regardless of local edits
pet init for_claude # also installs the pet skill into Claude Code (.claude/)
Process a template once, or watch it for changes:
pet my-doc.md.pet my-doc.md # one-shot
pet watch my-doc.md.pet my-doc.md # live reload on every save
3 Built-in Macro Library
Text & Structure
| Macro | What it does |
|---|---|
chapter(header_prefix="#", sep=" ") |
Hierarchical section counter — # 1, ## 1.1, ### 1.1.1 |
number(start=1, step=1, fmt="{} ") |
Prefix each line of a text block with a sequential number |
include(filename) |
Read any file and return its content as a string |
snippet(source, exclude=None) |
Scan for named snippet / end snippet markers; source is a directory, glob pattern, or list of either; exclude accepts a filename, glob, or list |
dedent(text) |
Strip common leading indentation from included code |
Data Readers
All data macros expose get("dot.separated.path") for deep value access.
Lists support integer indices: get("dependencies.0.name").
| Macro | Format |
|---|---|
toml(file) |
TOML — great for pyproject.toml, config files |
yaml(file) |
YAML — nested keys, lists, indexed access |
json(file) |
JSON — identical API to yaml |
xml(file) |
XML — xpath queries, attribute access via attr() |
properties(file) |
Java .properties — flat key=value, dotted keys preserved |
env(file) |
.env files — strips surrounding quotes, skips comments |
Pipelines
pipe is an identity transformer. Compose steps with |:
use('pipe')
use('dedent')
use('number')
n = number(fmt="{:3d} ")
clean = pipe | dedent | n # dedent first, then add line numbers
doc | clean(include("src/core.py"))
Apply a transformation line by line with .on_lines():
shout = pipe | str.upper
doc | shout.on_lines()(include("words.txt"))
4 Worked Example — This README
This very document is generated by PET 2.0.0.
The version badge above is not hardcoded — it is read live from pyproject.toml:
1 | use('data/toml')
2 | proj = toml("pyproject.toml")
3 | VERSION = proj.get('project.version')
Inline values use {{ expr }} — the version in the footer is simply:
{{ VERSION }}
The section headings are auto-numbered by chapter:
4 | ch = chapter()
5 | # becomes -> # 1 The Idea in 30 Seconds
6 | # becomes -> # 2 Installation
7 | # etc.
Change the order, add a section, remove one — the numbers update on the next
pet README.md.pet README.md.
5 Why PET?
Documentation rots. Code evolves, but the docs that reference it often don't. Copy-pasted version numbers fall behind. Pasted code samples go stale. Chapter numbers break the moment you reorder sections.
PET's answer is deliberately small:
- No new language — your macros are plain Python classes
- No build framework — one command, one output file
- No lock-in — the output is standard Markdown; stop using PET any time
- Self-documenting — the template is the documentation intent
If information already exists somewhere in your project, humans shouldn't maintain a second copy of it. PET makes the automated copy the only copy.
For the full theory — single-source-of-truth, consistency checks as document unit tests, and why correct docs matter more than ever in the age of LLM RAG — see RATIONALE.md.
To learn how to write your own macros, with every built-in macro explained pattern by pattern, see MACROGUIDE.md.
Generated from README.md.pet by PET 2.0.0 · pet README.md.pet README.md
Metadata
Release files for pet-doc 2.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| pet_doc-2.0.0.tar.gz | 70.7 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pet_doc-2.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 102.4 kB
Release files / pet_doc-2.0.0.tar.gz
| Download URL | pet_doc-2.0.0.tar.gz |
|---|---|
| Size | 70.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
b2abf37b33f03e364b2af8cb98d3b7e8e1002fcc1b29f081e61650b2472c9718
|
|
BLAKE2b-256 checksum How to use checksums |
55fac4afd965b17685817b683e5f8c7ea8abc952d135bfc723745a86b64099aa
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.7
|
Release files / pet_doc-2.0.0-py3-none-any.whl
| Download URL | pet_doc-2.0.0-py3-none-any.whl |
|---|---|
| Size | 31.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
7fc31f0214481b48cc08cf73ed9e2e04bedc11fcc66f4d5b878a819491bdb7a6
|
|
BLAKE2b-256 checksum How to use checksums |
4b2d1ac6dc8f64cbc0d069e41918a85013c7bafcdb948e041598d5b36c2ac59f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.7
|
Security for pet-doc 2.0.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.