Advanced Guide

Create plugins

Mistune has many built-in plugins, you can take a look at the source code in mistune/plugins to find out how to write a plugin. In this documentation, I’ll guide you with an example, let’s take a look at the math plugin (located at mistune/plugins/math.py):

def math(md):
    md.block.register('block_math', BLOCK_MATH_PATTERN, parse_block_math, before='list')
    md.inline.register('inline_math', INLINE_MATH_PATTERN, parse_inline_math, before='link')
    if md.renderer and md.renderer.NAME == 'html':
        md.renderer.register('block_math', render_block_math)
        md.renderer.register('inline_math', render_inline_math)

The parameter md is the instance of Markdown. In our example, we have registered a block level math plugin and an inline level math plugin.

Block level plugin

Function md.block.register will register a block level plugin. In the math example:

$$
\operatorname{ker} f=\{g\in G:f(g)=e_{H}\}{\mbox{.}}
$$

This is how a block level math syntax looks like. Our BLOCK_MATH_PATTERN is:

# block level pattern MUST startswith ^
BLOCK_MATH_PATTERN = r'^ {0,3}\$\$[ \t]*\n(?P<math_text>.+?)\n\$\$[ \t]*$'

# regex represents:
BLOCK_MATH_PATTERN = (
  r'^ {0,3}'  # line can startswith 0~3 spaces just like other block elements defined in commonmark
  r'\$\$'  # followed by $$
  r'[ \t]*\n'  # this line can contain extra spaces and tabs
  r'(?P<math_text>.+?)'  # this is the math content, MUST use named group
  r'\n\$\$[ \t]*$'  # endswith $$ + extra spaces and tabs
)

# if you want to make the math pattern more strictly, it could be like:
BLOCK_MATH_PATTERN = r'^\$\$\n(?P<math_text>.+?)\n\$\$$'

Then the block parsing function:

def parse_block_math(block, m, state):
    text = m.group('math_text')
    # use ``state.append_token`` to save parsed block math token
    state.append_token({'type': 'block_math', 'raw': text})
    # return the end position of parsed text
    # since python doesn't count ``$``, we have to +1
    # if the pattern is not ended with `$`, we can't +1
    return m.end() + 1

The token MUST contain type, others are optional. Here are some examples:

{'type': 'thematic_break'}  # <hr>
{'type': 'paragraph', 'text': text}
{'type': 'block_code', 'raw': code}
{'type': 'heading', 'text': text, 'attrs': {'level': level}}
  • text: inline parser will parse text

  • raw: inline parser WILL NOT parse the content

  • attrs: extra information saved here, renderer will use attrs

Inline level plugin

Function md.inline.register will register an inline level plugin. In the math example:

function $f$

This is how an inline level math syntax looks like. Our INLINE_MATH_PATTERN is:

INLINE_MATH_PATTERN = r'\$(?!\s)(?P<math_text>.+?)(?!\s)\$'

# regex represents:
INLINE_MATH_PATTERN = (
  r'\$'  # startswith $
  r'(?!\s)'  # not whitespace
  r'(?P<math_text>.+?)'  # content between `$`, MUST use named group
  r'(?!\s)'  # not whitespace
  r'\$'  # endswith $
)

Then the inline parsing function:

def parse_inline_math(inline, m, state):
    text = m.group('math_text')
    # use ``state.append_token`` to save parsed inline math token
    state.append_token({'type': 'inline_math', 'raw': text})
    # return the end position of parsed text
    return m.end()

The inline token value looks the same with block token. Available keys: type, raw, text, attrs.

Plugin renderers

It is suggested to add default HTML renderers for your plugin. A renderer function looks like:

def render_hr(renderer):
    # token with only type, like:
    # {'type': 'hr'}
    return '<hr>'

def render_math(renderer, text):
    # token with type and (text or raw), e.g.:
    # {'type': 'block_math', 'raw': 'a^b'}
    return '<div class="math">$$' + text + '$$</div>'

def render_link(renderer, text, **attrs):
    # token with type, text or raw, and attrs
    href = attrs['href']
    return f'<a href="{href}">{text}</a>'

If current markdown instance is using HTML renderer, developers can register the plugin renderer for converting markdown to HTML.

Write directives

Mistune has some built-in directives that have been presented in the directives part of the documentation. These are defined in the mistune/directives, you can learn how to write a new directive by reading the source code in mistune/directives/.