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
indented verbatim blocks 7.7% 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 129 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 third 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, one regular expression 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

This affects 179 standard library docstrings (3.9% of the multi-line ones). 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

266 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. The name is a working title: simplemark is reported taken on PyPI.

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.0.tar.gz (13.9 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.0-py3-none-any.whl (16.2 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: simplemark-0.1.0.tar.gz
  • Upload date:
  • Size: 13.9 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.0.tar.gz
Algorithm Hash digest
SHA256 7e235304b9cc6dcd255e4399f979b2e6c42d77386cddbe4641ceb392ea482607
MD5 0802ac9757e391e3af317963ff85d41d
BLAKE2b-256 99d7b76f22abf6c825628c315545e3594fd596b35185ef8be145b0b1c9916460

See more details on using hashes here.

File details

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

File metadata

  • Download URL: simplemark-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 16.2 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.0-py3-none-any.whl
Algorithm Hash digest
SHA256 0417ad8696307f84f81577010827f97630cad877479977353b89216e8a63e68e
MD5 9958e76e99dccff3fd68a036ee5e08cc
BLAKE2b-256 2b253f4fbb1866b11a89d602f5dac3952db2c0aa72e52859fd401bc73e072470

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