Writing documentation

This page explains how to add to these docs. There are two kinds of content:

  1. Prose pages — hand-written guides like this one, in reStructuredText (.rst).

  2. API reference — generated automatically from the docstrings in the semi_cr source code.

Building the docs locally

Run the docs server from the repo root:

uv run docs

This regenerates the API reference from docstrings, builds the HTML, serves it at http://127.0.0.1:8000, opens a browser tab, and rebuilds on every save. Press Ctrl-C to stop.

The built HTML lands in docs/_build/html and is not committed to the repo (it is .gitignored). Rendered Jupyter notebooks additionally require the pandoc binary; without it the notebook pages are skipped locally but the surrounding prose still builds.

Writing prose pages (reStructuredText)

Prose pages are .rst files under docs/. Sphinx uses reStructuredText, not Markdown. To make a new page appear, create the file and add it to a toctree (see Wiring a page into the site below).

reST cheat sheet

Headings are text underlined (and optionally overlined) with punctuation. The character you use defines the level; be consistent within a file. A common order is * (with overline) for the title, then =, -, ~:

*****
Title
*****

Section
=======

Subsection
-----------

Important

The underline must be at least as long as the text, or Sphinx emits a “Title underline too short” warning and may misparse the page.

Inline markup:

*italic*   **bold**   ``literal / code``

Lists — blank line before the list, indentation for continuation:

- bullet one
- bullet two

#. auto-numbered
#. auto-numbered

Code blocks — a :: at the end of a paragraph starts a literal block; the block must be indented:

Here is some Python::

    x = greeter("world")
    print(x)

Use .. code-block:: python when you want syntax highlighting for a specific language:

.. code-block:: python

    def f(x):
        return x + 1

Links:

`link text <https://example.com>`_          external link
:doc:`api/index`                            link to another page
:ref:`wiring-a-page`                         link to a labelled target
:func:`semi_cr.core.helpers.deprecated`     link to an API object

Admonitions draw attention to a note:

.. note::

   Blank line, then indented body.

.. warning::

   Same shape: ``note``, ``warning``, ``important``, ``tip``, ...

Wiring a page into the site

A page is invisible until it is listed in a toctree. Add its filename (without .rst) to the toctree in docs/index.rst:

.. toctree::
   :maxdepth: 2

   api/index
   writing_docs
   example_notebooks/index
   changes/index

Nested sections get their own index.rst with a local toctree — see docs/example_notebooks/index.rst for an example that globs its contents.

Documenting code for the API reference

The API pages under semi_cr API Documentation are generated, not written by hand. On every build the sphinx.ext.apidoc extension scans the source tree and autodoc imports each module and reads its docstrings. So “adding API docs” means writing good docstrings; nothing else is required.

Important

Only the semi_cr.core subtree is included in the API reference. The apps, setup, research and portable subtrees are excluded because they run hardware connections / interactive prompts at import time, which autodoc cannot do during a build. If you move reusable, safely importable code into core, it shows up automatically. See the apidoc_modules setting in docs/conf.py for the exclusion list.

Docstring style

This project uses Google-style docstrings (enforced by ruff’s pydocstyle config) which Sphinx understands via the napoleon extension. A function docstring looks like this:

def greeter(name: str, *, loud: bool = False) -> str:
    """Generate a greeting message.

    A longer description can go here, spanning multiple lines. Keep the
    one-line summary on the first line, then a blank line, then details.

    Args:
        name: The name of the person to greet. Types come from the
            annotation, so you don't need to repeat them here.
        loud: If ``True``, shout the greeting.

    Returns:
        The formatted greeting.

    Raises:
        ValueError: If ``name`` is empty.

    Example:
        >>> greeter("world")
        'Hello, world'
    """

Classes follow the same shape. Because autoclass_content = "both" is set, both the class docstring and the __init__ docstring are shown, so document constructor arguments in __init__:

class Chip:
    """A device under test.

    Attributes:
        name: Human-readable identifier for the chip.
    """

    def __init__(self, name: str) -> None:
        """Initialise the chip.

        Args:
            name: Human-readable identifier for the chip.
        """
        self.name = name

Common sections recognised by napoleon: Args, Returns, Yields, Raises, Attributes, Example/Examples, Note, Warning.

What gets picked up

  • Modules, classes, functions and methods are documented from their docstrings automatically once the module lives under semi_cr.core.

  • Undocumented members still appear (undoc-members is on), just without descriptions — so a docstring is always an improvement.

  • Private names (leading underscore) are omitted, except dunder methods that carry a docstring.

  • A new module or subpackage under core is included on the next build; sphinx-apidoc rediscovers the tree each time, so you don’t edit any .rst by hand.

Avoiding docstring warnings

autodoc re-parses docstrings as reST, so the reST rules above apply inside them. The two most common warnings:

  • “Unexpected indentation” — usually a continuation line under Args: that isn’t indented consistently, or a code example not introduced with ::.

  • “Title underline too short” — a Returns/Args line written as a reST heading (underlined with ---) instead of the Google Returns: section label. Use the Section: form, not underlines, inside docstrings.

Build with uv run docs and read the warnings it prints; each one names the file and line so you can fix it at the source.