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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7e235304b9cc6dcd255e4399f979b2e6c42d77386cddbe4641ceb392ea482607
|
|
| MD5 |
0802ac9757e391e3af317963ff85d41d
|
|
| BLAKE2b-256 |
99d7b76f22abf6c825628c315545e3594fd596b35185ef8be145b0b1c9916460
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0417ad8696307f84f81577010827f97630cad877479977353b89216e8a63e68e
|
|
| MD5 |
9958e76e99dccff3fd68a036ee5e08cc
|
|
| BLAKE2b-256 |
2b253f4fbb1866b11a89d602f5dac3952db2c0aa72e52859fd401bc73e072470
|