Skip to main content

simplemark

A small markup language for Python docstrings and argparse help text.

It is a subset of CommonMark's constructs, with one narrowed inline rule: emphasis delimiters must be flanked by whitespace or punctuation, so emphasis never happens inside a word. A docstring with no diagnostics renders identically in simplemark and under a Markdown renderer.

It exists to answer a question asked and left unanswered in a python.org discussion about rich text in pydoc and argparse: if just a subset will do, what markdown features can be safely omitted, and does omitting them actually reduce the complexity?

The measurement

Every docstring in the Python 3.12.13 standard library, all 7272 of them, run through the checker:

docstrings share
clean: no diagnostics, unchanged 6762 93.0%
containing an error 260 3.6%

What the corpus says a docstring format can drop:

feature docstrings using it verdict
# headings 7 kept anyway, on familiarity grounds: the one taste call
reST underline headings 21 omit
numbered lists 9 keep, it costs nothing
*emphasis* 251 keep, mostly marking parameter names
**strong** 1 keep, but the debate about it is misplaced
verbatim blocks 4.2% the core convention; cannot be dropped

And what CommonMark gets wrong about Python prose, which the narrowed rule fixes:

x = xc*10**xe and y = yc*10**ye, compute x**y.   CommonMark: <em>/<strong>.  simplemark: literal.
The __init__ and __del__ methods.                CommonMark: <strong>.       simplemark: literal.

That rule is why *args, **kwargs, f(*args, **kwargs), 10**e, __init__, _private, a_b_c, *.py, C:\dir, \d+ and [start, [stop]] are all literal text, with no escaping.

The language

Five block types and five inline forms. The whole grammar:

# Overview

Compute `n` factorial, in **bold** and *italic*.

Example:

    >>> fact(5)
    120

- a bullet
- another

1. numbered
2. list

See [the docs](https://example.com).

No definition lists, no tables, no block quotes, no images, no footnotes, no raw HTML, no nested inline markup.

reStructuredText constructs (::, :role:, .. directive, legacy `quoted' text) are not part of the language. check(compat=True) finds them, so an existing codebase can migrate deliberately.

Use

import simplemark

doc = simplemark.parse(docstring)
print(simplemark.render_text(doc, width=72))

for diagnostic in simplemark.check(docstring):
    print(diagnostic)          # 12:5: CM001 CommonMark reads some of these ...

for diagnostic in simplemark.check(docstring, compat=True):
    print(diagnostic)          # ... plus L001 trailing '::' is reStructuredText

parse, parse_inline, check and render_text never raise, on any input.

python -m simplemark --width 72 file.txt
python -m simplemark --check file.txt
python -m simplemark --check --compat --strict file.txt

See the before/after that motivates it, and the evidence behind it:

uv run python tools/pydoc_demo.py os.walk --width 60
uv run python tools/corpus_report.py

Sphinx

A project that adopts simplemark and builds its docs with autodoc needs its docstrings read as reStructuredText, or [a](link) and # Heading arrive as literal punctuation. tools/sm2rst.py renders the same AST as reST in 140 lines, with no changes to the parser:

printf '# Title\n\nUses `n` and 10**e.\n' | uv run python tools/sm2rst.py

It is not part of the package: pydoc and argparse render to terminals and have no use for reST, so shipping it would add a sixth of the size for nothing the core case needs.

Size

module lines of code
_parser.py 394
_render.py 220
_nodes.py 75
__init__.py, _cli.py, __main__.py 99
total 788

No runtime dependencies. Linear time, no backtracking, two small regular expressions in the block grammar.

A bug found along the way

inspect.cleandoc takes the smallest indentation in the body as the margin. When a code block is the only indented content, the block's own indentation becomes the margin and is stripped, flattening the example:

def f():
    """Example::

        >>> f()
    """
# inspect.cleandoc gives 'Example::\n\n>>> f()' -- the block is gone

Simplemark's margin differs from cleandoc's in 179 standard library docstrings (3.9% of the multi-line ones); in 65 of them cleandoc destroys a verbatim block outright. simplemark takes the margin from the closing-quote line when the docstring ends with one, which is exactly the enclosing indentation; 50% of stdlib docstrings provide that hint.

Design notes

The reasoning, the alternatives that were rejected, and why, are in notes/:

00-prior-art.md Every previous attempt, and why each died
01-vision.md The problem, goals, non-goals, failure modes
02-use-cases.md Corpus data and 24 use cases, as acceptance criteria
03-design-alternatives.md Every decision, its options, and its status
04-specs.md The specification
05-implementation.md How the code is built, and what it deliberately is not

Tests

uv run pytest

275 tests. tests/conformance.json is the behavioural specification as data, so a second implementation is possible. tests/b_integration/test_differential.py checks the normative claims against the real markdown-it-py rather than asserting them. The standard library is the fuzz corpus.

Status

Design and reference implementation, intended as a contribution to the discussion rather than as a competing library. Published on PyPI as simplemark; source at https://git.sr.ht/~sfermigier/simplemark; MIT licensed.

Metadata

Release files for simplemark 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for simplemark 0.1.1
File Size Uploaded
simplemark-0.1.1.tar.gz 15.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for simplemark 0.1.1
File Interpreter ABI Platform
simplemark-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 33.0 kB

Release files / simplemark-0.1.1.tar.gz

Download URL simplemark-0.1.1.tar.gz
Size 15.2 kB
Tags Source
SHA-256 checksum
How to use checksums
b42d51f47910ce2892e2d25cfa0fdd27e04ba6480e30f3073638039f446d9c09
BLAKE2b-256 checksum
How to use checksums
f53f86c0ca013f5ffd3733d2ff4c13a3b82e782f89c5531bef30b317aee69ecc
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.16 {"installer":{"name":"uv","version":"0.11.16","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / simplemark-0.1.1-py3-none-any.whl

Download URL simplemark-0.1.1-py3-none-any.whl
Size 17.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
50d72273c41e3aae8ffc25a86b222a80d14428a58f5bd940bae0a893374fc5bf
BLAKE2b-256 checksum
How to use checksums
81d039e6ddbec309ba6d14936edbdbfef911cb03b45d28a79cfbb41c1a612d17
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.16 {"installer":{"name":"uv","version":"0.11.16","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page