Metadata-Version: 2.4
Name: simplemark
Version: 0.1.1
Summary: A small markup language for Python docstrings and argparse help text
Keywords: docstring,markdown,markup,pydoc,argparse,documentation
Author: Stefane Fermigier
Author-email: Stefane Fermigier <sf@abilian.com>
License-Expression: MIT
License-File: LICENSE
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Documentation
Classifier: Topic :: Software Development :: Documentation
Classifier: Topic :: Text Processing :: Markup
Classifier: Typing :: Typed
Requires-Python: >=3.12
Project-URL: Homepage, https://git.sr.ht/~sfermigier/simplemark
Project-URL: Documentation, https://simplemark.lab.abilian.com/
Project-URL: Source, https://git.sr.ht/~sfermigier/simplemark
Description-Content-Type: text/markdown

# 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](https://discuss.python.org/t/markdown-and-others/108184) 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

```python
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:

```python
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](notes/00-prior-art.md) | Every previous attempt, and why each died |
| [01-vision.md](notes/01-vision.md) | The problem, goals, non-goals, failure modes |
| [02-use-cases.md](notes/02-use-cases.md) | Corpus data and 24 use cases, as acceptance criteria |
| [03-design-alternatives.md](notes/03-design-alternatives.md) | Every decision, its options, and its status |
| [04-specs.md](notes/04-specs.md) | The specification |
| [05-implementation.md](notes/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.
