click_extra.sphinx package

Helpers and utilities for Sphinx.

Note

The MkDocs counterpart lives in click_extra.mkdocs, which achieves the same ANSI color rendering by patching pymdownx.highlight’s formatter classes.

Todo

Split the documentation stack into a standalone extra-docs project, should it keep growing away from the CLI framework hosting it. It would take this package, click_extra.pygments, click_extra.mkdocs, the click_extra.markdown package with its MyST converters and marker-region primitives, and the ones repomatic keeps in its own docs/docs_update.py. The open question is the click:source and click:run directives: they invoke a Click CLI, so either they stay here, or extra-docs depends on click-extra.

click_extra.sphinx.EXEC_DIRECTIVES_OPT_IN = 'click_extra_enable_exec_directives'

Name of the conf.py config flag that gates every code-execution directive.

Default is False. A project that adds click_extra.sphinx to its extensions list gets the ANSI Pygments formatter unconditionally, but does not gain access to either the click:* or the python:* directive families until the maintainer opts in explicitly. Both families exec user-supplied Python at build time with full Sphinx-process privileges: gating them behind a single explicit flag keeps a transitive import or a doc-only pull request from silently expanding the build’s attack surface.

click_extra.sphinx.setup(app)[source]

Register extensions to Sphinx.

Always-on features (no execution surface):

  • The ANSI-capable HTML formatter for Pygments (replaces sphinx.highlighting.PygmentsBridge with one that renders ANSI colors in code blocks).

  • The matrix directive, which renders a package’s compatibility grid ({matrix} python or {matrix} <distribution>) from its git tag history. It runs a canned generator rather than user-supplied Python, so it carries no execution surface and needs no opt-in. See click_extra.sphinx.matrix.

  • Deduplication of the todolist page, which sphinx.ext.todo fills with one entry per rendering of a :todo: directive rather than one per directive. Inert on a project that enables neither the extension nor a todolist, and switched off with click_extra.sphinx.todos.DEDUPE_TODOS_CONFIG. See click_extra.sphinx.todos.

  • A click_extra.sphinx.todos.UNRENDERED_TODO_WARNING warning for each :todo: in a docstring that no page renders, which no todolist can list. Inert on a project that does not enable sphinx.ext.todo, and silenced through suppress_warnings. See click_extra.sphinx.todos.

  • A one-line first column for every table whose top-left cell names both axes, like the matrix tables: a narrow screen scrolls the table instead of wrapping its labels. See click_extra.sphinx.tables.

  • A crosshair on the same tables, which highlights the row and the column under the pointer, and makes a cell holding a single link clickable over its whole area. See click_extra.sphinx.crosshair.

  • A vertical layout for a sphinx-design tab-set marked with :class: tabs-vertical: its labels stack down a left rail. Deprecated for the :orientation: vertical option of sphinx-design 0.8.0, with a warning on each tab-set that carries the class. Inert without sphinx-design. See click_extra.sphinx.tabs.

  • Class diagrams of autoclasstree that let the mouse wheel scroll the page, where the d3 zoom of sphinxcontrib-mermaid would catch it. Inert without sphinxcontrib-mermaid, or with mermaid_d3_zoom off. See click_extra.sphinx.mermaid.

  • The linkcheck requests to github.com signed with the token of GITHUB_TOKEN, release downloads excepted. Inert until the variable is set. See click_extra.sphinx.linkcheck.

  • No sphinx.ext.viewcode page for a module that no documented object links to, so a parallel read publishes the pages of a serial one. Inert without sphinx.ext.viewcode. See click_extra.sphinx.viewcode.

  • Every node of a page pointed at the page’s own document before Sphinx pickles it, which keeps the build environment out of the doctree of a page with an {eval-rst} block. See click_extra.sphinx.doctrees.

  • Changelog releases anchored on their version, like #v1-4-2, in place of a positional #idN. Inert until a project sets click_extra.sphinx.changelog.RELEASE_ANCHORS_CONFIG. See click_extra.sphinx.changelog.

  • The man-page hook, which writes roff man pages for the CLIs a project lists in click_extra.sphinx.manpages.MANPAGES_CONFIG_KEY on every HTML build. Inert until a project lists one. See click_extra.sphinx.manpages.

  • A failed build on the warning types a project lists in click_extra.sphinx.fail_on_warnings.FAIL_ON_WARNINGS_CONFIG, where Sphinx can only fail on every warning at once. Inert until a project lists one. See click_extra.sphinx.fail_on_warnings.

Opt-in features (gated behind click_extra_enable_exec_directives):

  • click:source / click:run to define and execute Click CLIs at build time.

  • click:tree / click:config to render the command reference and the configuration reference of a Click CLI at build time.

  • python:source / python:run to execute arbitrary Python at build time and render its source or captured stdout.

  • python:render / python:render-myst / python:render-rst to execute arbitrary Python and parse the captured stdout as live document content.

All directives in the opt-in group execute user-supplied Python with the same privileges as the Sphinx process. They are therefore disabled by default. Set click_extra_enable_exec_directives = True in conf.py to register them.

Caution

This function replaces the HTML formatter of sphinx.highlighting.PygmentsBridge with AnsiHtmlFormatter, for every Sphinx app of the process, so that code blocks render ANSI colors.

Return type:

ExtensionMetadata

Submodules

click_extra.sphinx.announcement module

Count the years an announcement banner claims, like “maintained for 5+ years”.

A figure written by hand in conf.py goes stale on the next anniversary. years_since() computes it again on each build, so the banner of a site published from CI stays current.

click_extra.sphinx.announcement.years_since(start, *, today=None)[source]

Return the whole years from start to today.

The count goes up on each anniversary of start, never before it. A count of days divided by 365 would run a day ahead for each leap day in between. A start on 29 February has its anniversary on 1 March of a common year.

Parameters:
  • start (date) – the day the count starts from.

  • today (date | None) – the day the count ends on. Defaults to the current day in UTC, which is the day of the build.

Return type:

int

Returns:

the whole years from start to today, rounded down: negative when today comes before start.

click_extra.sphinx.changelog module

Anchor each release of a changelog on its version, like #v1-4-2.

docutils builds the ID of a section from its title, minus the leading digits and hyphens. That leaves nothing of a release title like 1.4.2 (2026-03-15), so the section takes the next value of a per-page counter: #id1 for the newest release. The next release then moves every older one down the counter, and a saved #id40 link lands on another release.

With RELEASE_ANCHORS_CONFIG set, ReleaseAnchors gives each section titled like a release an ID made from its version instead. The transform applies to every page, so it covers a changelog split over several pages, like an archive of old releases.

click_extra.sphinx.changelog.RELEASE_ANCHORS_CONFIG = 'click_extra_release_anchors'

Name of the conf.py value that anchors releases on their version.

False by default: the version IDs replace the ones a published changelog already carries, so each #idN link to a release stops working once.

click_extra.sphinx.changelog.RELEASE_HEADING = re.compile('(?P<version>\\d+\\.\\d+\\.\\d+)(?:\\.\\w+)? \\([^)]+\\)')

Rendered title of a release: 1.4.2 (2026-03-15).

The release in preparation carries a .devN suffix, and the unreleased label in place of a date: 1.5.0.dev0 (unreleased).

class click_extra.sphinx.changelog.ReleaseAnchors(document, startnode=None)[source]

Bases: SphinxTransform

Replace the ID docutils gave each release section with its version.

The version ID replaces the old one on the section, in the ID and name tables of the document, and in the table myst-parser resolves a `text <changelog.md#v1-4-2>`_ link against. The .devN suffix of the release in preparation is left out, so its anchor does not change the day it ships.

Runs right before PropagateTargets: the section then holds only the ID docutils gave it, and nothing has read that ID yet.

Initial setup for in-place document transforms.

default_priority = 259

Numerical priority of this transform, 0 through 999 (override).

apply(**kwargs)[source]

Rename the section of each release, if the project opted in.

Return type:

None

click_extra.sphinx.changelog.setup(app)[source]

Register the config value and the release transform on app.

Called from click_extra.sphinx.setup() so projects only need to list "click_extra.sphinx" in their extensions. The value rebuilds the environment, since the IDs are set while a page is read.

Return type:

None

click_extra.sphinx.click module

Sphinx rendering of CLI based on Click Extra.

Holds the click:source and click:run directives, their options, and the :screenshot: captures and :mirror: regions they maintain. click_extra.sphinx.click_runner runs their blocks, and click_extra.sphinx.click_reference builds the click:tree and click:config reference directives on top of them.

See also

These directives are based on Pallets’ Sphinx Themes, released under a BSD-3-Clause license.

Compared to the latter, it:

  • Adds support for MyST syntax.

  • Adds rendering of ANSI codes in CLI results.

  • Has better error handling and reporting which helps you pinpoint the failing code in your documentation.

  • Has no println function: a block prints a blank line natively.

Todo

Collapse ContentOffsetMixin.abs_content_offset of click_extra.sphinx._base onto its rST branch (content_offset verbatim) once a myst-parser release carries the open rework #1175, which makes content_offset document-relative.

click_extra.sphinx.click.RST_INDENT = '   '

The indentation used for rST code blocks lines.

click_extra.sphinx.click.DEFAULT_SCREENSHOT_DIR = 'assets'

Directory a click:run :screenshot: capture is written to by default.

Relative to the documentation source root. Overridden by the click_extra_screenshot_dir conf.py value, see SCREENSHOT_DIR_CONFIG.

click_extra.sphinx.click.RUN_CAPTURE_CONFIG = 'click_extra_run_capture'

Name of the conf.py value selecting the stream-capture mode for the CLIs that click:run and click:tree execute.

Maps to the capture parameter of Click’s CliRunner, "sys" or "fd". Defaults to "fd", which also captures what a subprocess or a C extension writes to the standard file descriptors: see ClickRunner for what it leaves out. Windows has no file-descriptor capture, so "fd" degrades to "sys" there.

click_extra.sphinx.click.SCREENSHOT_DIR_CONFIG = 'click_extra_screenshot_dir'

Name of the conf.py value locating the directory click:run writes captures to.

A path relative to the documentation source directory, holding the SVG a click:run block names with its :screenshot: option. Defaults to DEFAULT_SCREENSHOT_DIR, matching where a Sphinx project conventionally keeps the images its pages embed, and where a README pointing at the repository finds them.

click_extra.sphinx.click.SCREENSHOT_PRESET_CONFIG = 'click_extra_screenshot_preset'

Name of the conf.py value naming the terminal every capture is drawn as.

One of PRESETS, applied to each click:run block whose :screenshot: does not name a preset of its own. Empty by default, which keeps the renderer’s neutral window: a project wanting all of its captures to look like the same desktop states it once here instead of on every block.

click_extra.sphinx.click.SCREENSHOT_SYNTAX_STYLE_CONFIG = 'click_extra_screenshot_syntax_style'

Name of the conf.py value coloring every capture drawn from source code.

One of the Pygments styles, applied to each source block whose :screenshot: does not name a :screenshot-syntax-style: of its own. Empty by default, which takes the style each chrome is drawn for, see DEFAULT_SYNTAX_STYLES. Unused by a block picturing what a command printed, whose colors that command already chose.

click_extra.sphinx.click.SCREENSHOT_WATERMARK_CONFIG = 'click_extra_screenshot_watermark'

Name of the conf.py value crediting every capture a click:run writes.

Empty by default, which draws no credit line: unlike a capture of the screenshot command, a block’s image sits on a page that already says what drew it. A project wanting a credit states its text here, or per block with :screenshot-watermark:.

click_extra.sphinx.click.SCREENSHOT_MARKER_START = '<!-- screenshot -->'

Opening marker of a click:run :mirror: region.

Written on its own line, directly below the fence, and paired with SCREENSHOT_MARKER_END. The region holds the Markdown link to the capture the block’s :screenshot: option names, so the image shows wherever the raw Markdown is read: on GitHub, on PyPI, in an editor’s preview.

Same <!-- name --> / <!-- name-end --> grammar as the python:render :mirror: regions, under a name saying what this one holds. Unlike those, the region’s content is derived from the option alone, never from executing anything: it goes stale only when the capture is renamed, which is why no build-time pass regenerates it in memory.

click_extra.sphinx.click.SCREENSHOT_MARKER_END = '<!-- screenshot-end -->'

Closing marker of a :mirror: region. See SCREENSHOT_MARKER_START.

click_extra.sphinx.click.register_config(app)[source]

Declare the conf.py values the click:* directives read.

Called by click_extra.sphinx.setup() beside the opt-in flag gating the directives, and whether or not the project opts in: a conf.py naming one of these values is then valid on every project loading the extension.

Return type:

None

class click_extra.sphinx.click.ClickDirective(name, arguments, options, content, lineno, content_offset, block_text, state, state_machine)[source]

Bases: ContentOffsetMixin

Base class of every click:* directive.

has_content = True

May the directive have content?

required_arguments = 0

Number of required directive arguments.

optional_arguments = 1

The optional argument overrides the default Pygments language to use.

final_argument_whitespace = False

May the final argument contain whitespace?

option_spec: ClassVar[OptionSpec] = {'caption': <function unchanged_required>, 'class': <function class_option>, 'dedent': <function optional_int>, 'emphasize-lines': <function unchanged_required>, 'emphasize-result-lines': <function unchanged_required>, 'force': <function flag>, 'hide-prompt': <function flag>, 'hide-results': <function flag>, 'hide-source': <function flag>, 'language': <function unchanged_required>, 'lineno-start': <class 'int'>, 'linenos': <function flag>, 'mirror': <function flag>, 'name': <function unchanged>, 'screenshot': <function unchanged_required>, 'screenshot-animate': <function unchanged_required>, 'screenshot-backdrop': <function unchanged_required>, 'screenshot-background': <function _screenshot_background>, 'screenshot-blank': <function parse_pause>, 'screenshot-blink': <function parse_pause>, 'screenshot-border': <function unchanged_required>, 'screenshot-border-width': <function nonnegative_int>, 'screenshot-closing-prompt': <function flag>, 'screenshot-columns': <function parse_columns>, 'screenshot-cursor': <function _screenshot_cursor>, 'screenshot-emphasize-lines': <function unchanged_required>, 'screenshot-hold': <function parse_hold>, 'screenshot-interval': <function _positive_number>, 'screenshot-line-numbers': <function flag>, 'screenshot-margin': <function nonnegative_int>, 'screenshot-opacity': <function _screenshot_opacity>, 'screenshot-padding': <function nonnegative_int>, 'screenshot-preset': <function _screenshot_preset>, 'screenshot-prompt': <function unchanged>, 'screenshot-quantum': <function _positive_number>, 'screenshot-radius': <function nonnegative_int>, 'screenshot-record': <function unchanged_required>, 'screenshot-shadow': <function unchanged_required>, 'screenshot-speed': <function _positive_number>, 'screenshot-submit': <function _positive_number>, 'screenshot-syntax-style': <function unchanged_required>, 'screenshot-title': <function unchanged_required>, 'screenshot-typing': <function _positive_number>, 'screenshot-watermark': <function unchanged_required>, 'screenshot-watermark-color': <function unchanged_required>, 'show-prompt': <function flag>, 'show-results': <function flag>, 'show-source': <function flag>}

Options supported by this directive.

Support the same options as sphinx.directives.code.CodeBlock, and some specific to Click directives.

The standard emphasize-lines option applies to the source block only. Use emphasize-result-lines to highlight specific lines in the captured output block, with the same syntax (like :emphasize-result-lines: 1,3-5).

Every directive carries the :screenshot: family, because every one of them shows something worth committing as an image. What lands in that image is whatever the block itself draws: a run’s captured output, and a source block’s own code, highlighted by click_extra.capture.snippet.

:screenshot: and :mirror: are deliberately independent. :screenshot: <name>``only *writes*``<name>.svg``under the``click_extra_screenshot_dir, leaving the page’s code block alone: inside Sphinx that block beats an image, being selectable, searchable and theme-aware. :mirror: is what puts the image on the page, by keeping a Markdown link to it in the source .md between the same marker pair the python:render :mirror: flag uses, so the capture shows on GitHub and PyPI as well.

So :screenshot: alone maintains an asset some other surface embeds, and the two together also show it here. Both are refreshed offline by click-extra refresh-directives.

default_language: str

Default highlighting language to use to render the code block.

All Pygments’ languages short names are recognized.

source_language: str | None = None

Language the source block is highlighted in, when it is not language.

None for a directive showing code of the language it names: the source block of a click:source follows its :language: option. A directive that runs a block names the language of its results instead, so it states the language of the code producing them here, python for every runner of this package.

screenshots_source: bool = False

Whether a :screenshot: on this directive pictures the block’s own code.

False for a directive that runs something: what is worth committing there is the output, and the code producing it is on the page already. True for one that only declares code, which has no output to picture and whose subject is the code itself. See screenshot_lines().

show_source_by_default: bool = True

Whether to render the source code of the example in the code block.

show_results_by_default: bool = True

Whether to render the results of the example in the code block.

show_prompt_by_default: bool = True

Whether to draw the invocation above the results it produced.

runner_method: str

The name of the method to call on the per-document runner.

A ClickRunner for the click:* directives.

runner_attr: ClassVar[str] = 'click_runner'

Name of the attribute holding the runner on the doctree.

Subclasses (like PythonDirective) override this so the Click and Python runners don’t collide on the same document.

runner_factory

Class to instantiate for the per-document runner.

Called with the capture mode the click_extra_run_capture conf.py value resolves to, see runner.

alias of ClickRunner

property runner

Get or create the per-document runner.

Creates one runner per document, keyed by runner_attr.

property language: str[source]

Short name of the Pygments lexer used to highlight the code block.

Returns, in order of precedence, the language specified in the :language: directive options, the first argument of the directive (if any), or the default set in the directive class.

code_block_options(target='source')[source]

Render the options supported by Sphinx’ native code-block directive.

target selects which block these options will be attached to: "source" for the directive’s input source code, "results" for the captured output. emphasize-lines routes to the source block; emphasize-result-lines is rewritten as emphasize-lines on the results block, so authors can highlight different lines in each.

Return type:

list[str]

property show_source: bool[source]

Whether to show the source code of the example in the code block.

The last occurrence of either show-source or hide-source options wins. If neither is set, the default is taken from show_source_by_default.

property show_results: bool[source]

Whether to show the results of running the example in the code block.

The last occurrence of either show-results or hide-results options wins. If neither is set, the default is taken from show_results_by_default.

property show_prompt: bool[source]

Whether to draw the invocation above the output it produced.

The last occurrence of either show-prompt or hide-prompt options wins. If neither is set, the default is taken from show_prompt_by_default.

The prompt is one line rendered by format_cli_prompt(), prepended to the captured output. It is therefore part of the results, not a block of its own: hiding it drops it from the results code block and from the SVG a :screenshot: writes, which is drawn from the same lines. Reach for it when the invocation is noise the surrounding prose already carries, or when a capture is wanted as bare output.

evaluate(expression, subject)[source]

Evaluate expression in the runner namespace of the document.

The namespace is the one the earlier blocks of the page filled, so an expression names what a click:source above built. subject opens the error, naming what asked for the evaluation: a directive, like click:tree, or one of its options, like click:run: :screenshot-animate:.

Raises:

RuntimeError – when the expression cannot be evaluated.

Return type:

Any

render_code_block(lines, language, target='source')[source]

Render the code block with the source code or results.

target is forwarded to code_block_options() so the emphasize-lines / emphasize-result-lines split routes the right highlighting to each block.

Return type:

list[str]

property screenshot: str | None[source]

Name of the SVG capture this block renders to, without its extension.

Set by the :screenshot: option. None when the block renders its results as a code block, which is the default.

property screenshot_background: TerminalBackground[source]

Chrome the capture is drawn on, set by :screenshot-background:.

Defaults to the dark chrome a terminal and this package’s default theme both look like. A block rendering a light-background theme says so, or its capture washes out: see TerminalBackground.

property screenshot_columns: int | Literal['auto'][source]

Width the capture is laid out at, set by :screenshot-columns:.

Defaults to the fixed width a terminal capture is taken at. auto sizes the image to the longest line the block printed, which is what a run holding a line Click does not wrap needs to keep on one line: a prompt, a wide table, a machine-readable dump.

resolve_animation(expression, option)[source]

Frames the capture animates, set by :screenshot-animate:.

The option is a Python expression, evaluated in the same per-document namespace the block’s own source runs in, so a click:source block above can build the subject and this one picture it. It yields either:

  • a Spinner, whose frame_lines() and interval are taken as they stand, which is the whole declaration for a spinner;

  • a sequence of Frame, as a recording hands back, each carrying the screen it held and for how long. Those durations are wall-clock, so they are rounded onto a grid before they reach the picture, see :screenshot-quantum:;

  • any other sequence of strings, one captured text per frame, whose :screenshot-interval: says how long each is shown.

:screenshot-interval: overrides a spinner’s own when both are stated, and times a recording flatly when it would rather not keep the pace it was recorded at.

Caution

An animated block pictures its frames, not its results. The block still runs, and its results still render on the page as any other block’s do, but they are not what the image shows.

A declared subject keeps the committed asset deterministic outright: the same expression composes the same lines on every build. A recording cannot, its frames being timed by the wall clock, so it is written once: write_screenshot() leaves an asset that carries an @recording line alone.

Return type:

tuple[tuple[str, ...], float | tuple[float, ...]]

Returns:

the frames and how long each is shown, or None when the block draws a still.

Raises:
  • RuntimeError – when the expression cannot be evaluated.

  • TypeError – when it yields neither a spinner nor strings.

  • ValueError – when a bare sequence of frames states no interval.

property screenshot_animation: tuple[tuple[str, ...], float | tuple[float, ...]] | None[source]

Frames a declared animation draws, set by :screenshot-animate:.

Declared frames are composed rather than timed, so they come out the same on every build and the asset is regenerated like any other, which is what keeps it from drifting away from the code. See resolve_animation() for what the expression may yield, and :screenshot-record: for the case that cannot be regenerated.

Returns:

the frames and how long each is shown, or None when the block draws a still.

property emphasis_spec: str | None

The lines the capture bands, as the block spells them.

Only :screenshot-emphasize-lines: on a directive whose image is its output: the block’s three emphasis options then mark three different contents, and folding any two together would band the wrong one.

A block picturing its own code has one content, so :emphasize-lines: marks the same lines in the page’s code block and in the image, and saying it twice would be the surprise. :screenshot-emphasize-lines: still overrides it where the two should differ.

screenshot_emphasis(total)[source]

Lines the capture bands, set by :screenshot-emphasize-lines:.

Takes the same specification :emphasize-lines: does, 1,3-5 and the rest, and counts from one the same way. It is read here rather than at option-parsing time because the specification may name a range open at one end, which only means something once the capture’s height is known.

Parameters:

total (int) – how many lines the capture is drawing.

Return type:

tuple[int, ...]

Returns:

the lines to band, counted from one.

Raises:

ValueError – when the specification names no readable line.

numbered(frames)[source]

Number every frame’s lines when :screenshot-line-numbers: asks.

Counted within each frame rather than once across the animation: a frame is a screen, and a screen’s gutter numbers the rows it is showing. An animation whose rows accumulate therefore numbers more of them as it goes. The gutter is as wide on every frame as on the tallest, so no row moves when the count gains a digit, see number_frames().

Parameters:

frames (tuple[str, ...]) – the animation’s frames.

Return type:

tuple[str, ...]

Returns:

the same frames, each with a gutter, or unchanged.

screenshot_hold(fallback)[source]

Seconds the last frame stays up, set by :screenshot-hold:.

Parameters:

fallback (float | Literal['auto']) – what to hold for when the block states nothing.

Return type:

float | Literal['auto']

Returns:

the pause, in seconds, or the auto sentinel.

property screenshot_syntax_style: str[source]

Pygments style a source capture is colored with.

Set by :screenshot-syntax-style:, falling back to the click_extra_screenshot_syntax_style conf.py value, then to the one the chrome is drawn for. Unused by a directive picturing its output, whose colors the command it ran already chose.

Returns:

the style’s name.

Raises:

ValueError – when the name is not a style Pygments knows.

property screenshot_palette: TerminalPalette | None[source]

Colors the capture resolves against, beyond what the chrome names.

None for a capture of a terminal, which is every run: its colors are the terminal’s own, and the chrome already answers for them. A capture of code takes them from its syntax style instead, background included, so the picture looks like that style does in an editor.

screenshot_lines(results)[source]

The lines the still capture draws, before its gutter is added.

The captured output, with one substitution: a block is run by a documentation build rather than by the terminal it pictures, so it prompts with this platform’s sigil where the drawn terminal has its own.

A block picturing its own code draws that instead, colored by highlight_code() into the same ANSI a command would have printed. It has no prompt to substitute, and results is whatever its runner returned, which for a source block is nothing.

Parameters:

results (Iterable[str]) – what the block’s runner returned.

Return type:

list[str]

Returns:

the lines to draw.

Raises:

ValueError – when the block names a language or style Pygments does not know.

property screenshot_frame: dict[str, Any][source]

Window the capture is drawn in, set by the :screenshot-*: options.

Each entry is left out when its option is: the renderer then picks what the chrome asks for, which is what every block on these pages wants. :screenshot-backdrop:, :screenshot-border: and :screenshot-shadow: take a CSS color (or none to paint neither), :screenshot-border-width:, :screenshot-margin:, :screenshot-padding: and :screenshot-radius: take pixels, :screenshot-opacity: how solid the window’s body is, :screenshot-title: the caption drawn in the window’s own chrome, and :screenshot-watermark: a credit line for the image’s corner, which no capture carries here unless asked for. :screenshot-line-numbers: is a flag, numbering every line the block rendered, its prompt first. :screenshot-cursor: draws a terminal cursor, in the shape it names or in the preset’s own, and :screenshot-blink: says how long one blink takes. See render_svg().

A block naming no preset falls back to the one the click_extra_screenshot_preset conf.py value names, so a project drawing all of its captures as the same terminal states it once.

property invocation: str[source]

The command line the capture draws above itself, styled as a prompt.

Set by :screenshot-prompt:, which states the line a reader would type rather than anything the block ran: a click:run block invokes a Python callable, so there is no argv to recover one from. Styled exactly as a still capture styles its own, see prompt_line().

Note

Animations only. A still block already prints its invocation, so the option would draw it twice; the animated paths are the ones holding what a command drew and nothing about the command line that drew it.

Returns:

the styled line, empty when the block states none.

closed(frames)[source]

Put the shell’s prompt under the last frame, when asked.

:screenshot-closing-prompt: draws the sigil on the row a finished command leaves the shell waiting on, see append_prompt(). The last frame alone carries it: the shell has not come back while the command is still drawing.

Parameters:

frames (tuple[str, ...]) – the animation’s frames, in order.

Return type:

tuple[str, ...]

Returns:

the same frames, the last one closed, or unchanged.

opened(frames, interval)[source]

Draw the invocation over an animation, and type it when asked.

A block records what a command drew and never the command line that drew it, so an animation opens on output arriving from nowhere. :screenshot-prompt: puts the line back over every frame, and :screenshot-typing: opens on it being typed, one character a frame, see type_line().

Parameters:
  • frames (tuple[str, ...]) – the animation’s frames, in order.

  • interval (float | tuple[float, ...]) – how long each is shown, one number for all alike.

Return type:

tuple[tuple[str, ...], float | tuple[float, ...]]

Returns:

the frames and their timing, both grown by the opening.

render_animation(frames, interval, *, hold, blank)[source]

Draw an animated capture of frames, whatever produced them.

The two animated paths agree on everything but what closes a cycle: a recording ends somewhere and pauses on that ending, while a declared animation turns in place and ends nowhere, so it pauses for nothing unless the block asks. Both fallbacks are overridden by :screenshot-hold: and :screenshot-blank:.

Parameters:
  • frames (tuple[str, ...]) – the animation’s frames, in order.

  • interval (float | tuple[float, ...]) – how long each of them is shown.

  • hold (float | Literal['auto']) – seconds the last frame stays up when the block states none.

  • blank (float) – seconds of empty screen closing the cycle when the block states none.

Return type:

str

Returns:

the SVG document.

write_screenshot(results)[source]

Write the captured output as an SVG beside the documentation.

The file lands in the directory the click_extra_screenshot_dir conf.py value names, under the source root, so a README pointing at the repository embeds the very output this page renders live.

This is a side effect, not a rendering: the page keeps its results code block, which stays selectable, searchable and theme-aware where an image would not be. Use :mirror: to put the image on the page as well.

Writing during the build keeps the committed asset in step with the CLI without anyone remembering to refresh it. unique_id is pinned to the asset’s name, so an unchanged CLI draws the same bytes, and the file is then left alone, its modification time included. Only the generator’s release stamps and any output that varies by machine can change.

Note

That refresh only happens when the document carrying the block is re-parsed: Sphinx’s environment cache skips unchanged sources. A change on the package side (a new config format widening a --config default, say) leaves every capture stale until a rebuild with a fresh environment (sphinx-build -E).

Return type:

None

run()[source]

Execute the directive and render its source and results.

Return type:

list[Node]

class click_extra.sphinx.click.SourceDirective(name, arguments, options, content, lineno, content_offset, block_text, state, state_machine)[source]

Bases: ClickDirective

Directive to declare a Click CLI source code.

This directive is used to declare a Click CLI example in the documentation. It renders the source code of the example in a Python code block.

default_language: str = 'python'

Default highlighting language to use to render the code block.

All Pygments’ languages short names are recognized.

show_source_by_default: bool = True

Whether to render the source code of the example in the code block.

show_results_by_default: bool = False

Whether to render the results of the example in the code block.

runner_method: str = 'execute_source'

The name of the method to call on the per-document runner.

A ClickRunner for the click:* directives.

screenshots_source: bool = True

Whether a :screenshot: on this directive pictures the block’s own code.

False for a directive that runs something: what is worth committing there is the output, and the code producing it is on the page already. True for one that only declares code, which has no output to picture and whose subject is the code itself. See screenshot_lines().

class click_extra.sphinx.click.RunDirective(name, arguments, options, content, lineno, content_offset, block_text, state, state_machine)[source]

Bases: ClickDirective

Directive to run a Click CLI example.

This directive is used to run a Click CLI example in the documentation. It renders the results of running the example in a shell session code block supporting ANSI colors.

default_language: str = 'ansi-shell-session'

Default highlighting language to use to render the code block.

All Pygments’ languages short names are recognized.

show_source_by_default: bool = False

Whether to render the source code of the example in the code block.

show_results_by_default: bool = True

Whether to render the results of the example in the code block.

runner_method: str = 'run_cli'

The name of the method to call on the per-document runner.

A ClickRunner for the click:* directives.

source_language: str | None = 'python'

Language the source block is highlighted in, when it is not language.

None for a directive showing code of the language it names: the source block of a click:source follows its :language: option. A directive that runs a block names the language of its results instead, so it states the language of the code producing them here, python for every runner of this package.

click_extra.sphinx.click.rewrite_screenshot_regions(text, directory='assets')[source]

Return text with every capture block’s :mirror: region refreshed.

Only a top-level click:run, click:source, python:run or python:source fence carrying both :screenshot: and :mirror: gets a region, holding the Markdown link to the capture. The walk, the insertion and the idempotency are those of click_extra.markdown.blocks.rewrite_fenced_regions().

The image itself is written by the directive at build time. This only maintains the Markdown pointing at it. The click-extra refresh-directives command calls it on each page, and update_screenshot_blocks() on each file it is given.

Return type:

str

click_extra.sphinx.click.update_screenshot_blocks(paths, *, check=False, directory='assets')[source]

Refresh every capture block’s :mirror: region in the given sources.

See click_extra.markdown.blocks.update_blocks() for the walk, write, and check-mode contract. Unlike the python:render :mirror: refresher, this executes nothing: a region’s content is derived from the block’s :screenshot: name.

Parameters:
  • paths (Iterable[Path]) – Markdown files, or directories recursed for *.md.

  • check (bool) – report what would change without writing.

  • directory (str) – where the captures live, relative to each document.

Return type:

list[Path]

Returns:

the files whose regions were (or, under check, would be) updated.

click_extra.sphinx.click.cleanup_runner(app, doctree)

Drop the ClickRunner from the doctree.

Connected to doctree-read, so it runs once the document is read.

Return type:

None

click_extra.sphinx.click_reference module

Reference directives of the click domain: click:tree and click:config.

Each one evaluates a Python expression in the runner namespace that the click:source and click:run blocks of the page share, and expands what it yields into MyST: a summary table, then one section per command or per configuration option.

ClickDomain lives here too, because it registers every click:* directive: this module imports the ones of click_extra.sphinx.click, which cannot import this module back.

click_extra.sphinx.click_reference.CONFIG_DOCSTRING_HEADING_WARNING = 'click_extra.config_docstring_heading'

Type and subtype of the warning about a heading in a click:config docstring.

List it in suppress_warnings to silence the warning, or in click_extra.sphinx.fail_on_warnings.FAIL_ON_WARNINGS_CONFIG to fail the build on it.

class click_extra.sphinx.click_reference.ReferenceDirective(name, arguments, options, content, lineno, content_offset, block_text, state, state_machine)[source]

Bases: ClickDirective

Base of the directives expanding a Python expression into a MyST reference.

click:tree and click:config both take one required argument, an expression evaluated in the per-document runner namespace, and an optional body of Python run in that namespace first. Both write MyST headings under the section hosting them, so both refuse an rST host.

required_arguments = 1

Number of required directive arguments.

optional_arguments = 0

The optional argument overrides the default Pygments language to use.

resolve_argument()[source]

Run the body, then evaluate the argument in the runner namespace.

The body lets a page inline from mypkg.cli import mycli instead of seeding the namespace with a separate click:source :hide-source: block. Hard errors (not self.error()) so the build fails fast: a partially rendered reference page hides bugs in what the directive was meant to document.

Return type:

tuple[str, Any]

Returns:

the expression as written, and what it evaluated to.

Raises:

RuntimeError – on an rST host, or when the expression cannot be evaluated.

property heading_offset: int[source]

Levels the generated headings are shifted down by.

Set by :heading-offset:. Without it, the headings nest one level below the surrounding section, so the document outline stays consistent wherever the directive is placed: at the document’s top level this resolves to 1, a root rendered at h2 under a document title at h1, and inside an h3 section to 3, a root rendered at h4.

The surrounding level is read from state.memo.section_level, which the MockState of myst-parser fills, never below 1.

class click_extra.sphinx.click_reference.TreeDirective(name, arguments, options, content, lineno, content_offset, block_text, state, state_machine)[source]

Bases: ReferenceDirective

Render a complete CLI reference for a Click command and all its subcommands.

Walks the Click command tree at build time and emits, in MyST syntax:

  • A GFM summary table linking each command to its section anchor.

  • A heading + click:run --help block for the root command.

  • One heading + click:run --help block per subcommand, nested by depth.

The directive walks the live command tree on every build, so the reference cannot drift from the CLI it documents.

The required argument is a Python expression evaluated in the per-document runner namespace; it must yield a click.Command. The optional directive body is Python preamble exec’d in the same namespace before evaluation, so authors may either import the CLI in a prior click:source :hide-source: block or inline the import here.

Note

Currently MyST-only. Use the directive in a .md document with myst_parser enabled.

option_spec: ClassVar[OptionSpec] = {'anchor-prefix': <function unchanged>, 'heading-offset': <function nonnegative_int>, 'label-prefix': <function unchanged>, 'max-depth': <function positive_int>, 'no-root': <function flag>, 'no-table': <function flag>, 'root-label': <function unchanged>}

Recognized directive options.

max-depth caps the recursion into nested click.Group commands (default: 10). heading-offset shifts all generated headings down by N levels. When unset, the directive reads state.memo.section_level and uses the surrounding section depth so the root nests one level below the enclosing section: inside the document’s h1 title this yields 1 (root at h2); inside an h3 section it yields 3 (root at h4). Override only when the auto-detected depth is wrong for the page layout. anchor-prefix and label-prefix override the slug and display prefix used for anchors and labels; both default to the CLI’s click.Command.name. root-label sets the heading text for the root help block (default: "Help screen"). no-table skips the summary table; no-root skips the root --help block.

run()[source]

Execute the directive and render its source and results.

Return type:

list[Node]

class click_extra.sphinx.click_reference.ConfigDirective(name, arguments, options, content, lineno, content_offset, block_text, state, state_machine)[source]

Bases: ReferenceDirective

Render the configuration reference of a CLI’s config_schema.

Introspects a configuration schema dataclass at build time and expands, in MyST syntax:

  • A GFM summary table linking each option to its section anchor, with its one-line summary and default value.

  • One heading per option, with its docstring, type, default, and a TOML example pinned to the default value.

Option metadata comes from schema_field_infos(): dotted kebab-case keys, type annotations, defaults from a pristine schema instance, and attribute docstrings (which are parsed as the host document’s markup). The directive documents the live schema on every build, so the reference cannot drift from the configuration it describes.

The required argument is a Python expression evaluated in the per-document runner namespace; it must yield either a click.Command whose config_schema is set (the schema is pulled off its ConfigOption), or a schema dataclass directly. The optional directive body is Python preamble exec’d in the same namespace before evaluation, so authors may either import the CLI in a prior click:source :hide-source: block or inline the import here.

Caution

Attribute docstrings are recovered from the schema’s source file, so a schema defined inside an exec’d click:source block documents its options without descriptions. Import the schema from a real module instead (see field_docstrings()).

Note

Currently MyST-only. Use the directive in a .md document with myst_parser enabled.

option_spec: ClassVar[OptionSpec] = {'heading-offset': <function nonnegative_int>, 'no-examples': <function flag>, 'no-table': <function flag>, 'section': <function unchanged>}

Recognized directive options.

heading-offset shifts all generated headings down by N levels; when unset, the surrounding section depth is used (same behavior as click:tree). section overrides the TOML table header shown in the per-option examples: it defaults to tool.{cli-name} when the argument is a CLI (matching how click-extra and its downstream CLIs read their section from pyproject.toml), and to no header at all for a bare schema; an explicitly empty :section: suppresses the header. no-table skips the summary table; no-examples skips the TOML example blocks.

run()[source]

Execute the directive and render its source and results.

Return type:

list[Node]

class click_extra.sphinx.click_reference.ClickDomain(env)[source]

Bases: StatelessDomain

Register the directives of the click namespace:

  • click:source which renders a Click CLI source code

  • click:run which renders the results of running a Click CLI

  • click:tree which walks a Click command tree and renders the full --help reference for every subcommand, with a summary table on top

  • click:config which documents a CLI’s config_schema: a summary table plus one section per option, with types, defaults, and TOML examples

name: ClassVar[str] = 'click'

domain name: should be short, but unique

label: ClassVar[str] = 'Click'

domain label: longer, more descriptive (used in messages)

directives: ClassVar[dict] = {'config': <class 'click_extra.sphinx.click_reference.ConfigDirective'>, 'run': <class 'click_extra.sphinx.click.RunDirective'>, 'source': <class 'click_extra.sphinx.click.SourceDirective'>, 'tree': <class 'click_extra.sphinx.click_reference.TreeDirective'>}

directive name -> directive class

click_extra.sphinx.click_runner module

Runner executing the bodies of the click:* directives.

ClickRunner runs every block of a document in one shared namespace, and returns what a terminal would show for it: the invocations, the input, the output, the prompts and the tracebacks. The exec plumbing around it lives here too: the redirected subprocess.call, the program name read off a command line, and the ^D that TerminatedEchoingStdin echoes.

click_extra.sphinx.click_runner.PROMPT_SIGIL = '$'

Sigil a block draws before the command it ran.

Fixed rather than taken from PROMPT, which answers to the platform the build runs on: a page would otherwise prompt with a > for every reader whose documentation was built on Windows. A capture drawn as another terminal swaps it for that terminal’s own, see TerminalPreset.

Passed as the prompt argument of format_cli_prompt(), which is what builds the line for both the results code block and the SVG capture drawn from it.

class click_extra.sphinx.click_runner.TerminatedEchoingStdin(input, output)[source]

Bases: EchoingStdin

Like click.testing.EchoingStdin but adds a visible ^D in place of the EOT character (\x04).

ClickRunner.invoke() adds \x04 when terminate_input=True.

click_extra.sphinx.click_runner.patch_subprocess()[source]

Patch subprocess to work better with ClickRunner.invoke().

subprocess.call output is redirected to click.echo so it shows up in the example output.

Caution

The replacement is installed on the subprocess module itself (not thread-local), so for the duration of the with block any other code in the same process that calls subprocess.call sees the patched version. With parallel_read_safe = True declared on ClickDomain, a parallel reader running concurrently on a different document gets the patched subprocess.call too. The redirection is benign (output goes to click.echo) but the race is real, and the parallel-safe claim is weaker than it looks for documents that themselves shell out via subprocess.call.

click_extra.sphinx.click_runner.program_from_command_line(command_line)[source]

The program name Click is given for a command line a block displays.

A block documenting a multi-word program has to hand Click all of it: the command reads its own name back out of the context, for its usage line and for any output quoting the invocation that produced it, like the provenance header of a Carapace spec. Handing over the last word alone makes an image contradict the prompt drawn right above it.

Only an interpreter prefix is dropped, since it names no program: see _INTERPRETER_RE.

Return type:

str

class click_extra.sphinx.click_runner.ClickRunner(capture=None)[source]

Bases: CliRunner

A sub-class of click.testing.CliRunner for Sphinx directive execution.

Produces unfiltered ANSI codes so that the directives of click_extra.sphinx.click can render colors in the HTML output. Because Click Extra executes the documented command here, invoke() forces color across both color systems a CLI might use: color=True covers Click’s (should_strip_ansi), and forced_color() sets FORCE_COLOR for Rich’s (which rich-click uses and color=True never reaches). The MkDocs plugin shares the latter lever but cannot pass color=True, since it patches a renderer it never executes.

The runner defaults to capture="fd", overridable through the click_extra_run_capture conf.py value. Click then sends file descriptors 1 and 2 to the capture, so a block also renders what a subprocess, a C extension or a direct write to descriptor 1 prints, placed after what the command wrote to sys.stdout. sys.stdout.fileno() returns a copy of the original stdout descriptor instead of raising io.UnsupportedOperation, so a command calling it does not abort the build. But what the command writes through that copy goes to the stdout of the build, not to the page. On Windows, where fd-backed streams are not supported, the default falls back to capture="sys".

without_build_logging()[source]

Take the logging setup of the build off the root logger for the block.

A build is one process, shared with Sphinx, its extensions and its theme, and any of them may set the root logger up: the Furo theme depends on accessible-pygments, which calls logging.basicConfig() when imported, and so gives the root logger a handler and the INFO level.

A documented command adds the handler of Click Extra beside the ones it finds, so each of its records would print a second time, in the format of the build. It also leaves the root logger the level it found, so the one of the build would let through the records a command logs before its verbosity options apply. The shell of a reader has none of this: the root logger holds no handler there, at DEFAULT_LEVEL.

A handler the code of the document attached itself, or a level it set, is not of the build, and stays in place.

Return type:

Generator[None, None, None]

isolation(*args, **kwargs)[source]

Echo a ^D marker at the end of the isolated stdin.

Todo

Delete TerminatedEchoingStdin and this override once the Click floor reaches the release that ships pallets/click#3878, targeted at 8.6.0: CliRunner(echo_stdin=True) then echoes the EOT byte as ^D itself.

invoke(cli, args=None, prog_name=None, input=None, terminate_input=False, env=None, _output_lines=None, _show_prompt=True, **extra)[source]

Like CliRunner.invoke but displays what the user would enter in the terminal for env vars, command arguments, and prompts.

Parameters:
  • terminate_input – Whether to display ^D after a list of input.

  • _output_lines – A list used internally to collect lines to be displayed.

  • _show_prompt – Whether to draw the invocation above the output. Set from the directive’s :show-prompt: / :hide-prompt: options, see show_prompt.

Return type:

Result

execute_source(directive)[source]

Execute the given code, adding it to the runner’s namespace.

Return type:

None

run_cli(directive)[source]

Execute the body of directive and return what a terminal would show.

The lines mix the invocations, the input, the output, the prompts and the tracebacks of every command the body ran.

The body can call these functions without importing them:

  • invoke(): which is the same as ClickRunner.invoke()

  • isolated_filesystem(): A context manager that changes to a temporary directory while executing the block.

Raises:

RuntimeError – when the body assigns a variable named like one of these functions, naming the document line that does.

Return type:

list[str]

click_extra.sphinx.crosshair module

Highlight the row and the column under the pointer in a two-axis table.

A reader of a two-axis table traces each cell back to its row label and its column header. On a wide grid, like a feature comparison across a dozen projects, that means counting columns. This module ships a script that highlights the row and the column of the cell under the pointer, headers included, and the cell itself more strongly.

The script also makes a cell whose whole content is one link clickable over its full area, like a cell holding only a check mark that links to its evidence.

Both apply to the tables click_extra.sphinx.tables marks with CORNER_TABLE_CLASS: the ones whose top-left cell names both axes. Other tables are left as they are. The stylesheet and the script are written into the build rather than packaged as data files.

click_extra.sphinx.crosshair.CROSSHAIR_CSS = '/* Hovered row and column. The row rule matches the `tr` the script marks, and\n * keeps the specificity of the column rule: one notch higher, it would outrank\n * the rule of the hovered cell below. */\ntable td.crosshair-col,\ntable th.crosshair-col,\ntr.crosshair-row > td,\ntr.crosshair-row > th {\n  background-color: var(--color-background-hover, rgba(128, 128, 128, 0.12));\n}\n\n/* The hovered cell, outlined to mark the intersection. */\ntable td.crosshair-cell,\ntable th.crosshair-cell {\n  background-color: var(--color-highlight-on-target, rgba(255, 209, 0, 0.4));\n  outline: 2px solid var(--color-brand-primary, #336790);\n  outline-offset: -2px;\n}\n\n/* A cell whose whole content is one link: the link\'s hit area covers the\n * cell. The link itself is untouched, so keyboard focus and middle-click keep\n * working, and the overlay is transparent, so the highlight shows through. */\ntable td.linked-cell,\ntable th.linked-cell {\n  position: relative;\n  cursor: pointer;\n}\ntable td.linked-cell a[href]::after,\ntable th.linked-cell a[href]::after {\n  content: "";\n  position: absolute;\n  inset: 0;\n}\n'

Stylesheet of the classes the crosshair script sets.

The colors come from Furo’s CSS variables, so the highlight follows Furo’s light and dark modes. Each variable carries a fallback for other themes.

click_extra.sphinx.crosshair.CROSSHAIR_JS = '(function (selector) {\n  function ready(fn) {\n    if (document.readyState !== "loading") {\n      fn();\n    } else {\n      document.addEventListener("DOMContentLoaded", fn);\n    }\n  }\n\n  ready(function () {\n    Array.prototype.forEach.call(document.querySelectorAll(selector), function (table) {\n      // Mark each cell whose whole content is one link. A cell mixing a link\n      // with other text, like a header with a footnote marker, is left alone.\n      Array.prototype.forEach.call(table.querySelectorAll("td, th"), function (cell) {\n        var links = cell.querySelectorAll("a[href]");\n        if (\n          links.length === 1 &&\n          cell.textContent.trim() === links[0].textContent.trim()\n        ) {\n          cell.classList.add("linked-cell");\n        }\n      });\n\n      // A merged cell shifts the cells after it in its row, so a cell index no\n      // longer names a visual column.\n      if (table.querySelector("[colspan], [rowspan]")) {\n        return;\n      }\n\n      function clear() {\n        var marked = table.querySelectorAll(\n          ".crosshair-col, .crosshair-row, .crosshair-cell"\n        );\n        Array.prototype.forEach.call(marked, function (element) {\n          element.classList.remove("crosshair-col", "crosshair-row", "crosshair-cell");\n        });\n      }\n\n      table.addEventListener("mouseover", function (event) {\n        var cell = event.target.closest("td, th");\n        if (!cell || !table.contains(cell)) {\n          return;\n        }\n        var column = cell.cellIndex;\n        clear();\n        // The column: the cell at the same index in every row, header included.\n        Array.prototype.forEach.call(table.rows, function (row) {\n          var columnCell = row.cells[column];\n          if (columnCell) {\n            columnCell.classList.add("crosshair-col");\n          }\n        });\n        cell.parentElement.classList.add("crosshair-row");\n        cell.classList.add("crosshair-cell");\n      });\n\n      table.addEventListener("mouseleave", clear);\n    });\n  });\n})("table.corner-table");\n'

Script setting the crosshair and full-cell link classes on two-axis tables.

The selector of the tables is the argument of the outer function, so the class click_extra.sphinx.tables sets is named in one place.

click_extra.sphinx.crosshair.STYLESHEET = 'click-extra-crosshair.css'

File name of the stylesheet, served from the _static folder.

click_extra.sphinx.crosshair.SCRIPT = 'click-extra-crosshair.js'

File name of the script, served from the _static folder.

click_extra.sphinx.crosshair.add_crosshair(app)[source]

Write CROSSHAIR_CSS and CROSSHAIR_JS into the build.

Both files go to the static folder of the extension (see click_extra.sphinx.tables._write_static_file), and every page links them. Only an HTML builder serves them, so other builders skip it.

Return type:

None

click_extra.sphinx.crosshair.setup(app)[source]

Register the crosshair stylesheet and script on app.

Called from click_extra.sphinx.setup() so projects only need to list "click_extra.sphinx" in their extensions. The tables it applies to are marked by click_extra.sphinx.tables.mark_corner_tables().

Return type:

None

click_extra.sphinx.doctrees module

Keep the build environment out of each pickled page.

Sphinx pickles the doctree of every page it reads. Before it does, it removes the build environment from a copy of the page’s settings. A node that belongs to another document escapes that copy: it pickles its own document, whose settings still hold the whole environment. On a project with an API reference, each page holding such nodes then weighs megabytes, and a build writes hundreds of them.

Two parsers leave such nodes. myst-parser parses an {eval-rst} block into a throwaway document that shares the page’s settings, then moves the parsed nodes into the page. docutils points each moved node at the page, but not its descendants, which keep the throwaway document as theirs. The python:render-rst and python:render-myst directives of click_extra.sphinx.python parse their output into a throwaway document the same way.

This module points every node of a page at the page’s own document, once Sphinx has read the page and before it pickles it.

Todo

Re-scope this module once myst-parser parses an {eval-rst} block into the page’s own document, which executablebooks/MyST-Parser#1212 asks for in its item 3, and the sphinx extra’s myst-parser floor passes that release. The hook then only serves the python:render-* directives, which can point their nodes at the page where they parse them, and the module can go.

click_extra.sphinx.doctrees.rehome_moved_nodes(app, doctree)[source]

Point every node of doctree at doctree itself.

A node moved in from a throwaway document keeps that document, and with it a reference to the build environment, through its settings. Pointing the node at the page lets it pickle without them.

Return type:

None

click_extra.sphinx.doctrees.setup(app)[source]

Register the rehoming hook on app.

Called from click_extra.sphinx.setup() so projects only need to list "click_extra.sphinx" in their extensions. doctree-read is the last event before Sphinx pickles a page, and priority 900 runs the hook after the handlers at the default 500, which can add nodes to the page too.

Return type:

None

click_extra.sphinx.fail_on_warnings module

Fail a Sphinx build on the warning types a project lists, and only on those.

Sphinx fails a build on warnings all or nothing: --fail-on-warning counts every warning, and suppress_warnings can hide a type but never make one fatal. A project that lives with some warnings, like the unresolved references autodoc produces for third-party names, then cannot stop the build on the ones it does not accept.

The main case is myst.xref_missing. myst-parser resolves every `text <#anchor>`_ and `text <page.md#anchor>`_ link against what the build produced. For a link it cannot place, it logs that warning and ships the link as written anyway, so a dead fragment reaches the published site behind a green build.

A handler on the sphinx logger records each warning whose type the project lists in FAIL_ON_WARNINGS_CONFIG. Once the build is over, it reports them in one error and sets the exit status to 1. The build itself runs to its end, so every offending warning is reported, not only the first.

Todo

Contribute this upstream as a Sphinx config value, on sphinx-doc/sphinx#7949, which asks for the same feature and has waited for a design since 2020.

Sphinx already holds both halves. sphinx.util.logging.is_suppressed_warning is the matching this module reuses, and the exit status --fail-on-warning sets is the outcome. A config value read where sphinx.util.logging.WarningSuppressor already counts each warning would replace this module.

Should it land, keep this module as a shim for the Sphinx releases that predate it, then drop it once click-extra’s Sphinx floor moves past them.

click_extra.sphinx.fail_on_warnings.FAIL_ON_WARNINGS_CONFIG = 'click_extra_fail_on_warnings'

Name of the conf.py value listing the warning types that fail the build.

Entries use the syntax of suppress_warnings: myst covers every subtype, myst.xref_missing only that one. Empty by default, which leaves Sphinx’s behavior untouched: which warnings a project can live with is its own call.

class click_extra.sphinx.fail_on_warnings.ListedWarningCollector(app)[source]

Bases: Handler

Collect the warnings whose type the project listed as fatal.

Attached to the sphinx logger rather than to Sphinx’s warning stream, so it sees every warning, whatever --quiet does to the console. A warning that suppress_warnings hides is skipped, the same way --fail-on-warning skips it.

Initializes the instance - basically setting the formatter to None and the filter list to empty.

matched: dict[LogRecord, str]

The type.subtype label of each matched record.

Sphinx buffers the warnings of a build phase and replays them through every handler when the phase ends, after this handler saw them live. A record hashes by identity, so keying on it counts each one once.

emit(record)[source]

Keep record if its type is listed and not suppressed.

Return type:

None

finish(app, exception)[source]

Detach, then fail the build if a listed warning was logged.

Return type:

None

click_extra.sphinx.fail_on_warnings.setup(app)[source]

Register the config value and attach a collector for app.

Called from click_extra.sphinx.setup() so projects only need to list "click_extra.sphinx" in their extensions. The collector reports at priority 900, after the default 500 of other build-finished handlers, so a warning one of them logs still counts.

Return type:

None

click_extra.sphinx.linkcheck module

Sign the linkcheck requests to github.com with a CI token.

GitHub throttles anonymous requests to about one a minute, which overruns the link check of a site with many links to github.com. When the environment holds GITHUB_TOKEN_ENV, as a CI workflow sets it on its link-check step, the requests to github.com carry that token. A local build has no token, so its requests stay anonymous.

Release downloads stay anonymous: with an Authorization header, github.com redirects them to the legacy objects.githubusercontent.com host, whose signed URL answers 401. A page linking one download per released binary would then report every one of them as broken. linkcheck_auth carries the exception because its patterns are regular expressions, where linkcheck_request_headers matches whole hosts only.

click_extra.sphinx.linkcheck.GITHUB_TOKEN_ENV = 'GITHUB_TOKEN'

Environment variable holding the token the requests to github.com carry.

click_extra.sphinx.linkcheck.GITHUB_URL_PATTERN = 'https://github\\.com/(?!.*/releases/(?:latest/)?download/)'

URLs the token signs: every page of github.com, but a release download.

click_extra.sphinx.linkcheck.github_bearer_auth(request)[source]

Sign request with the token GITHUB_TOKEN_ENV holds.

The token is read when the request goes out. A function also stays out of the build environment Sphinx pickles, where an object holding the token would have written it to disk.

Return type:

PreparedRequest

Returns:

the request, as requests expects from an auth callable.

click_extra.sphinx.linkcheck.add_github_auth(app, config)[source]

Append the github.com entry to linkcheck_auth, if a token is set.

The entry goes after the ones conf.py declares. Sphinx signs a request with the first entry whose pattern matches its URL, so a project entry keeps precedence.

Return type:

None

click_extra.sphinx.linkcheck.setup(app)[source]

Register the github.com authentication on app.

Called from click_extra.sphinx.setup() so projects only need to list "click_extra.sphinx" in their extensions. config-inited fires once conf.py is read, so its own linkcheck_auth entries come first.

Return type:

None

click_extra.sphinx.manpages module

Sphinx integration to render roff man pages alongside the HTML build.

A project that adds click_extra.sphinx to its extensions list and declares one or more entries in click_extra_manpages gets its Click command tree(s) emitted as .1 files into <outdir>/<output_dir>/ on every HTML build, with no project-local helper script. Pages mirror what click_extra.command_doc.write_manpages() produces from a CLI invocation, so the docs site, the release pipeline, and downstream packagers all share one generator.

When mandoc or groff is available on PATH, each .1 file is also rendered to a browser-viewable .html sibling. Browsers download raw .1 files rather than display them, so the HTML pass is what makes Sphinx’s :manpage: role useful when manpages_url points at this hook’s output.

The hook only fires for HTML-class builders (html, dirhtml, singlehtml). Non-HTML builders (linkcheck, man, epub, coverage, etc.) skip it: they typically have different output semantics, and writing roff into a linkcheck output/ directory serves no purpose.

Configuration shape:

click_extra_manpages = [
    {
        "script": "meta_package_manager.cli:mpm",  # required
        "prog_name": "mpm",  # optional, see below
        "output_dir": "man",  # optional, defaults to "man"
        "render_html": True,  # optional, see below
    },
]
  • script is resolved by click_extra.cli_wrapper.resolve_target_command() exactly as it would be from the click-extra man CLI: a console_scripts entry-point name, a module:function path, a .py file, or a plain module name.

  • prog_name is the basename used for both the man-page .TH header and the generated filenames. When omitted, it falls back to the resolved Click command’s own name attribute (mpm for the meta_package_manager.cli:mpm target), matching the default the click-extra man --output-dir CLI uses.

  • output_dir is a relative path under app.outdir. It is created on demand and reused across builds.

  • render_html toggles the HTML sibling pass. Defaults to True. When no renderer is on PATH, the build still produces the .1 files and logs a single info-level notice; set render_html to False to suppress that notice.

An empty (or absent) click_extra_manpages list disables the feature, which is the default for every project pulling in the extension.

Cross-referencing the generated pages from prose

To make :manpage:`myprog(1)` resolve to the HTML sibling this hook emits, set Sphinx’s manpages_url to the same output_dir:

manpages_url = "man/{page}.{section}.html"

Sphinx’s role provides {page}, {section} and {path} placeholders; the file layout produced here is {page}.{section} plus the optional .html extension, so the template above matches every file regardless of how deep the subcommand tree goes.

click_extra.sphinx.manpages.MANPAGES_CONFIG_KEY = 'click_extra_manpages'

Name of the conf.py config flag holding the man-page emit list.

click_extra.sphinx.manpages.DEFAULT_OUTPUT_DIR = 'man'

Subdirectory under app.outdir where .1 files land when the caller omits the output_dir entry. Picked to match the URL fragment projects typically publish their man pages under (like https://example.com/<project>/man/<cli>.1).

click_extra.sphinx.manpages.HTML_BUILDER_NAMES = frozenset({'dirhtml', 'html', 'singlehtml'})

Builder names that get the man-page emit hook.

Restricted to HTML-family builders because they are the ones whose output directory becomes the published docs site. Other builders (linkcheck, man, epub, coverage) have different output semantics, and writing roff into their build trees would either be redundant or confusing.

click_extra.sphinx.manpages.MANPAGE_LIST_DIRECTIVE = 'click-extra-manpages'

Name of the directive that renders an auto-generated index of every man page declared in MANPAGES_CONFIG_KEY. The hyphenated form mirrors the click_extra_manpages config key it surfaces.

class click_extra.sphinx.manpages.ManpageListDirective(name, arguments, options, content, lineno, content_offset, block_text, state, state_machine)[source]

Bases: SphinxDirective

Render a bullet list with one link per emitted man page.

The directive walks every entry in MANPAGES_CONFIG_KEY and, for each, calls iter_command_contexts() to discover the (sub)command tree. Each list item links to the corresponding .1.html file written by the emit hook.

Link targets are stamped as build-root-relative URIs and rebased against the enclosing page by _rebase_manpage_links, so the list works whether it appears at the docs root or in a nested page. The directive takes no arguments and no content: it surfaces whatever the config declares at the time the doc is built.

has_content = False

May the directive have content?

required_arguments = 0

Number of required directive arguments.

optional_arguments = 0

Number of optional arguments after the required arguments.

run()[source]
Return type:

list[Node]

click_extra.sphinx.manpages.setup(app)[source]

Register the man-page hooks and the index directive on app.

Called from click_extra.sphinx.setup() so projects only need to list "click_extra.sphinx" in their extensions. The hooks are a no-op when click_extra_manpages is unset or empty, and the directive renders nothing in that case.

Return type:

None

click_extra.sphinx.matrix module

Release compatibility matrices derived from a project’s git history.

Walk every vX.Y.Z tag in a project’s git repository, extract each release’s declared support for some axis, group consecutive tags that agree, and render a GitHub-flavored markdown matrix suitable for a project’s install.md.

Two axes are supported:

  • Python ({matrix} python): per-tag support comes from the Programming Language :: Python :: X.Y classifier list in pyproject.toml (the explicit tested grid), falling back in priority order to PEP 621 requires-python, Poetry [tool.poetry.dependencies].python, and setup.py’s python_requires. A floor-only declaration is capped at the latest Python released while the range was current, so the ✅ set does not over-claim support for Pythons that did not yet exist. Cells are three-valued: the classifier list drives ✅, requires-python drives ❌, and a version neither attested nor ruled out renders as – (see UNDECLARED_CELL).

  • A dependency ({matrix} <distribution>, like {matrix} click): the per-tag constraint is that distribution’s requirement specifier; each column covers a run of that distribution’s releases on PyPI (plus the uv.lock resolved version) that every range treats alike. The specifier, read with packaging, drives ✅ and ❌, and a release it accepts but that came out after the range was superseded renders as –, like a future Python. So does a release of a major that came out after the last release of a superseded range: no release of that range ever met it.

Both axes then keep only the support envelope of the project (see _envelope()): the ranges from the newest one down to the oldest one that supports the floor of the newest range or a later version, and the versions from the oldest one these ranges support. The <!-- envelope --> region lists the obsolete bounds of the envelopes of several axes: see envelope_bounds().

The rendered tables back the always-on matrix Sphinx directive (see MatrixDirective), so a project’s install.md can embed a live matrix kept current by the click-extra refresh-directives command instead of a static table maintained by hand. The generation functions shell out to git; they carry no runtime CLI relevance and are kept out of the click_extra public API.

click_extra.sphinx.matrix.PYTHON_RELEASE_DATES: dict[str, str] = {'2.7': '2010-07-03', '3.0': '2008-12-03', '3.1': '2009-06-27', '3.10': '2021-10-04', '3.11': '2022-10-24', '3.12': '2023-10-02', '3.13': '2024-10-07', '3.14': '2025-10-07', '3.15': '2026-10-09', '3.2': '2011-02-20', '3.3': '2012-09-29', '3.4': '2014-03-16', '3.5': '2015-09-13', '3.6': '2016-12-23', '3.7': '2018-06-27', '3.8': '2019-10-14', '3.9': '2020-10-05'}

ISO release date of each X.Y Python final release.

Used to cap ✅ cells when a release range’s Python support is derived from a requires-python-style floor rather than an explicit classifier list: a floor without upper bound would otherwise over-claim support for Pythons that did not yet exist while the range was current. Update this table each October when a new final release ships.

click_extra.sphinx.matrix.SUPPORTED_CELL: str = '✅'

Cell marking a version the release declares support for.

click_extra.sphinx.matrix.FORBIDDEN_CELL: str = '❌'

Cell marking a version the release’s own metadata rules out.

Reserved for a hard incompatibility: a version an installer refuses outright because it falls below the declared floor, on or above the declared ceiling, or inside an exclusion clause.

click_extra.sphinx.matrix.UNDECLARED_CELL: str = '–'

Cell marking a version the release neither supports nor forbids.

Covers the flavours of absence of evidence: a version its declaration lets through but that came out after the range was superseded or belongs to a major that came out after its last release, and, on the python axis, a Python that was released but never added to the classifier list. Neither is a statement of incompatibility, so spelling them FORBIDDEN_CELL would over-claim. Spelling the first one SUPPORTED_CELL would over-claim the other way: an open-ended specifier like click>=8.0 accepts every future release, whether it works or not.

click_extra.sphinx.matrix.DEFAULT_TAG_PATTERN: str = '^v\\d+\\.\\d+\\.\\d+$'

Default regex for release tags (vMAJOR.MINOR.PATCH).

click_extra.sphinx.matrix.DEFAULT_TAGS_SORT: str = 'version:refname'

Default git tag --sort argument.

click_extra.sphinx.matrix.NEWEST_FIRST: str = 'newest-first'

Ordering value: highest version / most recent release first.

click_extra.sphinx.matrix.OLDEST_FIRST: str = 'oldest-first'

Ordering value: lowest version / oldest release first.

click_extra.sphinx.matrix.ORDER_CHOICES: tuple[str, ...] = ('newest-first', 'oldest-first')

Allowed values of the column-order and row-order matrix options.

click_extra.sphinx.matrix.PYPI_JSON_URL: str = 'https://pypi.org/pypi/{name}/json'

PyPI’s JSON API endpoint for a project, formatted with its normalized name.

click_extra.sphinx.matrix.PYPI_TIMEOUT: float = 10

Seconds to wait for PyPI before the release lookup gives up.

click_extra.sphinx.matrix.POETRY_VERSION_PATTERN: str = '(?P<release>\\d+(?:\\.\\d+){0,2})(?P<suffix>(?:[-_.]?[a-z]+\\d*)*)'

The version a Poetry caret or tilde range opens on.

Up to three release components, then an optional PEP 440 pre-, post- or dev-release suffix. The suffix stays on the floor and plays no part in the ceiling, as poetry-core reads it: ^2.0.0.post1 means >=2.0.0.post1,<3.0.0.

click_extra.sphinx.matrix.POETRY_CARET_RE = re.compile('^\\^\\s*(?P<release>\\d+(?:\\.\\d+){0,2})(?P<suffix>(?:[-_.]?[a-z]+\\d*)*)$', re.IGNORECASE)

Poetry’s caret range.

click_extra.sphinx.matrix.POETRY_TILDE_RE = re.compile('^~\\s*(?P<release>\\d+(?:\\.\\d+){0,2})(?P<suffix>(?:[-_.]?[a-z]+\\d*)*)$', re.IGNORECASE)

Poetry’s tilde range.

Deliberately also matches a bare ~X. It cannot swallow PEP 440’s ~=, whose = is not a digit.

click_extra.sphinx.matrix.POETRY_WILDCARD_RE = re.compile('^(?:\\*|(\\d+)(?:\\.(\\d+))?\\.\\*)$')

Poetry’s wildcard range.

A bare * allows any version at all. X.* and X.Y.* pin their series, without PEP 440’s leading ==.

class click_extra.sphinx.matrix.PythonMatrixGroup(first_tag, last_tag, first_date, python_versions, spec='')[source]

Bases: NamedTuple

A contiguous run of release tags sharing the same Python support set.

Create new instance of PythonMatrixGroup(first_tag, last_tag, first_date, python_versions, spec)

first_tag: str

First tag in the group (in git tag --sort=version:refname order).

last_tag: str

Last tag in the group.

first_date: str

ISO YYYY-MM-DD date of the first tag’s commit.

python_versions: tuple[str, ...]

The X.Y Python versions this range supports, sorted ascending.

spec: str

The range’s declared requires-python, raw and unparsed.

Kept alongside the supported set because the two answer different questions: the set is what the release claims (its classifier list), while the spec is what an installer enforces. A version outside the spec is a hard incompatibility, a version merely missing from the set is an undeclared one. See UNDECLARED_CELL.

click_extra.sphinx.matrix.python_versions_released_by(cutoff_date, release_dates=None)[source]

Return Python X.Y versions released on or before cutoff_date.

Parameters:
Return type:

list[str]

Returns:

sorted list of X.Y strings.

click_extra.sphinx.matrix.parse_python_spec(spec)[source]

Parse a Python version spec into (floor, ceiling, excluded).

Supports:

  • PEP 440: >=3.10, >=3.10,<3.14, >=2.7, !=3.0.*, !=3.1.*.

  • Poetry caret: ^3.7 expands to >=3.7, <4.0.

  • Poetry tilde: ~3.7 expands to >=3.7, <3.8.

  • setup.py’s python_requires (uses the same PEP 440 syntax as requires-python).

Return type:

tuple[str, str, set[str]]

Returns:

(floor, ceiling, excluded) where each of floor and ceiling is a bare "X.Y" string (empty if unspecified) and excluded is the set of bare "X.Y" values one per !=X.Y.* clause. The ceiling is exclusive: <3.14 means 3.14 itself is not supported. Returns ("", "", set()) when no floor is found.

click_extra.sphinx.matrix.memoized_history()[source]

Serve every git read made inside from one memo.

A refresh renders a block per axis on every page of a project, and each block walks the same tags of the same working tree. Inside this context the tag listing and each tag’s release files are read once, whatever the floor or pattern each block applies. The memo lives no longer than the context: a tag pushed between two refreshes is seen by the second. Nesting reuses the outer memo.

Return type:

Generator[None, None, None]

click_extra.sphinx.matrix.python_matrix_groups(project_root, *, tag_pattern='^v\\d+\\.\\d+\\.\\d+$', tags_sort='version:refname', version_floor='', release_dates=None)[source]

Walk every release tag in project_root and group consecutive releases that declare the same effective set of Python versions.

Parameters:
  • project_root (Path) – git working tree to walk.

  • tag_pattern (str) – regex matching release tags. Defaults to DEFAULT_TAG_PATTERN (vMAJOR.MINOR.PATCH).

  • tags_sort (str) – value passed to git tag --sort. Defaults to DEFAULT_TAGS_SORT (version:refname).

  • version_floor (str) – drop every release tag below this bare version ("4.9.0", "4.9"). Empty (the default) keeps all tags. Applied before grouping so the oldest surviving group starts at the floor.

  • release_dates (dict[str, str] | None) – Python-version release-date table used for the cap. Defaults to PYTHON_RELEASE_DATES.

Return type:

list[PythonMatrixGroup]

Returns:

List of PythonMatrixGroup, in chronological order. Tags with no Python declaration in any recognized form are skipped.

click_extra.sphinx.matrix.python_matrix_table(project_root, label, *, tag_pattern='^v\\d+\\.\\d+\\.\\d+$', tags_sort='version:refname', python_floor='', version_floor='', column_order='newest-first', row_order='newest-first', show_date=False, full_history=False, release_dates=None)[source]

Render the Python compatibility matrix as a GitHub-flavored markdown table.

By default newest releases sit on top and newest Python versions on the left, so the most recent compatibility information always sits in the upper-left corner of the table; row_order and column_order flip either axis.

The table keeps the support envelope of the project (see _envelope()). Consecutive ranges whose cells then coincide merge into one row.

Parameters:
  • project_root (Path) – git working tree to walk.

  • label (str) – the header column name (usually the package name, like "click-extra" or "repomatic"). Rendered in backticks.

  • tag_pattern (str) – passed to python_matrix_groups().

  • tags_sort (str) – passed to python_matrix_groups().

  • python_floor (str) – drop every Python X.Y column below this bare version ("3.9"). Empty (the default) sets no floor. The support envelope reads its floor from the columns left (see _envelope()): a python_floor above the oldest Python of the newest range raises that floor, which drops the ranges below the oldest one supporting it or a later Python. Under full_history, python_floor drops columns only.

  • version_floor (str) – passed to python_matrix_groups() to drop release rows below a bare package version.

  • column_order (str) – left-to-right ordering of the Python columns: NEWEST_FIRST (default) or OLDEST_FIRST.

  • row_order (str) – top-to-bottom ordering of the release rows: NEWEST_FIRST (default) or OLDEST_FIRST.

  • show_date (bool) – follow each row label with the release date of its first tag, like 6.0.x (2025-09-25) → 9.x.

  • full_history (bool) – keep every release range, not just the support envelope.

  • release_dates (dict[str, str] | None) – passed to python_matrix_groups().

Return type:

str

Returns:

rendered markdown table, or the empty string when no group was collected.

Raises:

ValueError – on an unrecognized column_order or row_order.

class click_extra.sphinx.matrix.DependencyMatrixGroup(first_tag, last_tag, first_date, last_date, spec)[source]

Bases: NamedTuple

A contiguous run of release tags declaring the same dependency spec.

Create new instance of DependencyMatrixGroup(first_tag, last_tag, first_date, last_date, spec)

first_tag: str

First tag in the group (in git tag --sort=version:refname order).

last_tag: str

Last tag in the group.

first_date: str

ISO YYYY-MM-DD date of the first tag’s commit.

last_date: str

ISO YYYY-MM-DD date of the last tag’s commit.

spec: str

The raw requirement specifier declared for the dependency at this range.

exception click_extra.sphinx.matrix.ReleasesUnreadable[source]

Bases: Exception

PyPI could not answer for a distribution’s releases.

Raised by _pypi_releases() only when its caller asked for the read to be required, which the source refresh in _regenerate() does. Live rendering never asks, and keeps the fallback columns.

click_extra.sphinx.matrix.dependency_matrix_groups(project_root, dep_name, *, tag_pattern='^v\\d+\\.\\d+\\.\\d+$', tags_sort='version:refname', version_floor='')[source]

Group consecutive release tags declaring the same dep_name spec.

Specifiers are compared parsed (see _spec_identity()), so a re-spelled specifier continues its range: cloup >= 3.0.7 rewritten as cloup>=3.0.7 by a formatter is no new range. Splitting there would end the older half early, and every release of the dependency published after that would read as undeclared for it. A group keeps the spelling of its first tag.

Parameters:

dep_name (str) – the distribution whose requirement specifier is tracked (like "click").

Return type:

list[DependencyMatrixGroup]

Returns:

DependencyMatrixGroup list in chronological order; tags with no declared requirement for dep_name are skipped.

click_extra.sphinx.matrix.dependency_matrix_table(project_root, label, dep_name, *, show_spec=False, show_date=False, tag_pattern='^v\\d+\\.\\d+\\.\\d+$', tags_sort='version:refname', version_floor='', column_order='newest-first', row_order='newest-first', full_history=False, require_releases=False)[source]

Render the dep_name compatibility matrix as a markdown table.

Each cell classifies a release of dep_name against a range (see _dependency_cell()). Each column covers a run of releases that every shown range treats alike (see _column_candidates() and _dependency_columns()), labeled like the rows. Headers read from their newest bound, 9.4.x ← 9.3.4, while the columns run newest first. The table keeps the support envelope of the project (see _envelope()), and consecutive ranges whose cells coincide merge into one row. By default newest releases sit on top and newest dependency versions on the left, matching the Python axis; row_order and column_order flip either axis.

Parameters:
  • label (str) – header column name (the documented package, in backticks).

  • dep_name (str) – the tracked distribution ("click").

  • show_spec (bool) – add a Spec column with each range’s raw specifier. A merged row shows the specifier of its oldest range, so ranges then merge only when they also accept the same versions (see _spec_identity()).

  • show_date (bool) – follow each row label with the release date of its first tag, like 6.0.x (2025-09-25) → 9.x.

  • column_order (str) – left-to-right ordering of the version columns: NEWEST_FIRST (default) or OLDEST_FIRST.

  • row_order (str) – top-to-bottom ordering of the release rows: NEWEST_FIRST (default) or OLDEST_FIRST.

  • full_history (bool) – keep every release range, not just the support envelope.

  • require_releases (bool) – refuse the offline fallback columns, for a caller comparing the result against a checked-in table.

Return type:

str

Returns:

rendered markdown table, or "" when nothing was collected.

Raises:
  • ValueError – on an unrecognized column_order or row_order.

  • ReleasesUnreadable – under require_releases, when PyPI cannot answer for dep_name.

class click_extra.sphinx.matrix.EnvelopeBound(subject, version, date)[source]

Bases: NamedTuple

An obsolete bound of the support envelopes of several axes.

The release bound is the oldest release every envelope keeps. A version bound is the oldest version that a range in scope supports, which can be newer than the oldest column of the matrix of its axis (see envelope_bounds()).

Create new instance of EnvelopeBound(subject, version, date)

subject: str

What the bound limits: the package releases, or the versions of an axis.

version: str

The oldest release in scope, or the oldest version a range in scope supports.

date: str

The ISO release date of that release or version, empty when unknown.

click_extra.sphinx.matrix.envelope_bounds(project_root, package, axes, *, tag_pattern='^v\\d+\\.\\d+\\.\\d+$', tags_sort='version:refname', require_releases=False)[source]

Return the obsolete bounds of the support envelopes of axes.

Each axis keeps its own envelope, as its matrix does (see _envelope()). A release is in scope when every envelope that leaves out releases keeps it, so the release bound is the newest of their first releases. Each version bound is then the oldest version a range in scope supports, read over those ranges alone: it can be newer than the oldest version of the axis’ matrix, whose older rows support older versions.

A release is dated by its tag, a Python by its final release (see PYTHON_RELEASE_DATES), and a dependency release by PyPI.

Parameters:
  • package (str) – the documented package, named by the release bound.

  • axes (Iterable[str]) – python, or the names of tracked distributions. An axis the history never declares is skipped.

  • require_releases (bool) – passed to _pypi_releases().

Return type:

list[EnvelopeBound]

Returns:

the release bound first, when an envelope leaves out releases, then one version bound per axis, in the order of axes.

click_extra.sphinx.matrix.envelope_list(project_root, package, axes, *, tag_pattern='^v\\d+\\.\\d+\\.\\d+$', tags_sort='version:refname', require_releases=False)[source]

Render the obsolete bounds of the envelopes of axes as a Markdown list.

One item per bound of envelope_bounds(), which takes every parameter. An item names what the bound limits and the oldest version in scope, then the release date of that version when known.

Return type:

str

Returns:

the list, or the empty string when no axis has any data.

class click_extra.sphinx.matrix.MatrixDirective(name, arguments, options, content, lineno, content_offset, block_text, state, state_machine)[source]

Bases: ContentOffsetMixin

Render a package’s compatibility matrix for a given axis.

{matrix} python renders the interpreter matrix (release ranges × Python versions). {matrix} <distribution> (like {matrix} click) renders a dependency matrix (release ranges × that dependency’s versions, from its requirement specifier across the git history). Both emit a GitHub-flavored table parsed by the host document’s parser, so it lands as a real <table>.

The table normally lives inside the block as its content, kept current by the offline updater (update_matrix_blocks(), exposed as the click-extra refresh-directives command). Rendering that embedded copy needs no git access at build time, so shallow clones and read-only build hosts still show the matrix. An empty block falls back to generating from the working tree’s git tags, so a freshly authored block renders before its first refresh.

Argument: the axis, python or a distribution name.

Options:

  • :package: sets the header column label. Defaults to the name declared in the project’s pyproject.toml, then to the name of its folder.

  • :path: names the git working tree to walk, absolute or relative to the documented project’s git root. Defaults to that git root.

  • :version-floor: drops the release rows below this package version.

  • :tag-pattern: is the regex selecting release tags. Defaults to DEFAULT_TAG_PATTERN.

  • :column-order: orders the version columns from left to right: newest-first (default) or oldest-first.

  • :row-order: orders the release rows from top to bottom: newest-first (default) or oldest-first.

  • :python-floor: drops the Python columns below X.Y (python axis).

  • :show-spec: adds a raw-specifier Spec column (dependency axis).

  • :show-date: follows each row label with its first release date.

  • :full-history: keeps every release range, not just the support envelope (see _envelope()).

The git fallback is resilient: a missing git binary, a non-repository path, or a tag-less repository logs a build warning and renders nothing rather than aborting the build.

has_content = True

May the directive have content?

required_arguments = 1

Number of required directive arguments.

optional_arguments = 0

Number of optional arguments after the required arguments.

option_spec: ClassVar[OptionSpec] = {'column-order': <function _order_option>, 'full-history': <function flag>, 'package': <function unchanged>, 'path': <function unchanged>, 'python-floor': <function unchanged>, 'row-order': <function _order_option>, 'show-date': <function flag>, 'show-spec': <function flag>, 'tag-pattern': <function unchanged>, 'version-floor': <function unchanged>}

Mapping of option names to validator functions.

run()[source]
Return type:

list[Node]

click_extra.sphinx.matrix.setup(app)[source]

Register the always-on matrix directive on app.

Called from click_extra.sphinx.setup() so projects only need to list "click_extra.sphinx" in their extensions. Unlike the click:* / python:* families, the directive is registered unconditionally: it runs a canned matrix generator, not user-supplied Python, so it needs no opt-in.

Return type:

None

click_extra.sphinx.matrix.rewrite_matrix_blocks(text, base_dir)[source]

Return text with every matrix block’s table regenerated.

Handles both forms: the {matrix} <axis> directive fence (rendered by Sphinx) and the <!-- matrix <axis> --> comment region (which renders as a real table on GitHub too). Each keeps its axis and options; only the embedded table is replaced. Every <!-- envelope <axis>... --> region gets its list of obsolete bounds regenerated too (see envelope_list()). The walk is fence-aware: a {matrix} example nested inside a longer code fence is a documented illustration, copied verbatim and never refreshed. A block whose generation fails (non-repository path, missing git, unreadable PyPI, no data) is left byte-for-byte untouched.

Return type:

str

click_extra.sphinx.matrix.update_matrix_blocks(paths, *, check=False)[source]

Refresh every {matrix} block and envelope region of Markdown sources.

See click_extra.markdown.blocks.update_blocks() for the walk, write, and check-mode contract.

Every block of the refresh reads the git history through one memo, see memoized_history(): a project’s tags are listed once, and each tag’s release files read once, however many pages and axes render them.

Return type:

list[Path]

Returns:

the files whose {matrix} blocks were (or, under check, would be) updated.

click_extra.sphinx.mermaid module

Let the page scroll over the class diagrams of autoclasstree.

With mermaid_d3_zoom = True, sphinxcontrib-mermaid attaches d3’s wheel and drag handlers to the <svg> of every diagram on a page. It has no per-diagram opt-out, and the zoom of its fullscreen viewer needs the same option. A class inheritance tree is often taller than the screen, and the wheel then zooms the diagram where the reader wants to scroll the page.

This module overrides the autoclasstree directive of sphinxcontrib-mermaid with a subclass that wraps each diagram in a container of the AUTOCLASSTREE_CLASS class. NO_ZOOM_CSS turns off the pointer events of the inline <svg> in that container: the events never reach it, d3’s handlers never fire, and the wheel scrolls the page. The fullscreen viewer clones the diagram outside the container, so its button and its zoom still work. The stylesheet is written into the build rather than packaged as a data file.

The module is inert on a project that does not load sphinxcontrib.mermaid, or that leaves mermaid_d3_zoom off.

click_extra.sphinx.mermaid.MERMAID_EXTENSION = 'sphinxcontrib.mermaid'

Module name of the extension whose autoclasstree directive is overridden.

click_extra.sphinx.mermaid.AUTOCLASSTREE_CLASS = 'autoclasstree'

HTML class of the container wrapping each autoclasstree diagram.

click_extra.sphinx.mermaid.NO_ZOOM_CSS = "/* With pointer events off, the inline <svg> never becomes an event target, so\n * d3's wheel and drag handlers never fire, and the wheel scrolls the page. The\n * fullscreen viewer clones the diagram outside the container, so its button\n * and its zoom keep working. */\n.autoclasstree pre.mermaid svg {\n  pointer-events: none;\n}\n"

Stylesheet keeping the pointer off the inline diagram of each container.

click_extra.sphinx.mermaid.STYLESHEET = 'click-extra-mermaid.css'

File name of the stylesheet, served from the _static folder.

click_extra.sphinx.mermaid.override_class_diagrams(app)[source]

Register the zoom-free autoclasstree and its stylesheet on app.

Connected to builder-inited, which fires once every extension is set up, whatever their order in extensions: the directive to override is then registered, and mermaid_d3_zoom holds the value of conf.py. Inert without sphinxcontrib.mermaid, or with mermaid_d3_zoom off. Only an HTML builder serves the stylesheet, so other builders get the directive alone.

Return type:

None

click_extra.sphinx.mermaid.setup(app)[source]

Register the class diagram override on app.

Called from click_extra.sphinx.setup() so projects only need to list "click_extra.sphinx" in their extensions.

Return type:

None

click_extra.sphinx.myst_docstrings module

Convert MyST-flavored docstrings to reST for sphinx.ext.autodoc.

Hooks into autodoc-process-docstring to convert the MyST markdown of Python docstrings to reStructuredText before Sphinx processes them, so sphinx_autodoc_typehints, autodoc_default_options and every other extension built on sphinx.ext.autodoc keep working. It replaces sphinx-autodoc2, whose native MyST parsing does not work with current Sphinx and docutils. See MyST docstrings for setup, the syntax reference and the limitations.

The conversion is idempotent: docstrings already in reST pass through unchanged. This allows incremental migration one module at a time.

Supported conversions:

  • Cross-references: a MyST role, its name in braces, becomes a reST role, its name between colons.

  • Directive fences, with backticks or colons, become reST directives, their leading option lines right under the directive header.

  • Plain code fences become code-block directives. The content of a code block is kept verbatim.

  • A fence longer than three characters can hold fences of its own character, as in any MyST document. click_extra.markdown.blocks.fence_spans() reads the fences.

  • Inline code spans get the double backticks of reST literals.

  • Markdown links become reST hyperlinks, without the backticks of their label.

  • Footnote references and definitions become auto-numbered reST footnotes.

Field list markers (:param:, :return:) need no conversion; the content inside field list entries is converted normally (inline code, cross-references, links).

Register this extension in your Sphinx conf.py, before sphinx_autodoc_typehints if present, with click-extra[sphinx] in your docs dependency group:

extensions = [
    "sphinx.ext.autodoc",
    "click_extra.sphinx.myst_docstrings",
    "sphinx_autodoc_typehints",  # must come after
]
click_extra.sphinx.myst_docstrings.myst_to_rst(lines)[source]

Convert MyST syntax to reST, modifying lines in place.

The conversion is idempotent: reST-only docstrings pass through unchanged because none of the patterns match reST syntax.

Return type:

None

click_extra.sphinx.myst_docstrings.setup(app)[source]

Sphinx extension entry point.

Raises:

sphinx.errors.ExtensionError – If sphinx_autodoc_typehints is already loaded.

click_extra.sphinx.python module

Generic Python execution directives for Sphinx documentation.

Sibling to click_extra.sphinx.click. The Click directives execute a Click CLI and render its terminal output; these execute arbitrary Python and render either captured stdout (python:run) or live document content parsed from the captured output (python:render, python:render-myst, python:render-rst).

The render trio lets a documentation page embed dynamically generated tables, references, or whole sections without a separate docs_update.py regenerator script: the content stays in sync with its source by construction.

Each render variant has a clear parser contract:

  • python:render parses the captured output with whatever parser owns the host document. Use it when the generated markup matches the host file format (MyST inside .md, reST inside .rst).

  • python:render-myst always parses the captured output as MyST, regardless of host. Lets .rst documents embed MyST-generated content.

  • python:render-rst always parses the captured output as reST, regardless of host. Lets .md documents embed reST-generated content.

On top of live rendering, python:render accepts a :mirror: flag. A mirror block keeps a copy of its generated Markdown in the source .md, between MIRROR_MARKER_START / MIRROR_MARKER_END markers directly below the fence, so the output is reviewable in place and renders on GitHub. The committed region is refreshed by update_mirror_blocks() (behind the click-extra refresh-directives command, alongside the {matrix} blocks of click_extra.sphinx.matrix), while Sphinx builds always render the fresh output by regenerating the region in memory at source-read time: the marker region of a docs_update.py script, with the generator inlined into the page.

click_extra.sphinx.python.MIRROR_MARKER_START = '<!-- mirror -->'

Opening marker of a python:render :mirror: region.

Written on its own line, directly below the fence, and paired with MIRROR_MARKER_END. Everything between the two markers is regenerated by update_mirror_blocks(): edit the Python block above the marker, never the mirrored region itself. Same <!-- name --> / <!-- name-end --> grammar as the <!-- matrix … --> regions of click_extra.sphinx.matrix.

click_extra.sphinx.python.MIRROR_MARKER_END = '<!-- mirror-end -->'

Closing marker of a :mirror: region. See MIRROR_MARKER_START.

click_extra.sphinx.python.MIRROR_SRC_MARKER_END = '<!-- mirror-src-end -->'

Closing marker of a <!-- mirror-src … --> region.

The comment form of :mirror:: the generator Python lives inside an <!-- mirror-src … --> HTML comment instead of a visible {python:render} fence, so the generated body renders on GitHub or PyPI (a README diagram, a compatibility table) while the generator stays invisible. Everything between the opening comment’s closing --> and this marker is regenerated by update_mirror_blocks(); edit the comment, never the body.

class click_extra.sphinx.python.PythonRunner(capture=None)[source]

Bases: object

Stateful runner for python:* directives.

Holds a per-document namespace so consecutive directive blocks share imports and variables. One instance is attached to state.document on first use and dropped at doctree-read time by cleanup_python_runner().

namespace: dict[str, object]
execute_source(directive)[source]

Execute the directive’s content with no output capture.

Used by PythonSourceDirective when the example is run only for its side effects (typically to seed imports for a follow-up python:run block).

Return type:

None

run_python(directive)[source]

Execute the directive’s content and capture stdout.

Returns the captured output as a list of lines, suitable for either a code-block render (python:run) or a live nested-parse pass (the python:render directives).

Return type:

list[str]

class click_extra.sphinx.python.PythonDirective(name, arguments, options, content, lineno, content_offset, block_text, state, state_machine)[source]

Bases: ClickDirective

Base class for python:* directives.

Reuses the option spec and rendering scaffolding of ClickDirective, but rebinds the per-document runner so the Click and Python namespaces don’t collide within the same document.

runner_attr: ClassVar[str] = 'python_runner'

Name of the attribute holding the runner on the doctree.

Subclasses (like PythonDirective) override this so the Click and Python runners don’t collide on the same document.

runner_factory

alias of PythonRunner

class click_extra.sphinx.python.PythonSourceDirective(name, arguments, options, content, lineno, content_offset, block_text, state, state_machine)[source]

Bases: PythonDirective

Render a Python source block and execute it silently.

Counterpart to SourceDirective for non-Click code: show the source for teaching, run it as a side effect to seed the runner namespace for subsequent python:run or python:render blocks in the same document.

default_language: str = 'python'

Default highlighting language to use to render the code block.

All Pygments’ languages short names are recognized.

show_source_by_default: bool = True

Whether to render the source code of the example in the code block.

show_results_by_default: bool = False

Whether to render the results of the example in the code block.

runner_method: str = 'execute_source'

The name of the method to call on the per-document runner.

A ClickRunner for the click:* directives.

screenshots_source: bool = True

Whether a :screenshot: on this directive pictures the block’s own code.

False for a directive that runs something: what is worth committing there is the output, and the code producing it is on the page already. True for one that only declares code, which has no output to picture and whose subject is the code itself. See screenshot_lines().

class click_extra.sphinx.python.PythonRunDirective(name, arguments, options, content, lineno, content_offset, block_text, state, state_machine)[source]

Bases: PythonDirective

Execute a Python block and render its captured stdout.

Default language for the result code block is text (plain, no highlighting). Override with the :language: option when the output is structured (json, html, yaml, etc.). The source, when shown, is rendered as python whatever the result language, and a :screenshot: pictures the captured output, as it does on click:run.

default_language: str = 'text'

Default highlighting language to use to render the code block.

All Pygments’ languages short names are recognized.

show_source_by_default: bool = False

Whether to render the source code of the example in the code block.

show_results_by_default: bool = True

Whether to render the results of the example in the code block.

runner_method: str = 'run_python'

The name of the method to call on the per-document runner.

A ClickRunner for the click:* directives.

source_language: str | None = 'python'

Language the source block is highlighted in, when it is not language.

None for a directive showing code of the language it names: the source block of a click:source follows its :language: option. A directive that runs a block names the language of its results instead, so it states the language of the code producing them here, python for every runner of this package.

class click_extra.sphinx.python.PythonRenderBaseDirective(name, arguments, options, content, lineno, content_offset, block_text, state, state_machine)[source]

Bases: PythonDirective

Common scaffolding for the python:render directive family.

Subclasses set forced_parser to either None (use the host document’s parser via state.nested_parse) or a Parser factory that produces the parser to apply to the captured output.

default_language: str = 'markdown'

Default highlighting language to use to render the code block.

All Pygments’ languages short names are recognized.

show_source_by_default: bool = False

Whether to render the source code of the example in the code block.

show_results_by_default: bool = True

Whether to render the results of the example in the code block.

runner_method: str = 'run_python'

The name of the method to call on the per-document runner.

A ClickRunner for the click:* directives.

forced_parser: ClassVar[type[Parser] | None] = None

Parser class to instantiate for the captured output.

None means defer to the host document’s parser via state.nested_parse (preserving full Sphinx context). A concrete class forces that parser regardless of host file format.

run()[source]

Render the captured stdout as live document content.

A :mirror: block is not run here. rewrite_python_mirror_regions() writes its output as a raw Markdown region below the fence during the source-read pass, and the host parser renders that region: the directive shows at most its Python source, so the content is not rendered twice.

Return type:

list[Node]

class click_extra.sphinx.python.PythonRenderDirective(name, arguments, options, content, lineno, content_offset, block_text, state, state_machine)[source]

Bases: PythonRenderBaseDirective

Execute a Python block and parse its stdout with the host parser.

The captured output is fed to whatever parser owns the surrounding source file: MyST inside .md documents, reST inside .rst documents. Use this when the generated markup matches the host file format. Cross-references and Sphinx-aware roles resolve naturally because the host state machine is reused.

For a host-independent contract, see PythonRenderMystDirective and PythonRenderRstDirective.

Accepts an extra :mirror: flag on top of the shared option spec. When set, the block keeps a copy of its generated Markdown in the source file, between MIRROR_MARKER_START / MIRROR_MARKER_END markers, so the output is reviewable in place and renders on GitHub. The committed region is refreshed by update_mirror_blocks(); builds render fresh output via rewrite_python_mirror_regions().

forced_parser: ClassVar[type[Parser] | None] = None

Parser class to instantiate for the captured output.

None means defer to the host document’s parser via state.nested_parse (preserving full Sphinx context). A concrete class forces that parser regardless of host file format.

class click_extra.sphinx.python.PythonRenderMystDirective(name, arguments, options, content, lineno, content_offset, block_text, state, state_machine)[source]

Bases: PythonRenderBaseDirective

Execute a Python block and parse its stdout as MyST, regardless of host.

Lets a .rst document embed MyST-generated content: the captured output goes through MyST parsing even when the surrounding file is reST. Requires myst-parser to be importable; the import is deferred to directive run time so consumers that never use this directive don’t pay the dependency cost.

Replaces the docs_update.py regenerator pattern (write generated content between <!-- start --> / <!-- end --> markers) with inline build-time execution. The content is always current and there is no separate regeneration step.

property forced_parser: type[Parser]

The MyST docutils parser, imported on first use.

class click_extra.sphinx.python.PythonRenderRstDirective(name, arguments, options, content, lineno, content_offset, block_text, state, state_machine)[source]

Bases: PythonRenderBaseDirective

Execute a Python block and parse its stdout as reST, regardless of host.

Lets a .md MyST document embed reST-generated content: the captured output goes through the docutils reST parser even when the surrounding file is MyST. No additional dependency: the reST parser ships with docutils.

forced_parser

alias of Parser

click_extra.sphinx.python.rewrite_mirrors(text, location)[source]

Return text with both kinds of mirror region refreshed.

The fence form first (_rewrite_mirror_regions), then the comment form (_rewrite_mirror_src_regions), each only when text can hold one: a walk costs a pass over every line of the document.

Return type:

str

click_extra.sphinx.python.update_mirror_blocks(paths, *, check=False)[source]

Refresh every python:render :mirror: region in the given sources.

See click_extra.markdown.blocks.update_blocks() for the walk, write, and check-mode contract.

Danger

Refreshing executes each mirror block’s Python with the privileges of the current process, exactly as a documentation build would. Only run it on trusted sources.

Return type:

list[Path]

Returns:

the files whose mirror regions were (or, under check, would be) updated.

click_extra.sphinx.python.rewrite_python_mirror_regions(app, docname, source)[source]

source-read handler refreshing :mirror: regions in memory.

Refreshes both kinds of region, below a visible {python:render} :mirror: fence and below an invisible <!-- mirror-src --> comment, in the in-memory source before the document is parsed, so every build renders the fresh output even when the committed region is stale. Nothing is written to disk: the committed region is refreshed offline by update_mirror_blocks(), behind the click-extra refresh-directives command.

Danger

Like the python:* directives, this executes arbitrary Python at build time with the full privileges of the Sphinx process. It is registered only when click_extra_enable_exec_directives is set.

Return type:

None

class click_extra.sphinx.python.PythonDomain(env)[source]

Bases: StatelessDomain

Sphinx domain registering the python:* directives.

Provides:

  • python:source to show and silently execute Python source.

  • python:run to execute Python and render the captured stdout in a code block.

  • python:render to execute Python and parse the captured stdout with the host document’s parser.

  • python:render-myst to force MyST parsing of the captured stdout.

  • python:render-rst to force reST parsing of the captured stdout.

The domain name is python, distinct from Sphinx’s built-in py domain (which provides py:function / py:class / py:mod for documenting Python API objects). Both coexist without collision.

name: ClassVar[str] = 'python'

domain name: should be short, but unique

label: ClassVar[str] = 'Python (build-time execution)'

domain label: longer, more descriptive (used in messages)

directives: ClassVar[dict] = {'render': <class 'click_extra.sphinx.python.PythonRenderDirective'>, 'render-myst': <class 'click_extra.sphinx.python.PythonRenderMystDirective'>, 'render-rst': <class 'click_extra.sphinx.python.PythonRenderRstDirective'>, 'run': <class 'click_extra.sphinx.python.PythonRunDirective'>, 'source': <class 'click_extra.sphinx.python.PythonSourceDirective'>}

directive name -> directive class

click_extra.sphinx.python.cleanup_python_runner(app, doctree)

Drop the PythonRunner from the doctree once the document is read.

Return type:

None

click_extra.sphinx.tables module

Style the two-axis tables of a Sphinx build.

A table whose top-left cell names both of its axes, as corner_header() spells them, is read down its first column and across its headers. On a narrow screen, a browser wraps that first column wherever it can, including after the hyphen of click-extra or of a date, which splits every label over several lines. The labels of such a table are short, so this module keeps each one whole: the table scrolls sideways instead, inside the table-wrapper Furo puts around every table.

A compatibility matrix is such a table, and each of its cells holds the glyph of a state (see click_extra.sphinx.matrix). This module tints those cells green, red or grey by state, so the shape of the support reads at a glance.

The extension marks every table whose first header cell holds CORNER_GLYPH, and each of its body cells holding a state glyph alone, then ships the stylesheet the marks hook into. The stylesheet is written into the build rather than packaged as a data file.

click_extra.sphinx.tables.CORNER_TABLE_CLASS = 'corner-table'

HTML class of a table whose top-left cell names both of its axes.

click_extra.sphinx.tables.SUPPORTED_CELL_CLASS = 'matrix-supported'

HTML class of a cell holding SUPPORTED_CELL.

click_extra.sphinx.tables.FORBIDDEN_CELL_CLASS = 'matrix-forbidden'

HTML class of a cell holding FORBIDDEN_CELL.

click_extra.sphinx.tables.UNDECLARED_CELL_CLASS = 'matrix-undeclared'

HTML class of a cell holding UNDECLARED_CELL.

click_extra.sphinx.tables.STATE_CELL_CLASSES: dict[str, str] = {'–': 'matrix-undeclared', '✅': 'matrix-supported', '❌': 'matrix-forbidden'}

The HTML class of each state glyph, for a body cell holding that glyph alone.

click_extra.sphinx.tables.CORNER_TABLE_CSS = 'table.corner-table th:first-child,\ntable.corner-table td:first-child {\n  white-space: nowrap;\n}\n\n/* State tints are translucent, so they read on light and dark themes alike.\n * Each is drawn as a background image, which layers over the background color\n * the crosshair sets on hover instead of hiding it. */\ntable.corner-table td.matrix-supported {\n  --matrix-tint: rgba(46, 160, 67, 0.15);\n}\ntable.corner-table td.matrix-forbidden {\n  --matrix-tint: rgba(248, 81, 73, 0.15);\n}\ntable.corner-table td.matrix-undeclared {\n  --matrix-tint: rgba(128, 128, 128, 0.1);\n}\ntable.corner-table td.matrix-supported,\ntable.corner-table td.matrix-forbidden,\ntable.corner-table td.matrix-undeclared {\n  background-image: linear-gradient(var(--matrix-tint), var(--matrix-tint));\n}\n'

Stylesheet of corner-labeled tables.

Keeps their first column on one line, and tints the state cells of a matrix.

click_extra.sphinx.tables.STATIC_DIR = '_click_extra_static'

Output folder of the stylesheets and scripts the extension writes.

Sphinx copies it into _static with the other static files, as it does for every folder listed in html_static_path. See _write_static_file.

click_extra.sphinx.tables.STYLESHEET = 'click-extra.css'

File name of the stylesheet, served from the _static folder.

click_extra.sphinx.tables.mark_corner_tables(app, doctree)[source]

Add CORNER_TABLE_CLASS to every table with a corner label.

Each body cell of such a table holding a state glyph alone also gets the class of that state (see STATE_CELL_CLASSES).

Return type:

None

click_extra.sphinx.tables.add_stylesheet(app)[source]

Write CORNER_TABLE_CSS into the build and link it from each page.

Only an HTML builder serves stylesheets, so other builders skip it.

Return type:

None

click_extra.sphinx.tables.setup(app)[source]

Register the table marker and its stylesheet on app.

Called from click_extra.sphinx.setup() so projects only need to list "click_extra.sphinx" in their extensions.

Return type:

None

click_extra.sphinx.tabs module

Lay out a sphinx-design tab-set vertically, its labels down a left rail.

A tab-set with many tabs, like the install channels of a project, wraps its labels into a strip that is hard to read. This module ships a stylesheet that lays out a tab-set marked with VERTICAL_TABS_CLASS in two columns: the labels stack down the left, and the selected pane fills the right. On a screen narrower than NARROW_SCREEN, the tab-set falls back to the stock horizontal layout.

sphinx-design keeps the radio inputs, their labels and the panes of a tab-set as flat siblings of one flex container, so a two-column grid on that container is enough: grid placement decouples the visual position of each sibling from its source order. The rules use the child combinator, so a tab-set nested inside a pane keeps the horizontal layout. The stylesheet is written into the build rather than packaged as a data file, and is inert on a project that does not load sphinx-design.

The class is deprecated: sphinx-design 0.8.0 lays out a tab-set the same way with the :orientation: vertical option of tab-set, from executablebooks/sphinx-design#304. Each tab-set that carries the class gets a VERTICAL_TABS_WARNING, and the class stops working in 11.0.0.

click_extra.sphinx.tabs.VERTICAL_TABS_CLASS = 'tabs-vertical'

The class a tab-set takes in its :class: option to lay out vertically.

click_extra.sphinx.tabs.VERTICAL_TABS_WARNING = 'click_extra.tabs_vertical'

Type and subtype of the warning about a VERTICAL_TABS_CLASS tab-set.

List it in suppress_warnings to silence the warning.

click_extra.sphinx.tabs.NARROW_SCREEN = '50em'

The screen width under which a vertical tab-set falls back to horizontal tabs.

click_extra.sphinx.tabs.VERTICAL_TABS_CSS = ".sd-tab-set.tabs-vertical {\n  display: grid;\n  /* The pane's track is floored at zero: a bare `1fr` means `minmax(auto, 1fr)`,\n   * whose floor grows to the widest line the pane cannot break, like a long URL\n   * in a code block, and pushes the pane under the sidebar. */\n  grid-template-columns: minmax(7rem, max-content) minmax(0, 1fr);\n  align-items: start;\n}\n\n/* No column gap: the marker of the active label then sits on the rule of the\n * pane, the way horizontal tabs merge an underline into an overline. */\n.sd-tab-set.tabs-vertical > label {\n  grid-column: 1;\n  text-align: left;\n  padding-right: 1rem;\n  border-bottom: none;\n  border-right: 0.125rem solid transparent;\n}\n.sd-tab-set.tabs-vertical > input:checked + label {\n  border-right-color: var(--sd-color-tabs-underline-active);\n}\n.sd-tab-set.tabs-vertical > .sd-tab-content {\n  grid-column: 2;\n  /* Span every row, or the height of the pane inflates the first row and\n   * pushes the labels down the rail. `1 / -1` counts from the last explicit\n   * row, and the grid declares none, so the span overshoots any tab count. */\n  grid-row: 1 / span 99;\n  align-self: stretch;\n  padding: 0 0 0 1.5rem;\n  /* sphinx-design rules a pane with two shadows, one above and one below. A\n   * single hairline down the left edge replaces them. */\n  box-shadow: -0.0625rem 0 var(--sd-color-tabs-overline);\n}\n\n/* Too narrow for two columns: restate the values of sphinx-design, since a\n * shorthand cannot fall back to the value of another stylesheet. */\n@media (max-width: 50em) {\n  .sd-tab-set.tabs-vertical {\n    display: flex;\n    flex-wrap: wrap;\n  }\n  .sd-tab-set.tabs-vertical > label {\n    padding: 1em 1.25em 0.5em;\n    border-right: none;\n    border-bottom: 0.125rem solid transparent;\n  }\n  .sd-tab-set.tabs-vertical > input:checked + label {\n    border-bottom-color: var(--sd-color-tabs-underline-active);\n  }\n  .sd-tab-set.tabs-vertical > .sd-tab-content {\n    align-self: auto;\n    padding: 0.75rem 0;\n    box-shadow:\n      0 -0.0625rem var(--sd-color-tabs-overline),\n      0 0.0625rem var(--sd-color-tabs-underline);\n  }\n}\n"

Stylesheet laying out a tab-set marked with VERTICAL_TABS_CLASS.

click_extra.sphinx.tabs.STYLESHEET = 'click-extra-tabs.css'

File name of the stylesheet, served from the _static folder.

click_extra.sphinx.tabs.add_vertical_tabs(app)[source]

Write VERTICAL_TABS_CSS into the build and link it from each page.

The file goes to the static folder of the extension (see click_extra.sphinx.tables._write_static_file). Only an HTML builder serves it, so other builders skip it.

Return type:

None

click_extra.sphinx.tabs.warn_vertical_tabs_class(app, doctree)[source]

Warn about each tab-set of doctree marked with VERTICAL_TABS_CLASS.

sphinx-design gives the node of each tab-set and tab-set-code the sd-tab-set class, beside the classes of its :class: option.

Return type:

None

click_extra.sphinx.tabs.setup(app)[source]

Register the vertical tab stylesheet and the deprecation warning on app.

Called from click_extra.sphinx.setup() so projects only need to list "click_extra.sphinx" in their extensions.

Return type:

None

click_extra.sphinx.theme_docs module

Render a theme palette as an inline-styled HTML fragment for documentation.

palette_html() is called from docs/theme.md to render every built-in theme’s palette at Sphinx build time. inject_slot_example_docstring() is registered as a Sphinx autodoc-process-docstring hook from docs/conf.py to inject a colored example into each HelpTheme slot’s autodoc block. This is build-time documentation code, kept out of the runtime theme module.

click_extra.sphinx.theme_docs.inject_slot_example_docstring(app, what, name, obj, options, lines)[source]

Sphinx autodoc-process-docstring hook injecting per-slot colored examples.

For every HelpTheme slot that has an entry in _PALETTE_EXAMPLES, append an ansi-color code block to the slot’s autodoc lines. The example is rendered through _render_slot_ansi, which calls BUILTIN_THEMES["dark"].<slot>(text) to obtain the actual ANSI escapes click-extra would emit at runtime.

Wire this up from a Sphinx conf.py with:

from click_extra.sphinx.theme_docs import inject_slot_example_docstring


def setup(app):
    app.connect("autodoc-process-docstring", inject_slot_example_docstring)

The hook intentionally targets only click_extra.theme.HelpTheme.<slot> names so it won’t accidentally rewrite unrelated docstrings; downstream projects can register the hook in their own conf.py if they consume HelpTheme docstrings.

Return type:

None

click_extra.sphinx.theme_docs.palette_html(theme)[source]

Render a theme’s palette as an inline-styled HTML <dl> fragment.

The output is a two-column definition list (slot name → styled swatch plus attribute decorations) safe to inject into MyST or reST host documents via the python:render Sphinx directive (or any raw:: html block). Used by docs/theme.md to render every built-in theme’s palette at Sphinx build time without hand-maintaining swatch tables. Downstream projects with their own custom themes can call the same helper to get matching swatch listings in their own docs:

```{python:render}
from click_extra.sphinx.theme_docs import palette_html
from my_app.themes import MY_THEME
print(palette_html(MY_THEME))
```

Slots that hold identity (no styling applied), the boolean cross_ref_highlight toggle, and a handful of inherited cloup slots that built-ins never style are skipped: every emitted row corresponds to a real palette choice in the theme.

Return type:

str

click_extra.sphinx.todos module

List each docstring :todo: once on a todolist, and report those it misses.

sphinx.ext.todo collects doctree nodes, not documented objects. A :todo: written once in a docstring therefore lands on the todolist page once per rendering of that docstring, and two conventions common to autodoc projects render the same docstring several times:

  • A full-API page plus per-feature pages. A project documenting every module on one page, then documenting the same modules again next to the prose that explains them, renders each docstring twice. :no-index: on the second block does not help: it suppresses the cross-reference target and the search-index entry, leaving the docstring (and its todo node) rendered in full.

  • A package re-exporting its members. automodule documents the imported names a package lists in __all__, so a symbol appears once under the package and once under the module that defines it. Both renderings can even land on the same page.

The two multiply, and nothing upstream deduplicates: sphinx.ext.todo.TodoListProcessor flattens the whole todo domain into the page in read order. This module removes the surplus nodes from that domain just before the processor reads it, so the rendered list carries one entry per directive.

The same cause has the opposite effect on a docstring that no page renders: the docstring of a private module, of a private member that :members: skips, or of a nested function. It never becomes a node, so its :todo: reaches no todolist. This module reads the source of the documented packages instead, and logs a UNRENDERED_TODO_WARNING for each such :todo:. Its author can then move it into a docstring that a page renders.

Todo

Propose the deduplication upstream, as a sphinx.ext.todo feature rather than a third-party hook.

The repetition is a property of how autodoc renders a docstring, not of how a project writes one, so every autodoc project documenting a module twice hits it and none of them can fix it in their own source: :no-index: reads like the cure and is not. sphinx.ext.todo.TodoListProcessor already flattens the whole domain in one place, which is where a todo_deduplicate config value would apply; the helpers this module needed (todo_identity(), is_reexport() and the comparison of the objects behind two labels) are the whole of the logic.

Should it land, keep this module as a shim for the Sphinx releases below that floor, then drop it once the floor moves past them.

click_extra.sphinx.todos.AUTODOC_DOCSTRING_MARKER = 'docstring of '

Separator autodoc puts in front of the object a docstring belongs to.

A node produced from a docstring carries a synthetic source naming the object autodoc documented: {file}:docstring of {dotted.path} when a source file was analyzed, and plain docstring of {dotted.path} when none was. Both forms live inside the supported sphinx>=8 range, so the split has to accept the two of them. It is what lets todo_identity() recognize two renderings of a single docstring reached through two import paths.

click_extra.sphinx.todos.DEDUPE_TODOS_CONFIG = 'click_extra_dedupe_todos'

Name of the conf.py value gating the deduplication.

True by default: a todolist page listing the same item three times is a defect in every project I know of, and a project that has not enabled sphinx.ext.todo never notices the hook either way. Set it to False to get Sphinx’s raw output back, one entry per rendering.

click_extra.sphinx.todos.UNRENDERED_TODO_WARNING = 'click_extra.todo_unrendered'

Type and subtype of the warning about a docstring :todo: no page renders.

List it in suppress_warnings to silence the warning, or in click_extra.sphinx.fail_on_warnings.FAIL_ON_WARNINGS_CONFIG to fail the build on it.

click_extra.sphinx.todos.todo_identity(node)[source]

Identify the {todo} directive a node was rendered from.

Two nodes share an identity when they come from the same line of the same docstring or document, whatever page rendered them and whichever import path autodoc reached the object through. The file prefix is dropped from a docstring source precisely so a re-exported symbol matches the module that defines it.

Return type:

tuple[str, int | None]

click_extra.sphinx.todos.is_reexport(node)[source]

Tell whether a node was rendered through a package’s __init__.

Used to rank competing renderings: the entry surviving deduplication keeps its backlink and its “located in …” attribution, so a rendering reached through the defining module is worth more to a reader than the same docstring reached through a re-exporting package.

Return type:

bool

click_extra.sphinx.todos.deduplicate_todos(app, doctree, docname)[source]

Drop every todo node duplicating one already held by the domain.

Connected to doctree-resolved below the priority sphinx.ext.todo.TodoListProcessor runs at, and a no-op on any document holding no todolist, so the work happens once on the page that consumes the domain.

Two renderings come from one docstring when their labels import the same object. autodoc labels a function or a class with the path of its defining module, whatever path reached it, but labels an attribute or a property with the path it documented: a re-exported attribute carries a different label under its package and under its module, so only the object matches them.

Among the renderings of one directive, the surviving node is the first in (rendered through a defining module, document name, position in the document) order. That ordering is total and reads the same on a clean build and an incremental one, so the backlink a reader follows does not move around between builds.

Note

The todo domain is mutated in place. That is safe because Sphinx pickles the environment at the end of the reading phase, before the writing phase this hook belongs to: the removals reach the page being written and never the cached environment, so an incremental rebuild still starts from the full set.

Return type:

None

click_extra.sphinx.todos.docstring_todos(source, module)[source]

Yield the owner and the line of each todo directive in docstrings.

source is the text of the module named module. The owner of a docstring is the dotted path autodoc labels it with: module for the module docstring, and {module}.{qualname} for a class or a function, with <locals> for a definition nested in a function. An attribute docstring, the string literal right after an assignment, belongs to each name the assignment binds, as autodoc reads it. Lines count from 1.

A directive counts in both syntaxes a docstring can use: a MyST fence, with backticks or colons, and a reST directive. A directive nested in another one counts too. A directive quoted in a code block does not.

Raises:

SyntaxError – If source does not parse.

Return type:

Iterator[tuple[str, int]]

click_extra.sphinx.todos.report_unrendered_todos(app, env)[source]

Warn about each docstring {todo} that no page renders.

The warning has the UNRENDERED_TODO_WARNING type, and points at the line of the directive. The check is connected to env-check-consistency, which fires once the build has read its documents and merged the domains of a parallel read: the todo domain then holds a node for each :todo: a page renders. Like Sphinx’s own consistency checks, it does not fire on a build that reads no document.

The packages read are the ones holding a module of the Python domain, which is where automodule registers the modules it documents. Every module of such a package is read, private ones included, without being imported.

A docstring rendered under another path still counts. autodoc labels a function or a class with the path of its defining module, whatever path reached it, but labels an attribute or a property with the path it documented. So when the labels differ, the check imports the object behind both paths and compares them.

Inert unless the project enables sphinx.ext.todo with todo_include_todos: without it, no {todo} renders anywhere, so none can be missing.

Return type:

None

click_extra.sphinx.todos.setup(app)[source]

Register the deduplication hook and the unrendered-todo check on app.

Called from click_extra.sphinx.setup() so projects only need to list "click_extra.sphinx" in their extensions. Priority 400 places the deduplication hook below the default 500 sphinx.ext.todo.TodoListProcessor is connected at, which is what makes the domain already trimmed by the time the list is rendered.

Return type:

None

click_extra.sphinx.viewcode module

Publish only the sphinx.ext.viewcode pages that a documented object links to.

sphinx.ext.viewcode reads the source of every module a documented object may come from, and keeps an entry for that module even when none of its objects gets a [source] link. Each such entry becomes a page of highlighted source that only the _modules index links to. A documented constant is the usual cause: viewcode looks for its definition in the module that defines the class of its value, like re for a compiled pattern, and finds none there.

A serial read drops such an entry when it purges the next document, so only the last document read leaves one behind. A parallel read merges the entries of its workers after every purge has run, so it publishes a page for each unused module (sphinx-doc/sphinx#14675). This module drops every entry with no recorded use once the read is over, which gives both reads the same pages.

Todo

Delete this module once sphinx-doc/sphinx#14675 is fixed and the sphinx extra’s floor passes the release that carries the fix.

click_extra.sphinx.viewcode.drop_unused_viewcode_modules(app, env)[source]

Forget each module sphinx.ext.viewcode read but no document links.

Connected to env-updated, which fires once the read is over: after a parallel read merged the entries of its workers, and before Sphinx pickles the environment. Inert without sphinx.ext.viewcode, which creates the entries.

Return type:

None

click_extra.sphinx.viewcode.setup(app)[source]

Register the cleanup of unused viewcode modules on app.

Called from click_extra.sphinx.setup() so projects only need to list "click_extra.sphinx" in their extensions.

Return type:

None