Skip to content

Guide

Render HTML

import pyromark

html = pyromark.html("# Hello *world*")
assert html == "<h1>Hello <em>world</em></h1>\n"

For big documents, write_html streams the HTML into a binary file instead of building one string:

import pyromark

with open("out.html", "wb") as file:
    pyromark.write_html("# Hello world", file)

Enable extensions

Everything beyond CommonMark is off by default. Combine Options flags with |:

import pyromark

html = pyromark.html("| a | b |\n| - | - |\n| 1 | 2 |", options=pyromark.Options.ENABLE_TABLES)
assert html.startswith("<table>")
Flag Enables
ENABLE_TABLES pipe tables
ENABLE_FOOTNOTES [^1] footnotes
ENABLE_OLD_FOOTNOTES footnotes, older syntax
ENABLE_STRIKETHROUGH ~~strikethrough~~
ENABLE_TASKLISTS - [x] done
ENABLE_GFM GitHub alerts, > [!NOTE]
ENABLE_MATH $inline$ and $$display$$ math
ENABLE_SMART_PUNCTUATION curly quotes, -- and --- dashes, ...
ENABLE_HEADING_ATTRIBUTES # Title { #id .class }
ENABLE_DEFINITION_LIST : definition lists
ENABLE_SUPERSCRIPT ^superscript^
ENABLE_SUBSCRIPT ~subscript~
ENABLE_WIKILINKS [[wikilinks]]
ENABLE_YAML_STYLE_METADATA_BLOCKS --- front matter
ENABLE_PLUSES_DELIMITED_METADATA_BLOCKS +++ front matter

To render like GitHub:

import pyromark

GITHUB = (
    pyromark.Options.ENABLE_TABLES
    | pyromark.Options.ENABLE_FOOTNOTES
    | pyromark.Options.ENABLE_STRIKETHROUGH
    | pyromark.Options.ENABLE_TASKLISTS
    | pyromark.Options.ENABLE_GFM
)
html = pyromark.html("- [x] ~~done~~", options=GITHUB)

Highlight code

Pick a bundled Theme. It emits inline styles, so no CSS is needed:

import pyromark

html = pyromark.html("```python\nprint('hi')\n```", highlight=pyromark.Theme.INSPIRED_GITHUB)
assert html.startswith('<pre style="background-color:#ffffff;">')

Or pass a function that gets the language and the code and returns HTML, or None to leave the block as is. Here with Pygments, installed with uv add pygments:

import pyromark
from pygments import highlight
from pygments.formatters import HtmlFormatter
from pygments.lexers import get_lexer_by_name
from pygments.util import ClassNotFound


def pygmentize(language: str | None, code: str) -> str | None:
    try:
        lexer = get_lexer_by_name(language or "")
    except ClassNotFound:
        return None
    return highlight(code, lexer, HtmlFormatter())


html = pyromark.html("```python\nprint('hi')\n```", highlight=pygmentize)
assert html.startswith('<div class="highlight">')
css = HtmlFormatter().get_style_defs(".highlight")

broken_link_callback turns [label] references that have no definition into links. Return (url, title), or None to keep the text:

import pyromark

html = pyromark.html(
    "See [Python].", broken_link_callback=lambda link: (f"/wiki/{link['reference']}", "")
)
assert html == '<p>See <a href="/wiki/Python">Python</a>.</p>\n'

reference_definitions returns the definitions a document does have:

import pyromark

definitions = pyromark.reference_definitions("[home]: https://example.com")
assert definitions["home"]["dest"] == "https://example.com"

Reuse settings

Markdown keeps options and callbacks, so you pass them once:

import pyromark

md = pyromark.Markdown(
    options=pyromark.Options.ENABLE_TABLES, highlight=pyromark.Theme.SOLARIZED_DARK
)
first = md.html("# One")
second = md.html("# Two")

Render in parallel

html and write_html release the GIL, so threads render in parallel. A Markdown can be shared between them:

from concurrent.futures import ThreadPoolExecutor

import pyromark

md = pyromark.Markdown(options=pyromark.Options.ENABLE_TABLES)
documents = ["# One", "# Two", "# Three"]
with ThreadPoolExecutor() as pool:
    pages = list(pool.map(md.html, documents))

The GIL is held while your callbacks run. Event iterators hold it on every step and can't be shared between threads.