Metadata-Version: 2.4
Name: wenmode
Version: 0.13.3
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 :: 4 - Beta
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.10
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.10
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 disables raw HTML
passthrough and 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 these snapshots:

===============  =======
Library          Version
===============  =======
wenmode          0.11.0
mistune          3.3.3
python-markdown  3.10.2
markdown-it-py   4.2.0
markdown2        2.5.5
marko            2.2.3
commonmark.py    0.9.2
===============  =======

Mean time from one local Python 3.12.9 ``--case all`` run:

=========  =========  ===============  ========  =====  =======
Case       Bytes      Library          Mean      MB/s   vs core
=========  =========  ===============  ========  =====  =======
docs       135,115    wenmode-core     18.08ms   7.84   1.00x
docs       135,115    wenmode-all      20.70ms   6.56   0.87x
docs       135,115    mistune          25.42ms   5.85   0.71x
docs       135,115    python-markdown  76.18ms   1.84   0.24x
docs       135,115    markdown-it-py   39.21ms   3.61   0.46x
docs       135,115    markdown2        158.86ms  0.88   0.11x
docs       135,115    marko            144.15ms  1.00   0.13x
docs       135,115    commonmark.py    90.95ms   1.63   0.20x
rust-book  1,226,076  wenmode-core     168.82ms  7.80   1.00x
rust-book  1,226,076  wenmode-all      181.23ms  7.08   0.93x
rust-book  1,226,076  mistune          222.76ms  5.60   0.76x
rust-book  1,226,076  python-markdown  588.23ms  2.10   0.29x
rust-book  1,226,076  markdown-it-py   337.53ms  3.69   0.50x
rust-book  1,226,076  markdown2        4.129s    0.30   0.04x
rust-book  1,226,076  marko            1.107s    1.12   0.15x
rust-book  1,226,076  commonmark.py    10.046s   0.12   0.02x
progit     502,090    wenmode-core     28.90ms   17.95  1.00x
progit     502,090    wenmode-all      36.45ms   15.32  0.79x
progit     502,090    mistune          45.41ms   11.94  0.64x
progit     502,090    python-markdown  138.27ms  3.72   0.21x
progit     502,090    markdown-it-py   71.63ms   7.73   0.40x
progit     502,090    markdown2        1.429s    0.35   0.02x
progit     502,090    marko            338.29ms  1.52   0.09x
progit     502,090    commonmark.py    339.19ms  1.52   0.09x
=========  =========  ===============  ========  =====  =======

In this run, ``wenmode-all`` remains faster than the other parsers even after
loading many extra rules that the benchmark inputs mostly do not use.

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.
