Migrating from Marko#

Move Marko conversion, AST traversal, renderer overrides, and extensions to Wenmode parser rules, nodes, transforms, and renderer handlers.


Marko and Wenmode both expose parser and renderer concepts, so migration is usually straightforward at the architecture level. The main work is adapting node classes, extension hooks, and renderer methods.

Simple rendering#

Marko’s direct conversion path is:

marko#
import marko

html = marko.convert(text)

Use Wenmode:

wenmode#
from wenmode import Wenmode

html = Wenmode().render(text)

If you use a configured Marko Markdown object:

marko#
import marko

markdown = marko.Markdown(extensions=['footnote'])
html = markdown.convert(text)

Use a configured Wenmode instance:

wenmode#
from wenmode import Wenmode
from wenmode.presets import github

wen = Wenmode(github)
html = wen.render(text)

AST migration#

Marko’s AST classes are not the same as Wenmode nodes. Replace Marko-specific node checks with Wenmode node types:

marko#
import marko

markdown = marko.Markdown()
root = markdown.parse(text)
wenmode#
from wenmode import Wenmode

root = Wenmode().parse(text)

Use root.to_ast() when you need serializable dictionaries:

wenmode#
payload = root.to_ast()

Wenmode’s documented node types are listed in Reference.

Extension mapping#

Marko extensions typically define parser elements and renderer behavior. In Wenmode, split extension behavior into a plugin that can register:

  • InlineRule for inline syntax,

  • BlockRule for block syntax,

  • ContinueRule for paragraph continuations,

  • root transforms for document-wide state,

  • renderer handlers for HTML, Markdown, RST, or custom output.

If a Marko extension introduced a new AST element, create a custom Wenmode plugin with the node and renderer handlers. See Custom Plugins.

Renderer migration#

Marko renderers usually override methods on renderer classes:

marko#
from marko import Markdown
from marko.html_renderer import HTMLRenderer


class MyRenderer(HTMLRenderer):
    def render_paragraph(self, element):
        return f'<p class="lead">{self.render_children(element)}</p>\n'


markdown = Markdown(renderer=MyRenderer)
html = markdown.convert(text)

Wenmode renderers register handlers by node type:

wenmode#
from wenmode import HTMLRenderer
from wenmode.nodes import Paragraph
from wenmode.renderers import RenderContext


@HTMLRenderer.register('paragraph')
def render_paragraph(
    renderer: HTMLRenderer,
    node: Paragraph,
    context: RenderContext,
) -> str:
    body = renderer.render_children(node.children, context)
    return f'<p class="lead">{body}</p>\n'

For application-specific output, subclass BaseRenderer and register handlers on the subclass so the global HTMLRenderer behavior stays unchanged.

GFM and extras#

If your Marko setup enabled GFM-like extensions:

marko#
from marko.ext.gfm import gfm

html = gfm(text)

Start with Wenmode’s github preset:

wenmode#
from wenmode import Wenmode
from wenmode.presets import github

html = Wenmode(github).render(text)

For a smaller dialect, pass only the rule classes or configured rule instances you need.

Checklist#

  • Replace marko.convert(text) with Wenmode().render(text).

  • Replace Marko AST traversal with Wenmode node traversal or to_ast().

  • Split Marko extensions into custom plugins with rules, transforms, and renderer handlers.

  • Review renderer behavior for custom nodes.

  • Decide whether you need commonmark, github, streaming, or a custom rule list.