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

Wenmode

Parse Markdown and render output with one object.

Parser

Parse Markdown when your code will render, transform, or store the AST.

HTMLRenderer, MarkdownRenderer, RSTRenderer, AsciiDocRenderer

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

Presets

Add built-in syntax such as math, smart punctuation, or directives

Plugins

Render untrusted user content safely

Security

Copy patterns for TOCs, heading IDs, AST JSON, or custom renderers

Recipes

Wire Wenmode into a larger application

Integrations