Recipes¶
Copy common integration patterns for GFM, tables of contents, heading IDs, custom renderers, AST JSON output, and migration planning.
This page collects common tasks that are one step beyond the quick start.
Enable GitHub-flavored Markdown¶
Use the github preset when you want tables, task list items, strikethrough,
bare URL autolinks, footnotes, and GFM disallowed HTML handling.
from wenmode import Wenmode
from wenmode.presets import github
wenmode = Wenmode(github)
text = '''
- [x] done
| A | B |
| --- | --- |
| **x** | https://example.com |
'''
html = wenmode.render(text)
assert '<input checked="" disabled="" type="checkbox">' in html
assert '<table>' in html
assert '<a href="https://example.com">https://example.com</a>' in html
Render a table of contents¶
Use heading rules with id_transform=True and register the built-in
TableOfContents directive renderer.
from wenmode import Wenmode
from wenmode.directives import TableOfContents
from wenmode.rules import AtxHeading, LeafDirective
wenmode = Wenmode(
[AtxHeading(id_transform=True), LeafDirective],
directives=[TableOfContents()],
)
text = '''
::toc{min=2 max=3}
# Title
## Usage
'''
html = wenmode.render(text)
assert '<nav aria-label="Table of contents" class="toc">' in html
assert '<a href="#usage">Usage</a>' in html
assert '<h2 id="usage">Usage</h2>' in html
You can also build a table of contents manually when you want to place or style it outside the Markdown document.
from wenmode import HTMLRenderer, Wenmode
from wenmode.headings import Slugger, add_heading_ids
from wenmode.toc import collect_toc, render_toc_html
wenmode = Wenmode()
text = '''
# Title
## Usage
'''
root = wenmode.parse(text)
add_heading_ids(root, slugger=Slugger(), min_depth=2)
toc = collect_toc(root, min_depth=2, max_depth=3)
html = render_toc_html(toc) + HTMLRenderer().render(root)
assert '<a href="#usage">Usage</a>' in html
Disable raw HTML syntax¶
The default HTML renderer escapes raw HTML output. If you also want raw HTML
syntax to stay as plain text in the AST, use a rule list without HtmlBlock and
RawHtml.
from wenmode import Wenmode
from wenmode.presets import commonmark
from wenmode.rules import HtmlBlock, RawHtml
rules = [rule for rule in commonmark if rule not in {HtmlBlock, RawHtml}]
wenmode = Wenmode(rules)
text = '<span>text</span>'
expected = '''
<p><span>text</span></p>
'''
root = wenmode.parse(text)
html = wenmode.render_node(root)
assert root.to_ast()['children'][0]['children'][0] == {
'type': 'text',
'value': '<span>text</span>',
}
assert html == expected.lstrip()
See Security for renderer-level escaping and URL sanitization behavior.
Generate heading IDs¶
Pass id_transform=True to heading rules when you want Wenmode to add generated
heading IDs during parsing.
from wenmode import Wenmode
from wenmode.rules import AtxHeading
wenmode = Wenmode([AtxHeading(id_transform=True)])
text = '# Hello World'
expected = '''
<h1 id="hello-world">Hello World</h1>
'''
html = wenmode.render(text)
assert html == expected.lstrip()
For already-parsed trees, use add_heading_ids().
from wenmode import HTMLRenderer, Wenmode
from wenmode.headings import Slugger, add_heading_ids
text = '''
# Title
## Usage
'''
root = Wenmode().parse(text)
add_heading_ids(root, slugger=Slugger(), min_depth=2)
html = HTMLRenderer().render(root)
assert '<h1>Title</h1>' in html
assert '<h2 id="usage">Usage</h2>' in html
Convert the AST to JSON¶
Node.to_ast() returns plain Python dictionaries and lists, so you can serialize
the parsed tree with the standard json module.
import json
from wenmode import Wenmode
text = 'A [link](https://example.com).'
root = Wenmode().parse(text)
payload = json.dumps(root.to_ast(), ensure_ascii=False)
assert '"type": "root"' in payload
assert '"url": "https://example.com"' in payload
Write a custom renderer¶
Renderers inherit from BaseRenderer and register handlers by node type. This
example turns Markdown into plain uppercase text.
from wenmode import Wenmode
from wenmode.nodes import Text
from wenmode.renderers import BaseRenderer, RenderContext
class UpperRenderer(BaseRenderer):
pass
@UpperRenderer.register('text')
def render_text(renderer: UpperRenderer, node: Text, context: RenderContext) -> str:
return node.value.upper()
wenmode = Wenmode(renderer=UpperRenderer())
source = 'Hello *there*'
text = wenmode.render(source)
assert text == 'HELLO THERE'
Plan a parser migration¶
For migrations from Mistune, Python-Markdown, markdown-it-py, markdown2, Marko, or commonmark.py, start with the dedicated Migration guides section. The guides cover direct render-call replacements, feature mapping, HTML safety defaults, AST migration, and custom extension migration.