Integrations

Build application-level Markdown pipelines with a reusable Wenmode instance, safe user-content defaults, generated heading IDs, table-of-contents output, AST export, and streaming previews.


Use this page when you are wiring Wenmode into an application rather than trying one API call. The examples keep parser setup, rendering policy, and post-processing in one place so the behavior is easy to test.

Reuse one configured instance

Create a small service object around the rule set your product supports. A Wenmode instance can be reused across calls; render state is created per render operation.

from wenmode import Wenmode
from wenmode.presets import github


class MarkdownService:
    def __init__(self, wenmode: Wenmode | None = None) -> None:
        self.wenmode = wenmode if wenmode is not None else Wenmode(github)

    def render_comment(self, text: str) -> str:
        return self.wenmode.render(text)


service = MarkdownService()
text = '''
- [x] ship docs

Visit https://example.com
'''

html = service.render_comment(text)

assert '<input checked="" disabled="" type="checkbox">' in html
assert '<a href="https://example.com">https://example.com</a>' in html

Render untrusted user content

For comments, profile fields, forum posts, and other user-authored Markdown, start with the default HTML renderer. It escapes raw HTML and removes unsafe link targets.

from wenmode import Wenmode
from wenmode.presets import github

wenmode = Wenmode(github)
text = '''
Hello <script>alert(1)</script>.

[bad](javascript:alert(1))
'''

html = wenmode.render(text)

assert '&lt;script>alert(1)&lt;/script>' in html
assert '<a>bad</a>' in html
assert 'javascript:alert' not in html

If your application also wants raw HTML syntax to stay as plain text in the AST, remove the raw HTML parser rules as shown in Recipes.

Publish documentation pages

Documentation sites often need the rendered body, a table of contents, and a machine-readable AST for search or indexing. Parse once, then run tree transforms before rendering.

import json

from wenmode import HTMLRenderer, Wenmode
from wenmode.headings import Slugger, add_heading_ids
from wenmode.toc import collect_toc, render_toc_html


class RenderedPage:
    def __init__(self, html: str, toc_html: str, ast_json: str) -> None:
        self.html = html
        self.toc_html = toc_html
        self.ast_json = ast_json


def render_page(source: str) -> RenderedPage:
    root = Wenmode().parse(source)
    add_heading_ids(root, slugger=Slugger(), min_depth=2)

    toc = collect_toc(root, min_depth=2, max_depth=3)
    toc_html = render_toc_html(toc)
    body_html = HTMLRenderer().render(root)

    return RenderedPage(
        html=toc_html + body_html,
        toc_html=toc_html,
        ast_json=json.dumps(root.to_ast(), ensure_ascii=False),
    )


text = '''
# Guide

## Install

Run **wenmode**.
'''

page = render_page(text)

assert '<a href="#install">Install</a>' in page.toc_html
assert '<h2 id="install">Install</h2>' in page.html
assert '"type": "root"' in page.ast_json

Stream low-latency previews

Use the streaming preset for live previews, chat responses, or other views that should emit HTML chunks before the whole document is available. Keep reference-style links, footnotes, and other deferred features out of this path.

from collections.abc import Iterable

from wenmode import Wenmode
from wenmode.presets import streaming

preview = Wenmode(streaming)


def render_preview(lines: Iterable[str]) -> Iterable[str]:
    yield '<article class="preview">\n'
    yield from preview.stream(lines)
    yield '</article>\n'


chunks = list(
    render_preview(
        [
            '# Preview\n',
            '\n',
            'A [link](https://example.com).\n',
        ]
    )
)
html = ''.join(chunks)

assert html.startswith('<article class="preview">')
assert '<h1>Preview</h1>' in html
assert '<a href="https://example.com">link</a>' in html

See Rule matrix for rules that are not compatible with streaming.

Package a product dialect

When multiple services need the same Markdown behavior, keep the rule list in one application module and import that module everywhere. This avoids subtle differences between the editor preview, API rendering, background jobs, and test fixtures.

from wenmode import Wenmode
from wenmode.presets import commonmark
from wenmode.rules import HtmlBlock, RawHtml

product_rules = [rule for rule in commonmark if rule not in {HtmlBlock, RawHtml}]
product_markdown = Wenmode(product_rules)

text = '<span>plain text in our dialect</span>'
expected = '''
<p>&lt;span&gt;plain text in our dialect&lt;/span&gt;</p>
'''

html = product_markdown.render(text)

assert html == expected.lstrip()

For new syntax, add a parser rule and renderer handlers together. See Custom rules for an RST-inspired example that creates a new node type and registers HTML, Markdown, and RST rendering behavior.