Transforms
AST-to-AST mutations applied after parsing and before serialisation. They bake in cross-cutting behaviour without polluting the parser or renderer.
Built-in transforms
| Identifier | Class | What it does |
|---|---|---|
normalize | NormalizeText | Merge adjacent text nodes, drop empty ones. |
slugify | SlugifyHeadings | Add a stable id to every heading. |
toc | BuildTOC | Build a nested table-of-contents in doc.meta["toc"]. Requires slugify. |
linkify | Linkify | Convert bare URLs in text to link nodes. |
smarttypography | SmartTypography | Replace dashes/quotes/ellipses with typographic equivalents. |
Apply them by name when constructing a parser:
from markast import Parser
parser = Parser(transforms=["normalize", "slugify", "toc"])
doc = parser.parse("# Top\n\n## Sub\n\n# Top2")
print(doc.meta["toc"])
# [
# {"level": 1, "text": "Top", "id": "top", "children": [
# {"level": 2, "text": "Sub", "id": "sub", "children": []}
# ]},
# {"level": 1, "text": "Top2", "id": "top2", "children": []}
# ]
Order matters
Transforms run in the order you list them. toc reads ids set by slugify, so slugify must come first.
Writing a custom transform
from markast.transforms import Transform
from markast.ast import replace
from markast import NodeType
class StripDividers(Transform):
"""Remove every horizontal rule from the document."""
name = "strip-dividers"
def apply(self, doc, config):
return replace(doc, lambda n: None if n.get("type") == NodeType.DIVIDER else n)
Register it:
parser = Parser(transforms=[StripDividers])
Three things to remember: (1) receive and return a document, (2) use
markast.ast.replace for rewrites — it handles every container shape correctly, (3) use doc["meta"] to expose computed data rather than rewriting the tree to fit it.Transforms vs. rules
| Transform | Rule (markast.rules) | |
|---|---|---|
| When | After parsing, before rendering | During parsing |
| What | Mutates / annotates the AST | Observes and reports diagnostics |
| Side effect | Changes the output | Adds entries to doc.warnings |
| Use for | Slugs, TOCs, autolinks, normalisation | Cross-cutting validation, lints |
Pick a rule when you want to flag something. Pick a transform when you want to change something.