mistletoe

repository·master·Indexed 22 days ago

https://github.com/miyuchina/mistletoe

A fast, spec-compliant, and highly customizable Markdown parser written in pure Python. It parses Markdown into an Abstract Syntax Tree (AST), allowing users to swap renderers for output formats such as HTML, LaTeX, or custom formats. It includes a CLI for transpiling files and an interactive mode, as well as support for custom SpanTokens and Renderers.

Tokens
3.5K
Snippets
17
Records
20
Agent score
75%

What's inside mistletoe

  1. Understanding mistletoe performance vs mistune

    master

    When choosing between mistletoe and mistune, consider the following trade-offs:

    • mistletoe: Focuses on strict CommonMark compliance and extensibility. It uses a highly context-sensitive grammar to correctly handle complex Markdown cases (e.g., precedence of tokens like code spans vs emphasis). This contextual analysis introduces a performance penalty compared to simpler parsers.
    • mistune: Focuses on raw speed. It uses a simpler lexing and parsing process that avoids heavy contextual checks, making it faster but potentially unable to handle complex edge cases or specific token precedence rules correctly.

    Summary: Use mistune for maximum speed where spec-compliance is secondary. Use mistletoe when you need a spec-compliant, extensible parser that handles complex Markdown structures correctly.

  2. How to manually parse and render with a custom renderer

    master

    For more control, you can manually create a Document (the AST) and use a renderer.

    Important: The parsing phase is tightly connected to the lifecycle of the renderer. You should always call Document(...) inside a with ... as renderer block to ensure that internal token lists are correctly managed and reset.

    from mistletoe import Document, HtmlRenderer
    
    with open('foo.md', 'r') as fin:
        with HtmlRenderer() as renderer:     # or: `with HtmlRenderer(AnotherToken1, AnotherToken2) as renderer:`
            doc = Document(fin)              # parse the lines into AST
            rendered = renderer.render(doc)  # render the AST
  3. Understanding the AST and tokens

    master

    When mistletoe parses Markdown, it produces an Abstract Syntax Tree (AST) stored in a Document instance. The AST is a hierarchy of tokens.

    • Block tokens: Represent lines or blocks of lines (e.g., Document, List, Paragraph, ThematicBreak). They can contain other block tokens, span tokens, or no children.
    • Span tokens (or inline tokens): Represent content within a block (e.g., RawText, Link, Emphasis). They can only have span tokens as children.

    Every token has children and parent properties for traversing the hierarchy.

  4. View the AST using AstRenderer

    master

    To inspect the parsed structure of a Markdown file, use the AstRenderer. This outputs the AST as a JSON object, showing token types, attributes (like level for headings or target for links), and line_number for block tokens.

    mistletoe text.md --renderer mistletoe.ast_renderer.AstRenderer
  5. Parse and render Markdown to Markdown

    master

    To transform Markdown while preserving structure (e.g., replacing text only in paragraphs but not in code blocks), follow this workflow:

    1. Parse the Markdown into a Document AST.
    2. Traverse and modify the AST nodes manually.
    3. Use MarkdownRenderer to convert the modified AST back to Markdown.

    You can also use max_line_length in the MarkdownRenderer constructor to reflow text while preserving code block formatting.

    import mistletoe
    from mistletoe.markdown_renderer import MarkdownRenderer
    from mistletoe.span_token import RawText, InlineCode
    
    def update_text(token):
        # Only modify RawText, skip InlineCode
        if isinstance(token, RawText):
            token.content = token.content.replace("old", "new")
        if token.children:
            for child in token.children:
                update_text(child)
    
    with open("input.md", "r") as fin:
        with MarkdownRenderer() as renderer:
            doc = mistletoe.Document(fin)
            # ... logic to traverse doc and call update_text ...
            print(renderer.render(doc))
  6. Install mistletoe in editable mode

    master

    To install mistletoe from a local clone in editable mode (so changes to the source are immediately reflected), use the following commands:

    git clone https://github.com/miyuchina/mistletoe.git
    cd mistletoe
    pip3 install -e .
  7. Run mistletoe benchmarks

    master

    You can benchmark mistletoe against other Markdown parsers (like markdown, mistune, and commonmark) by running the provided benchmark script. This script outputs the time taken in seconds for a specific test document and number of iterations.

    To run the benchmarks, use the following command from the repository root:

    $ python test/benchmark.py
  8. Improve mistletoe performance with PyPy

    master

    Because mistletoe splits functionality into modules, function lookup overhead can impact performance compared to single-module parsers. To mitigate this and boost performance, it is recommended to run mistletoe using the PyPy interpreter instead of standard CPython.

    Benchmark results indicate that mistletoe performs significantly better on PyPy. You can run the benchmark specifically comparing mistune and mistletoe using PyPy with the following command:

    $ pypy3 test/benchmark.py mistune mistletoe
  9. Render Markdown to LaTeX

    master

    To produce LaTeX output, pass the LaTeXRenderer class as the renderer argument to mistletoe.markdown().

    import mistletoe
    from mistletoe.latex_renderer import LaTeXRenderer
    
    with open('foo.md', 'r') as fin:
        rendered = mistletoe.markdown(fin, LaTeXRenderer)
  10. Reflow Markdown text with MarkdownRenderer

    master

    Use MarkdownRenderer to reflow text with a specific maximum line length. Note that MarkdownRenderer should be used as a context manager.

    import mistletoe
    from mistletoe.markdown_renderer import MarkdownRenderer
    
    with open('dev-guide.md', 'r') as fin:
        with MarkdownRenderer(max_line_length=20) as renderer:
            print(renderer.render(mistletoe.Document(fin)))
  11. Create a custom SpanToken

    master

    To add a new inline (span-level) token, subclass SpanToken.

    1. Define a pattern: Set a pattern class variable containing a compiled regular expression. The tokenizer uses this to find occurrences.
    2. Initialize with match: The constructor __init__ receives a regex match object. Use this to extract data (e.g., match_obj.group(2)).
    3. Handle children:
      • Set parse_inner = True (default) to allow child tokens within the match.
      • Set parse_group (default 1) to specify which regex group contains the children.
    4. Precedence: Use the precedence class variable to control how the token competes with others. Higher values take priority.

    Example for a GitHub-style wiki link [[alt|target]]:

    import re
    from mistletoe.span_token import SpanToken
    
    class GithubWiki(SpanToken):
        pattern = re.compile(r"\[\[ *(.+?) *\| *(.+?) *\]\]")
        
        def __init__(self, match_obj):
            # group(2) is the target
            self.target = match_obj.group(2)
    
        # Optional: increase precedence if it should override code/links
        # precedence = 6