Metadata-Version: 2.5
Name: wenmode
Version: 1.0.0rc1
Summary: A fast, composable Markdown parser and renderer toolkit.
Project-URL: Homepage, https://wenmode.lepture.com
Project-URL: Repository, https://github.com/lepture/wenmode
Project-URL: Issues, https://github.com/lepture/wenmode/issues
Project-URL: Documentation, https://wenmode.lepture.com
Project-URL: Donate, https://github.com/sponsors/lepture
Author-email: Hsiaoming Yang <me@lepture.com>
License-Expression: BSD-3-Clause
License-File: LICENSE
Keywords: commonmark,markdown,mdast,parser,renderer
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: BSD License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: Implementation :: PyPy
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Text Processing :: Markup
Classifier: Topic :: Text Processing :: Markup :: Markdown
Requires-Python: >=3.11
Description-Content-Type: text/x-rst

Wenmode
=======

|Build Status| |PyPI version| |Code Coverage| |Maintainability Rating| |Security Rating|

.. |Build Status| image:: https://img.shields.io/github/actions/workflow/status/lepture/wenmode/test.yml?logo=github&label=test
   :target: https://github.com/lepture/wenmode/actions
   :alt: Build Status

.. |PyPI version| image:: https://img.shields.io/pypi/v/wenmode?logo=python&logoColor=fff&labelColor=3776ab
   :target: https://pypi.org/project/wenmode
   :alt: PyPI version

.. |Code Coverage| image:: https://img.shields.io/codecov/c/github/lepture/wenmode
   :target: https://codecov.io/gh/lepture/wenmode
   :alt: Code Coverage

.. |Maintainability Rating| image:: https://sonarcloud.io/api/project_badges/measure?project=lepture_wenmode&metric=sqale_rating
   :target: https://sonarcloud.io/summary/new_code?id=lepture_wenmode
   :alt: Maintainability Rating

.. |Security Rating| image:: https://sonarcloud.io/api/project_badges/measure?project=lepture_wenmode&metric=security_rating
   :target: https://sonarcloud.io/summary/new_code?id=lepture_wenmode
   :alt: Security Rating

Wenmode is a composable Markdown toolkit for Python by the same author as
`Mistune <https://mistune.lepture.com/>`__. It is a rewrite informed by Mistune's
design, with a stronger focus on explicit rule composition, mdast-compatible AST
output, extension state, and pluggable rendering.

The top-level ``Wenmode`` class combines a parser and a renderer. By default,
``Wenmode`` parses CommonMark-style Markdown and renders HTML.

Documentation: `https://wenmode.lepture.com <https://wenmode.lepture.com>`__

Use Wenmode to:

- render Markdown to HTML with safe defaults for user-authored content,
- choose the exact Markdown rules your application accepts,
- inspect or store an mdast-compatible AST,
- build a custom Markdown dialect with parser rules and renderer handlers,
- stream HTML output from Markdown input.

Installation
------------

.. code-block:: bash

   pip install wenmode

Run the CLI without installing it permanently:

.. code-block:: bash

   uvx wenmode render --preset=github README.md
   uvx wenmode ast --preset=github README.md

After installation, use either the console script or Python module entry point:

.. code-block:: bash

   wenmode render README.md --preset=github
   python -m wenmode ast README.md --positions

Quick start
-----------

.. code-block:: python

   from wenmode import Wenmode

   wen = Wenmode()

   text = '''
   # Hello

   This is **wenmode**.
   '''
   expected = '''
   <h1>Hello</h1>
   <p>This is <strong>wenmode</strong>.</p>
   '''

   html = wen.render(text)
   assert html == expected.lstrip()

Use ``parse()`` when you need the mdast-compatible syntax tree:

.. code-block:: python

   from wenmode import Wenmode

   wen = Wenmode()
   text = 'A [link](https://example.com).'

   tree = wen.parse(text)
   ast = tree.to_ast()

   assert ast == {
       'type': 'root',
       'children': [
           {
               'type': 'paragraph',
               'children': [
                   {'type': 'text', 'value': 'A '},
                   {
                       'type': 'link',
                       'children': [{'type': 'text', 'value': 'link'}],
                       'url': 'https://example.com',
                   },
                   {'type': 'text', 'value': '.'},
               ],
           }
       ],
   }

Set ``positions=True`` to include source ranges for editor integration,
diagnostics, or AST-based tooling:

.. code-block:: python

   from wenmode import Wenmode

   wen = Wenmode(positions=True)
   ast = wen.parse('A **bold**.\n').to_ast()

   assert ast['children'][0] == {
       'type': 'paragraph',
       'position': {
           'start': {'line': 1, 'column': 1, 'offset': 0},
           'end': {'line': 2, 'column': 1, 'offset': 12}
       },
       'children': [
           {
               'type': 'text',
               'position': {
                   'start': {'line': 1, 'column': 1, 'offset': 0},
                   'end': {'line': 1, 'column': 3, 'offset': 2}
               },
               'value': 'A '
           },
           {
               'type': 'strong',
               'position': {
                   'start': {'line': 1, 'column': 3, 'offset': 2},
                   'end': {'line': 1, 'column': 11, 'offset': 10}
               },
               'children': [
                   {
                       'type': 'text',
                       'position': {
                           'start': {'line': 1, 'column': 5, 'offset': 4},
                           'end': {'line': 1, 'column': 9, 'offset': 8}
                       },
                       'value': 'bold'
                   }
               ]
           },
           {
               'type': 'text',
               'position': {
                   'start': {'line': 1, 'column': 11, 'offset': 10},
                   'end': {'line': 1, 'column': 12, 'offset': 11}
               },
               'value': '.'
           }
       ]
   }

Pass a renderer when you need reStructuredText or AsciiDoc output:

.. code-block:: python

   from wenmode import AsciiDocRenderer, Wenmode

   wen = Wenmode(renderer=AsciiDocRenderer())

   text = '# Hello'
   expected = '= Hello\n'

   asciidoc = wen.render(text)
   assert asciidoc == expected

Rules, presets, and plugins
---------------------------

Most applications start with a preset:

- ``commonmark``, the default CommonMark-style rule set,
- ``github``, for GitHub-flavored Markdown features such as tables and task
  lists,
- ``streaming``, for incremental HTML output.

Rules are opt-in and composable. ``Wenmode()`` uses the ``commonmark`` preset by
default. Pass an explicit rule list to define a custom Markdown dialect.

.. code-block:: python

   from wenmode import Wenmode
   from wenmode.rules import AtxHeading, FencedCode, Image, InlineCode, Link

   wen = Wenmode([AtxHeading, FencedCode, Link, Image, InlineCode])
   text = '''
   # h1

   hi `code` **strong**
   '''
   expected = '''
   <h1>h1</h1>
   <p>hi <code>code</code> **strong**</p>
   '''

   assert wen.render(text) == expected.lstrip()

Because ``Emphasis`` is not enabled above, ``**strong**`` stays as text.

Use ``Parser`` directly when you only need an AST and want to choose rendering
separately:

.. code-block:: python

   from wenmode import HTMLRenderer, Parser
   from wenmode.presets import commonmark

   parser = Parser(commonmark())
   text = '# Hello'

   tree = parser.parse(text)

   html = HTMLRenderer().render(tree)

Use the ``github`` preset for GitHub-flavored Markdown features such as tables,
task lists, strikethrough, extended autolinks, and footnotes:

.. code-block:: python

   from wenmode import Wenmode
   from wenmode.presets import github

   wen = Wenmode(github())

Use built-in plugins for non-standard syntax, document metadata, and rendering
behavior such as front matter, math, definition lists, abbreviations, spoilers,
ruby text, HTML smart punctuation, and extra inline formatting:

.. code-block:: python

   from wenmode import Wenmode
   from wenmode.plugins import inline_math

   wen = Wenmode(plugins=[inline_math])

   assert wen.render('Inline $x + y$.\n') == (
       '<p>Inline <span class="math math-inline">x + y</span>.</p>\n'
   )

Benchmark
---------

Wenmode is designed so enabling more rules adds limited dispatch overhead. The
benchmark script compares Markdown-to-HTML throughput across Wenmode and the
libraries covered by the migration guides:

.. code-block:: bash

   uv run --locked --group benchmark python scripts/benchmark.py --case all

``wenmode-core`` uses CommonMark-style rules plus pipe tables. It allows raw HTML
passthrough and disables URL sanitization to match the other HTML renderers. Mistune,
Python-Markdown, markdown-it-py, and markdown2 enable table support. Marko uses
its broader GFM helper. ``commonmark.py`` is a CommonMark-only baseline because it
does not support pipe tables.

``wenmode-all`` uses the ``github`` preset plus Wenmode's built-in plugins,
including front matter, math, definition lists, abbreviations, spoilers, ruby
text, heading IDs, GitHub alerts, and additional inline formatting. The benchmark
corpora use few of these extra rules. This target measures rule dispatch
overhead, not equivalent syntax coverage.

All benchmark targets are created once before warmup and timed iterations, then
reused for every render call. Python-Markdown resets the same reusable
``Markdown`` instance before each conversion.

Versions used in this snapshot:

===============  ========
Library          Version
===============  ========
wenmode          1.0.0rc1
mistune          3.3.4
mistletoe        1.6.0
python-markdown  3.10.3
markdown-it-py   4.2.0
markdown2        2.5.5
marko            2.2.4
commonmark.py    0.9.2
===============  ========

Results from one local Python 3.12.9 run on an Apple M1 Pro (macOS arm64),
using ``--case all --iterations 5 --warmup 2``. Mean times cover the five timed
iterations; MB/s uses the best time, and ``vs core`` compares mean times:

=========  =========  ===============  ========  =====  =======
Case       Bytes      Library          Mean      MB/s   vs core
=========  =========  ===============  ========  =====  =======
docs       153,629    wenmode-core     24.73ms   6.70   1.00x
docs       153,629    wenmode-all      34.21ms   4.75   0.72x
docs       153,629    mistune          29.74ms   5.26   0.83x
docs       153,629    mistletoe        65.52ms   2.47   0.38x
docs       153,629    python-markdown  86.46ms   1.81   0.29x
docs       153,629    markdown-it-py   48.81ms   3.40   0.51x
docs       153,629    markdown2        209.02ms  0.75   0.12x
docs       153,629    marko            179.56ms  0.87   0.14x
docs       153,629    commonmark.py    113.47ms  1.39   0.22x
rust-book  1,226,057  wenmode-core     168.57ms  7.56   1.00x
rust-book  1,226,057  wenmode-all      221.57ms  5.64   0.76x
rust-book  1,226,057  mistune          230.55ms  5.44   0.73x
rust-book  1,226,057  mistletoe        478.09ms  2.60   0.35x
rust-book  1,226,057  python-markdown  596.68ms  2.08   0.28x
rust-book  1,226,057  markdown-it-py   343.31ms  3.62   0.49x
rust-book  1,226,057  markdown2        4.155s    0.30   0.04x
rust-book  1,226,057  marko            1.199s    1.03   0.14x
rust-book  1,226,057  commonmark.py    10.123s   0.12   0.02x
progit     502,090    wenmode-core     33.45ms   17.16  1.00x
progit     502,090    wenmode-all      40.22ms   12.53  0.83x
progit     502,090    mistune          41.95ms   11.99  0.80x
progit     502,090    mistletoe        149.99ms  3.52   0.22x
progit     502,090    python-markdown  134.98ms  3.73   0.25x
progit     502,090    markdown-it-py   72.07ms   7.79   0.46x
progit     502,090    markdown2        1.437s    0.35   0.02x
progit     502,090    marko            340.23ms  1.51   0.10x
progit     502,090    commonmark.py    338.51ms  1.58   0.10x
=========  =========  ===============  ========  =====  =======

In this run, ``wenmode-core`` has the lowest mean time in every case.
``wenmode-all`` is faster than the other parsers on Rust Book and Pro Git;
Mistune is faster than ``wenmode-all`` on the docs corpus.

Benchmark numbers depend on hardware, Python version, corpus, and parser
configuration. See the full methodology in the
`Benchmarks <https://wenmode.lepture.com/benchmarks/>`__ documentation.

Streaming
---------

Use the ``streaming`` preset to render HTML chunks before the complete document is
parsed and rendered:

.. code-block:: python

   from wenmode import Wenmode
   from wenmode.presets import streaming

   wen = Wenmode(streaming())

   text = '''
   # Hello

   A [link](/url).
   '''

   for chunk in wen.stream(text):
       send(chunk)

Pass the returned iterator to a streaming response in Django, Flask, FastAPI,
or another framework. The ``streaming`` preset keeps tables, strikethrough, direct
links, and direct images enabled. It excludes reference-style links, footnotes,
and other deferred document-wide transforms.

Learn more
----------

- `Usage <https://wenmode.lepture.com/usage/>`__ for the main APIs.
- `Presets <https://wenmode.lepture.com/presets/>`__ for choosing a rule set.
- `Security <https://wenmode.lepture.com/security/>`__ for raw HTML and URL
  handling.
- `Plugins <https://wenmode.lepture.com/plugins/>`__ for built-in extensions.
- `Migration guides <https://wenmode.lepture.com/migration/>`__ for moving from
  other Python Markdown parsers.
