Post-process sphinxcontrib-rust RST output, converting leftover markdown fragments (code fences, tables, links, headings, inline code) to proper RST via pandoc.
sphinxcontrib-rust generates RST files from Rust crates, but rustdoc
doc-comments are written in markdown. The generated RST ends up with markdown
fragments embedded verbatim inside directive bodies, which Sphinx cannot render
correctly.
For a real-world example, see rgpot (source), which uses this extension to document its Rust core library alongside C++ API docs.
Given Rust doc-comments like these in lib.rs:
//! ## Module Overview
//!
//! | Module | Purpose |
//! |--------|---------|
//! | [`types`] | `#[repr(C)]` data structures for force/energy I/O |
//! | [`tensor`] | DLPack tensor helpers |
sphinxcontrib-rust produces RST with the markdown still intact inside directives:
.. py:module:: rgpot_core
## Module Overview
| Module | Purpose |
|--------|---------|
| [`types`] | `#[repr(C)]` data structures for force/energy I/O |
| [`tensor`] | DLPack tensor helpers |
This renders incorrectly in Sphinx: headings inside directives break the document structure, markdown tables appear as literal pipe characters, and single-backtick code is not valid RST.
After this extension runs, the same file becomes:
.. py:module:: rgpot_core
**Module Overview**
+------------+----------------------------------------------------+
| Module | Purpose |
+============+====================================================+
| ``types`` | ``#[repr(C)]`` data structures for force/energy IO |
+------------+----------------------------------------------------+
| ``tensor`` | DLPack tensor helpers |
+------------+----------------------------------------------------+
Similarly, markdown code fences:
```c
rgpot_status_t s = rgpot_potential_calculate(pot, &input, &output);
if (s != RGPOT_SUCCESS) {
fprintf(stderr, "rgpot error: %s\n", rgpot_last_error());
}
```
become proper RST code-block directives:
.. code-block:: c
rgpot_status_t s = rgpot_potential_calculate(pot, &input, &output);
if (s != RGPOT_SUCCESS) {
fprintf(stderr, "rgpot error: %s\n", rgpot_last_error());
}
And markdown links like [metatensor](https://docs.metatensor.org/) become
`metatensor <https://docs.metatensor.org/>`_, while rustdoc intra-doc links
like [`types`] become types .
pip install sphinx-rustdoc-postprocess
Pandoc must be available on your PATH. See pandoc.org for installation
instructions.
Add to your Sphinx conf.py:
extensions = [
"sphinxcontrib_rust",
"sphinx_rustdoc_postprocess",
]
The extension hooks into builder-inited at priority 600 (after
sphinxcontrib-rust's default 500), so it automatically runs on the generated
RST files before Sphinx reads them.
| Config value | Default | Description |
|---|---|---|
rustdoc_postprocess_rst_dir |
"crates" |
Subdirectory of srcdir to scan for RST files |
rustdoc_postprocess_toctree_target |
"" |
RST file to inject a toctree snippet into (empty = skip) |
rustdoc_postprocess_toctree_rst |
"" |
RST snippet to append to the target file (empty = skip) |
# conf.py
import os
extensions = [
"sphinxcontrib_rust",
"sphinx_rustdoc_postprocess",
]
# sphinxcontrib-rust settings
rust_crates = {
"my_crate": os.path.abspath("../../my-crate/"),
}
rust_doc_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "crates")
rust_rustdoc_fmt = "rst"
# Inject a toctree entry for the Rust docs into an existing index page
rustdoc_postprocess_toctree_target = "api/index.rst"
rustdoc_postprocess_toctree_rst = """
Rust API
--------
.. toctree::
:maxdepth: 2
../crates/my_crate/lib
"""
| Markdown construct | RST output |
|---|---|
```lang code fences |
.. code-block:: lang directives |
\vert table \vert pipe tables |
RST grid tables (via pandoc) |
[text](url) links |
`text <url>`_ |
[`Name`] intra-doc links |
``Name`` |
`code` inline code |
``code`` |
## Heading ATX headings |
**Heading** (bold, since RST headings can't nest in directives) |
pixi install
pixi run test
A pre-commit job is setup on CI to enforce consistent styles, so it is best
to set it up locally as well (using uvx for isolation):
# Run before committing
uvx pre-commit run --all-files
MIT