DeCatenator
decat is a small command-line tool that unpacks code embedded in Markdown.
It reads headings that look like file paths and writes the accompanying code
to real files.
- Input rule – a level-2 or level-3 heading whose text is a single path
segment (no spaces).
Examples:
## main.py,### src/utils/helpers.js. Anything with spaces (e.g.## My Heading) is ignored. - Code source
- If the heading is followed (after optional blank lines) by a fenced block
opened with triple back-ticks, those lines become the file content.
The closing fence must be exactly
```(no language tag). - Otherwise, all lines until the next matching heading (or EOF) are taken as the file content.
- If the heading is followed (after optional blank lines) by a fenced block
opened with triple back-ticks, those lines become the file content.
The closing fence must be exactly
- Creates parent directories as needed and overwrites existing files.
- Works with a file argument or
stdin.
Note: This project is the inverse of Catenator.
Quick start
pip install decat
CLI
# write files into the current directory
decat article.md
# write into a different root
decat article.md -o ./extracted
# read from stdin
cat article.md | decat -
# version
decat --version
Example – fenced
### src/hello.py
```python
print("Hello, world!")
Running `decat readme.md` generates `src/hello.py` containing:
```python
print("Hello, world!")
Example – unfenced
## scripts/build.sh
#!/usr/bin/env bash
echo "building"
decat docs.md writes scripts/build.sh with the two lines above.
Programmatic use
from pathlib import Path
from decat import extract_files, write_files
with open("notes.md", "r", encoding="utf-8") as f:
lines = f.readlines()
pairs = extract_files(lines) # yields (rel_path, code)
write_files(pairs, Path("out")) # creates ./out/...
How it works
-
Regex
^#{2,3}\s+([^\s]+)\s*$recognises valid headings. -
Searches forward:
- if the next non-blank line matches
^```.*$, capture until a bare ```; otherwise capture until the next valid heading.
- if the next non-blank line matches
-
Raises
SyntaxErrorif a fenced block is unterminated. -
All IO logic is isolated in
src/decat/_core.py; CLI lives insrc/decat/cli.py.
Repository layout
./
│ README.md
│ pyproject.toml
│ MANIFEST.in
├── src/
│ └── decat/
│ ├── _core.py
│ ├── cli.py
│ └── __init__.py
└── tests/
└── test_core.py
Development
# editable install
pip install -e .
# run the test suite
pytest
# build sdist & wheel
python -m build
# upload (requires credentials)
twine upload dist/*
Release files for decatenator 0.1.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 | |
|---|---|---|---|
| decatenator-0.1.0.tar.gz | 13.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| decatenator-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 24.4 kB
Release files / decatenator-0.1.0.tar.gz
| Download URL | decatenator-0.1.0.tar.gz |
|---|---|
| Size | 13.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
7c699575b8be28ee081dceeac45357459e5702c9bbda9c5fdb6b300f6c795992
|
|
BLAKE2b-256 checksum How to use checksums |
915130a960d119ef479adae248027d914133c28aab6408b6e25f811e34bded40
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.12.3
|
Release files / decatenator-0.1.0-py3-none-any.whl
| Download URL | decatenator-0.1.0-py3-none-any.whl |
|---|---|
| Size | 10.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
7a20b63934fee86c29bb238c82319be50645ec6e7c868c288ab4b5949fc29c26
|
|
BLAKE2b-256 checksum How to use checksums |
52511773ad0f36cc9698a0d2eba35ccf9399853f33ab57d26a2fc7b963d4f595
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.12.3
|