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.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

simplemark-0.1.1.tar.gz (15.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

simplemark-0.1.1-py3-none-any.whl (17.8 kB view details)

Uploaded Python 3

File details

Details for the file simplemark-0.1.1.tar.gz.

File metadata

  • Download URL: simplemark-0.1.1.tar.gz
  • Upload date:
  • Size: 15.2 kB
  • Tags: Source
  • Uploaded using 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}

File hashes

Hashes for simplemark-0.1.1.tar.gz
Algorithm Hash digest
SHA256 b42d51f47910ce2892e2d25cfa0fdd27e04ba6480e30f3073638039f446d9c09
MD5 6e73d1a872b17a75ddd100db4060fd96
BLAKE2b-256 f53f86c0ca013f5ffd3733d2ff4c13a3b82e782f89c5531bef30b317aee69ecc

See more details on using hashes here.

File details

Details for the file simplemark-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: simplemark-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 17.8 kB
  • Tags: Python 3
  • Uploaded using 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}

File hashes

Hashes for simplemark-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 50d72273c41e3aae8ffc25a86b222a80d14428a58f5bd940bae0a893374fc5bf
MD5 0b2cfe76224e3376cb09c86ce4226085
BLAKE2b-256 81d039e6ddbec309ba6d14936edbdbfef911cb03b45d28a79cfbb41c1a612d17

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page