Usage#
Install Wenmode, render Markdown, inspect the AST, choose renderers, use the CLI, and stream HTML chunks.
Install#
Install Wenmode from PyPI with your preferred Python package manager.
pip install wenmode
uv add wenmode
Render Markdown#
Wenmode is the main convenience API. It owns a Parser and a renderer, and
uses the commonmark preset with HTMLRenderer when no options are provided.
from wenmode import Wenmode
wen = Wenmode()
text = '''
# Hello
This is **wenmode**.
'''
expected = '''
<h1>Hello</h1>
<p>This is <strong>wenmode</strong>.</p>
'''
html = wen.render(text)
assert html == expected.lstrip()
render() parses the source and renders the resulting syntax tree. The source
can be a string, a synchronous text stream, or another iterable of lines.
from wenmode import Wenmode
wen = Wenmode()
with open('README.md', encoding='utf-8') as file:
html = wen.render(file)
Core objects#
Use these objects for the following tasks:
Object |
Use it when |
|---|---|
|
Parse Markdown and render output with one object. |
|
Parse Markdown when your code will render, transform, or store the AST. |
|
Render parsed nodes in a specific output format. |
Wenmode() defaults to the commonmark preset and HTMLRenderer(). Pass a
different preset, rule list, or renderer when your application needs different
syntax or output. Set positions=True when your code needs source ranges.
Parse to AST#
Use parse() to return the AST instead of rendered output.
from wenmode import Wenmode
wen = Wenmode()
text = 'A [link](https://example.com).'
tree = wen.parse(text)
ast = tree.to_ast()
assert ast == {
'type': 'root',
'children': [
{
'type': 'paragraph',
'children': [
{'type': 'text', 'value': 'A '},
{
'type': 'link',
'children': [{'type': 'text', 'value': 'link'}],
'url': 'https://example.com',
},
{'type': 'text', 'value': '.'},
],
}
],
}
The returned root node is a wenmode.nodes.Root. Nodes are data objects; their
rendering behavior lives in renderers.
Source positions#
Set positions=True to include source ranges for editor integration, diagnostics,
or AST-based tooling. Positions are opt-in. This keeps the default AST shape and
parser overhead small.
from wenmode import Wenmode
wen = Wenmode(positions=True)
ast = wen.parse('A **bold**.\n').to_ast()
assert ast['children'][0]['children'][1] == {
'type': 'strong',
'position': {
'start': {'line': 1, 'column': 3, 'offset': 2},
'end': {'line': 1, 'column': 11, 'offset': 10},
},
'children': [
{
'type': 'text',
'position': {
'start': {'line': 1, 'column': 5, 'offset': 4},
'end': {'line': 1, 'column': 9, 'offset': 8},
},
'value': 'bold',
}
],
}
The same option is available on Parser(commonmark, positions=True) when you
use parser and renderer objects separately.
Parsed nodes store source ranges as 0-based offsets. Root.to_ast() converts
those offsets to the line and column fields shown above. If you call
to_ast() on a standalone node, such as a node yielded by Parser.parse_iter(),
the position object contains offsets only because there is no document root to
provide line-start context.
Enable positions only for tooling that needs source ranges. Leave them disabled for ordinary HTML rendering.
Incremental parsing#
Use Parser.parse_iter() to return parsed top-level blocks instead of HTML
chunks. Like Wenmode.stream(), it requires a streaming-compatible rule set. It
is useful with a line iterator for large files or uploads.
Wenmode.supports_streaming and Wenmode.streaming_blockers() report parser
and renderer compatibility before you call stream() or parse_iter(). For
streaming internals and custom rule constraints, see Internals.
Rendering#
Wenmode() uses HTMLRenderer by default. Pass a different renderer when you
want another output format.
from wenmode import AsciiDocRenderer, Wenmode
wen = Wenmode(renderer=AsciiDocRenderer())
text = '# Hello'
expected = '= Hello\n'
asciidoc = wen.render(text)
assert asciidoc == expected
Wenmode currently provides:
HTMLRenderer, for HTML output.MarkdownRenderer, for serializing the AST back to Markdown.RSTRenderer, for serializing the AST to reStructuredText.AsciiDocRenderer, for serializing the AST to AsciiDoc.BaseRenderer, a small dispatch-based base class for custom renderers.
MarkdownRenderer, RSTRenderer, and AsciiDocRenderer serialize the AST to
canonical markup. They are not source-preserving formatters; syntax details that
are not represented in the AST may be normalized or omitted.
If you already have a node, use render_node() to render it directly.
from wenmode import Wenmode
wen = Wenmode()
text = '# Hello'
root = wen.parse(text)
html = wen.render_node(root)
Parser and renderer separately#
Use Parser directly when you want parsing and rendering to be separate steps.
from wenmode import HTMLRenderer, Parser
from wenmode.presets import commonmark
parser = Parser(commonmark)
text = '# Hello'
tree = parser.parse(text)
html = HTMLRenderer().render(tree)
Parser state is created per parse. Reference definitions, footnote definitions, and abbreviation definitions do not leak between calls, so a parser instance can be reused safely.
Use this split form when different application layers parse and render the document, or when your code must transform the AST before rendering.
Streaming output#
Use the streaming preset to return HTML chunks before the complete document is
parsed and rendered.
from wenmode import Wenmode
from wenmode.presets import streaming
wen = Wenmode(streaming)
text = '''
# Hello
A [link](/url).
'''
sent_chunks: list[str] = []
for chunk in wen.stream(text):
sent_chunks.append(chunk)
expected = '''
<h1>Hello</h1>
<p>A <a href="/url">link</a>.</p>
'''
assert ''.join(sent_chunks) == expected.lstrip()
The streaming API yields rendered block output as parsing progresses. The
streaming preset keeps streaming-compatible tables, strikethrough, direct
links, and direct images enabled. It disables reference-style links and images,
footnotes, and other deferred document-wide transforms. If unsupported rules are
enabled, stream() raises StreamingUnsupportedError.
Check custom configurations before streaming:
from wenmode import Wenmode
from wenmode.presets import commonmark, streaming
wen = Wenmode(streaming)
assert wen.supports_streaming is True
assert wen.streaming_blockers() == []
wen = Wenmode(commonmark)
assert wen.supports_streaming is False
assert wen.streaming_blockers() == ['reference']
Wenmode.stream() returns a synchronous iterator of HTML chunks. Web frameworks
that accept iterable response bodies can send those chunks directly.
For framework response patterns and reusable application setup, see Integrations.
Command line#
Installing Wenmode exposes the wenmode command. The same CLI is available
through python -m wenmode, and uvx can run it without adding Wenmode to the
current project.
Render a Markdown file to HTML:
wenmode render README.md --preset=github
Run the same command without a permanent install:
uvx wenmode render README.md --preset=github
Read from stdin by omitting the source path or passing -. CLI output goes to
stdout unless you pass -o:
printf '# Hello\n' | wenmode render --preset=github
wenmode render README.md --format=rst -o README.rst
wenmode render README.md --format=asciidoc -o README.adoc
Use ast when you want JSON output for tooling, tests, or editor integrations:
wenmode ast README.md --preset=github --positions
python -m wenmode ast README.md --indent=2
The CLI supports the built-in presets: commonmark, github, and streaming.
It defaults to commonmark.
wenmode render README.md --preset=commonmark
wenmode render README.md --preset=github
Enable built-in plugins with --plugin. Repeat the option to enable multiple
plugins.
wenmode render notes.md --plugin=frontmatter --plugin=inline_math
HTML output uses the same safety defaults as HTMLRenderer(): raw HTML nodes
are escaped, and unsafe link or image URLs are sanitized. Use --unsafe-html or
--unsafe-urls only for trusted content or content sanitized by another layer.
wenmode render trusted.md --unsafe-html --unsafe-urls
Next steps#
Goal |
Next page |
|---|---|
Choose CommonMark, GFM, streaming, or a custom rule list |
|
Add built-in syntax such as math, smart punctuation, or directives |
|
Render untrusted user content safely |
|
Copy patterns for TOCs, heading IDs, AST JSON, or custom renderers |
|
Wire Wenmode into a larger application |