Sphinx¶
Sphinx is the best way to document your Python CLI. Click Extra provides several utilities to improve the quality of life of maintainers.
Important
For these helpers to work, you need to install click_extra’s additional dependencies from the sphinx extra group:
$ pip install click_extra[sphinx]
See also
To capture a CLI’s output as a static image for a README, slide, or any surface that cannot run Sphinx, see CLI screenshots.
See also
The same extension runs arbitrary Python at build time, and renders a release compatibility table. Both are documented in Python directives.
Setup¶
Once Click Extra is installed, you can enable its extensions in your Sphinx’s conf.py:
conf.py¶extensions = [
...
"click_extra.sphinx",
]
This unlocks the always-on features: the ANSI-capable Pygments HTML formatter, the GitHub-flavored alert (> [!NOTE], > [!WARNING], …) → MyST/reST admonition converter, todo-list deduplication, and the man-page hook. The click:* and python:* directive families are disabled by default and require an explicit opt-in described below.
Danger
Build-time code execution. Every click:* and python:* directive runs its body with the same privileges as the Sphinx process: full filesystem access, full network access, and full access to the build environment’s secrets (GITHUB_TOKEN, READTHEDOCS_TOKEN, etc.). The runner namespace is unrestricted: there is no sandbox.
This is intentional: build-time execution is the whole point of those directives. But it means the same trust boundary I’d apply to a Makefile or conftest.py applies here:
Only run
sphinx-buildagainst source I trust.Do not auto-build documentation from unverified pull requests in CI without an isolated, secret-free environment.
Treat any
printcall insidepython:render*whose output incorporates untrusted data as a content-injection sink. reST in particular allows.. raw:: htmland.. include:: /path/to/file, both of which can read local files or inject HTML into the rendered page.
The risk profile is identical to other build-time-execution extensions like jupyter-sphinx, myst-nb, and sphinx-exec-code.
Important
Opt-in required. Both directive families are disabled by default. A project that adds click_extra.sphinx to its extensions list gets the always-on features automatically, but does not gain build-time code execution unless the maintainer explicitly turns it on. Add this to conf.py:
click_extra_enable_exec_directives = True
Without it, click:source, click:run, python:source, python:run, python:render, python:render-myst, and python:render-rst are not registered with Sphinx. Documents that reference them get an “Unknown directive” warning and the directive body is never executed. This way a transitive import of click_extra.sphinx, or a maintainer who installs the extension purely for ANSI-aware code blocks, cannot be tricked into running attacker-supplied Python by a doc-only pull request.
Tip
I recommend using one of these themes, which works well with Click Extra:
Furo - Which has been fixed to support Click Extra as of
2023.05.20.Shibuya - Which is explicitly supporting Click Extra as of
2025.9.22.
See also
Using MkDocs instead of Sphinx? See the MkDocs integration.
click:* directives¶
Click Extra adds two new directives:
Directive |
Purpose |
|---|---|
|
Define and show the source code of a Click CLI in Sphinx. |
|
Invoke the CLI defined above, and display the results as if it was executed in a terminal session. |
Thanks to these, you can directly demonstrate the usage of your CLI in your documentation. You no longer have to maintain screenshots of you CLIs. Or copy and paste their outputs to keep them in sync with the latest revision. Click Extra will do that job for you.
These directives supports both MyST Markdown and reStructuredText syntax.
Usage¶
Here is how to define a simple Click-based CLI with the click:source directive:
```{click:source}
from click_extra import echo, command, option, style
@command
@option("--name", prompt="Your name", help="The person to greet.")
def hello_world(name):
"""Simple program that greets NAME."""
echo(f"Hello, {style(name, fg='red')}!")
```
.. click:source::
from click_extra import echo, command, option, style
@command
@option("--name", prompt="Your name", help="The person to greet.")
def hello_world(name):
"""Simple program that greets NAME."""
echo(f"Hello, {style(name, fg='red')}!")
After defining the CLI source code in the click:source directive above, you can invoke it with the click:run directive.
The click:run directive expects a Python code block that uses the invoke function. This function is specifically designed to run Click-based CLIs and handle their execution and output.
Here is how to invoke the example with a --help option:
```{click:run}
invoke(hello_world, args=["--help"])
```
.. click:run::
invoke(hello_world, args=["--help"])
Placed in your Sphinx documentation, the two blocks above renders to:
from click_extra import echo, command, option, style
@command
@option("--name", prompt="Your name", help="The person to greet.")
def hello_world(name):
"""Simple program that greets NAME."""
echo(f"Hello, {style(name, fg='red')}!")
$ hello-world --help
Usage: hello-world [OPTIONS]
Simple program that greets NAME.
Options:
--name TEXT The person to greet.
-h, --help Show this message and exit.
Configuration options:
--config LOCATION Location of the configuration file. Supports
local path with glob patterns or remote URL.
[default: ~/.config/hello-world/]
--no-config Ignore all configuration files and only use
command line parameters and environment
variables.
--validate-config LOCATION Validate the configuration file and exit.
--export-config FORMAT Export the configuration in the selected format
to <stdout>, then exit.
Output options:
--accessible Accessibility mode: disable colors and render
tables in a borderless, screen-reader-friendly
format.
--color [auto|always|never] Colorize the output. A bare --color is the same
as --color=always. [default: auto]
--no-color Disable colorization (alias of --color=never).
--progress / --no-progress Show progress indicators during long operations.
Disabled for non-interactive output (pipes, dumb
terminals, CI) and by --accessible. [default:
progress]
--theme [auto|dark|dracula|light|manpage|monokai|nord|solarized-dark]
Color theme used for help screens. [default:
dark]
--table-format FORMAT Rendering style of tables. [default: rounded-
outline]
Logging options:
--verbosity LEVEL Either CRITICAL, ERROR, WARNING, INFO, DEBUG.
[default: WARNING]
-v, --verbose Increase the default WARNING verbosity by one
level for each additional repetition of the
option. [default: 0]
-q, --quiet Decrease the default WARNING verbosity by one
level for each additional repetition of the
option. [default: 0]
--debug Shorthand for --verbosity DEBUG.
Introspection options:
--time / --no-time Measure and print elapsed execution time.
[default: no-time]
--params Show all CLI parameters, their provenance,
defaults and value, then exit.
--tree Show the tree of nested subcommands and exit.
--man Read the command's manual page and exit.
--help-format [carapace|json|json-full|man|markdown|markdown-full]
Render the command in the given format and exit.
--version Show the version and exit.
This is perfect for documentation, as it shows both the source code of the CLI and its results.
Notice how the CLI code is properly rendered as a Python code block with syntax highlighting. And how the invocation of that CLI renders into a terminal session with ANSI coloring of output.
You can then invoke that CLI again with its --name option:
```{click:run}
invoke(hello_world, args=["--name", "Joe"])
```
.. click:run::
invoke(hello_world, args=["--name", "Joe"])
Which renders in Sphinx as if it was executed in a terminal code block:
$ hello-world --name Joe
Hello, Joe!
Hint
The click:source and click:run directives work well with standard vanilla click-based CLIs.
The example above imports its CLI primitives from the click-extra module instead, to demonstrate the coloring of terminal session outputs: click-extra provides fancy coloring of help screens by default.
Tip
Need to run arbitrary Python that isn’t a Click CLI? See python:run and the rest of the python:* family for general-purpose build-time execution and live-content generation.
See also
Click Extra’s own documentation extensively use click:source and click:run directives. Look around in its Markdown source files for advanced examples and inspiration.
Options¶
You can pass options to both the click:source and click:run directives to customize their behavior:
Option |
Description |
Example |
|---|---|---|
Display line numbers. |
|
|
Specify the starting line number. |
|
|
Highlight specific lines in the source block. |
|
|
|
Highlight specific lines in the captured output block. Same syntax as |
|
Ignore minor errors on highlighting. |
|
|
Set a caption for the code block. |
|
|
Set a name for the code block (useful for cross-referencing). |
|
|
Set a CSS class for the code block. |
|
|
Specify the number of spaces to remove from the beginning of each line. |
|
|
Specify the programming language for syntax highlighting. This can be used as an alternative to passing the language as an argument. |
|
|
|
Flags to force the source code within the directive to be rendered or not. |
|
|
Flags to force the results of the CLI invocation to be rendered or not. Only applies to |
|
|
Flags to force the shell prompt drawn above the results to be rendered or not. Only applies to |
|
code-block options¶
Because the click:source and click:run directives produces code blocks, they inherits the same options as the Sphinx code-block directive.
For example, you can highlight some lines of with the :emphasize-lines: option, display line numbers with the :linenos: option, and set a caption with the :caption: option:
```{click:source}
:caption: A magnificent ✨ Hello World CLI!
:linenos:
:emphasize-lines: 4,7
from click_extra import echo, command, option, style
@command
@option("--name", prompt="Your name", help="The person to greet.")
def hello_world(name):
"""Simple program that greets NAME."""
echo(f"Hello, {style(name, fg='red')}!")
```
.. click:source::
:caption: A magnificent ✨ Hello World CLI!
:linenos:
:emphasize-lines: 4,7
from click_extra import echo, command, option, style
@command
@option("--name", prompt="Your name", help="The person to greet.")
def hello_world(name):
"""Simple program that greets NAME."""
echo(f"Hello, {style(name, fg='red')}!")
Which renders to:
1from click_extra import echo, command, option, style
2
3@command
4@option("--name", prompt="Your name", help="The person to greet.")
5def hello_world(name):
6 """Simple program that greets NAME."""
7 echo(f"Hello, {style(name, fg='red')}!")
Display options¶
You can also control the display of the source code and the results of the CLI invocation with the :show-source:/:hide-source: and :show-results:/:hide-results: options.
By default:
click:sourcedisplays the source code of the CLI. Because its content is not executed, no results are displayed. This is equivalent to having both:show-source:and:hide-results:options.click:rundisplays the results of the CLI invocation, but does not display the source code. This is equivalent to having both:hide-source:and:show-results:options.
Explicit options override this behavior. To display only the result of the CLI invocation, without the source code defining that CLI, add :hide-source: to the click:source directive:
```{click:source}
:hide-source:
from click_extra import echo, command, style
@command
def simple_print():
echo(f"Just a {style('string', fg='blue')} to print.")
```
```{click:run}
invoke(simple_print)
```
.. click:source::
:hide-source:
from click_extra import echo, command, style
@command
def simple_print():
echo(f"Just a {style('string', fg='blue')} to print.")
.. click:run::
invoke(simple_print)
Which only renders the click:run directive, as the click:source doesn’t display anything:
$ simple-print
Just a string to print.
If you want to display the source code used to invoke the CLI in addition to its results, you can add the :show-source: option to the click:run directive:
```{click:run}
:show-source:
result = invoke(simple_print)
# Some inline tests.
assert result.exit_code == 0, "CLI execution failed"
assert not result.stderr, "Found error messages in <stderr>"
```
.. click:run::
:show-source:
result = invoke(simple_print)
# Some inline tests.
assert result.exit_code == 0, "CLI execution failed"
assert not result.stderr, "Found error messages in <stderr>"
In this particular mode the click:run produced two code blocks, one for the source code, and one for the results of the invocation:
result = invoke(simple_print)
# Some inline tests.
assert result.exit_code == 0, "CLI execution failed"
assert not result.stderr, "Found error messages in <stderr>"
$ simple-print
Just a string to print.
Caution
:show-results:/:hide-results: options have no effect on the click:source directive and will be ignored. That’s because this directive does not execute the CLI: it only displays its source code.
Hiding the prompt¶
A click:run block opens its results with the invocation that produced them, drawn as a shell prompt:
$ simple-print
Just a string to print.
:hide-prompt: drops that line and leaves the output on its own. Reach for it when the surrounding prose already names the command, or when the block exists to show what a CLI prints rather than how to call it:
Just a string to print.
Environment variables handed to invoke() are rendered as assignments on the invocation itself, which is how the runner applies them: they are scoped to that one call and are not inherited by the blocks below.
$ WEATHER_UNITS=fahrenheit forecast
Paris: 18 fahrenheit.
The prompt is one line prepended to the captured output, so it is part of the results rather than a block of its own. It is rendered by format_cli_prompt(), the same helper that draws it onto an SVG capture: a block combining :hide-prompt: with :screenshot: therefore writes an image without one either.
Committed captures¶
Inside these pages a click:run block renders live, so none of them needs a screenshot. A README on GitHub or PyPI, a slide, or a social post cannot run code, and those surfaces need a captured image. Two options let a block maintain one without giving up its live rendering:
:screenshot: <name>writes the block’s output to<name>.svg, in the directory theclick_extra_screenshot_dirconf.pyvalue names (assetsby default, relative to the documentation source root). Nothing about the page changes: the results code block stays, being selectable, searchable and theme-aware where an image is none of those. The file is rewritten on every build, so it cannot drift from the CLI.:mirror:puts the image on the page as well, by keeping a Markdown link to it in the source.md, between<!-- screenshot -->and<!-- screenshot-end -->markers directly below the fence. That region is refreshed byclick-extra refresh-directives. Holding a link rather than generated data, it goes stale only when the capture is renamed.:screenshot-columns:lays the capture out at a width of its own, or atautofor the one its longest line asks for. Click wraps the CLI’s own text at its fixed width either way: what this decides is the picture, so a line the CLI never wrapped (a prompt, a wide table, a machine-readable dump) stops folding mid-word. See width for the CLI-side flag.:screenshot-border:,:screenshot-border-width:,:screenshot-radius:,:screenshot-shadow:,:screenshot-backdrop:,:screenshot-margin:,:screenshot-opacity:,:screenshot-padding:and:screenshot-title:restate the window the capture is drawn in, each mirroring the command-line option of the same name. Left out, each takes what the chrome asks for. See the window.:screenshot-line-numbers:is a flag, numbering the block’s captured lines in a gutter, and:screenshot-emphasize-lines:bands the lines it names with the same1,3-5specification:emphasize-lines:takes, open-ended ranges included. Both count the prompt as line 1, and both reach an animated capture as readily as a still. Quote a hex color (:screenshot-backdrop: "#1f6feb"): a directive’s options are read as YAML, where an unquoted#opens a comment and leaves the option empty.:screenshot-preset:draws the capture as a named terminal, from its window decorations down to the sigil its shell prompts with. A block naming none falls back to theclick_extra_screenshot_presetconf.pyvalue, so a project drawing every capture as the same desktop states it once. See terminal presets.:screenshot-watermark:credits the image in its bottom-right corner, with:screenshot-watermark-color:for the ink. Unlike thescreenshotcommand, which credits click-extra on every image it writes, a block draws none unless asked: its image is rewritten and committed on every build, so a mark naming a release would rewrite every asset the day that release changes.click_extra_screenshot_watermarkinconf.pyturns it on for the whole project. See the credit line.:screenshot-background: lightdraws the capture on white chrome, with the ANSI palette to match, for a block rendering a light-background theme. Defaults todark, what a terminal and this package’s default theme both look like. Only the image answers to it: the page’s own results block is styled by the site’s stylesheet and follows the reader’s theme either way. See light and dark chrome for the CLI-side flag.:screenshot-animate:draws an animated capture instead of a still. It is a Python expression, read in the same namespace the block’s own source runs in, so aclick:source :hide-source:block above can build the subject. Naming aSpinnertakes its frames and its interval as they stand; any other sequence of strings is one captured text per frame, and states how long each is shown with:screenshot-interval:. An animated block pictures its frames rather than its results, which still render on the page as any other block’s do.The frames come from a declared subject and not from a timing, so the same expression composes the same lines on every build and the committed asset is rewritten byte for byte. See picturing a spinner for a worked example.
:screenshot-record:is the same option for frames that were recorded rather than declared, and it is written once. A recording cannot be reproduced: which spinner glyph pairs with which screen is settled by the scheduler, so the same command records a different set of frames every other run, and regenerating would dirty the working tree for nothing anyone did. The expression is therefore not even evaluated once the asset exists, which also keeps the command it records off every later build’s clock. To take a fresh recording, delete the file and build again.:screenshot-quantum:rounds the recorded durations onto a grid, in seconds.:screenshot-hold:states how long the last frame stays up,:screenshot-blank:how long the cycle then closes on an empty screen, and:screenshot-speed:how much faster to replay than was recorded. A recording defaults to a two-second hold and a six-tenths blank; a declared animation cycles in place with no end to mark, so it defaults to neither. See printing while spinning for a worked example, and keeping a recording committable for why the two options differ.
```{click:run}
:screenshot: greet-screen
:mirror:
result = invoke(greet)
```
Which, once refreshed, keeps this below the fence, so the capture shows wherever the raw Markdown is read:
<!-- screenshot -->

<!-- screenshot-end -->
The name doubles as the image’s alt text, so pick one that reads as a description.
Both renderings, side by side¶
The two come from one execution, so a tab set can hold them together. Take a CLI leaning on color:
from click_extra import command, echo, style
@command
def forecast():
"""Report tomorrow's weather."""
echo(f"Lisbon {style('22°C', fg='yellow')} {style('sunny', fg='bright_yellow')}")
echo(f"Bergen {style('11°C', fg='cyan')} {style('rain', fg='blue')}")
echo(f"Nairobi {style('26°C', fg='red')} {style('clear', fg='bright_yellow')}")
$ forecast
Lisbon 22°C sunny
Bergen 11°C rain
Nairobi 26°C clear
The first tab is what the page renders on its own: real text, selectable and searchable, its colors resolved by the site’s stylesheet, so it follows a reader switching to the light theme. The second is the file that same run wrote, framed in terminal chrome and identical wherever it is embedded, theme included. That immutability is the point on a surface that cannot run code, and the drawback on one that can.
Note
:mirror: puts its region directly below the fence, which inside a tab set means inside that tab. A layout like this one references the image by hand instead: the link only ever changes when the capture is renamed, and Sphinx warns if the two fall out of step.
Tip
The two are independent on purpose. :screenshot: alone maintains an image some other surface embeds, which is how the before/after screens opening this project’s readme are produced: they come from the tutorial’s own blocks, and no one has to remember to reshoot them.
Caution
Captures are written into the documentation source tree, not the build output, since that is where a README finds them. A build therefore leaves them refreshed in your working copy: commit what changed. See screenshots for the surfaces this serves, and for capturing a CLI outside a documentation build.
Warning
Pick what you capture with the committed file in mind, because whatever the command prints is what gets checked into the repository. Verbose output is the trap: a single --verbosity DEBUG run echoes the configuration search, which means absolute paths, the host’s pyproject.toml, git hashes and kernel details, all frozen into an image and pushed. A live block gets away with it, being regenerated per build and never committed.
Two rules of thumb: prefer a command whose output depends only on the CLI, and read the image once before committing it. This project pins the application directory in conf.py for the same reason: click.get_app_dir() otherwise answers per platform, so a --config default would render one way on macOS and another on Linux, and every capture of a help screen would flip with its author’s laptop.
Standalone click:run blocks¶
You can also use the click:run directive without a preceding click:source block. This is useful when you want to demonstrate the usage of a CLI defined elsewhere, for example in your package’s source code.
In the example below, we import the click_extra.cli.demo function, which is defined in the click_extra/cli.py source file. There is no need to redefine the CLI in a click:source block beforehand:
```{click:run}
from click_extra.cli import demo
invoke(demo, args=["--help"])
```
.. click:run::
from click_extra.cli import demo
invoke(demo, args=["--help"])
The execution of that CLI renders as well:
$ click-extra --help
Usage: click-extra [OPTIONS] COMMAND [ARGS]...
Click Extra CLI.
Options:
-h, --help Show this message and exit.
Configuration options:
--config LOCATION Location of the configuration file. Supports
local path with glob patterns or remote URL.
[default: ~/.config/click-extra/]
--no-config Ignore all configuration files and only use
command line parameters and environment
variables.
--validate-config LOCATION Validate the configuration file and exit.
--export-config FORMAT Export the configuration in the selected format
to <stdout>, then exit.
Output options:
--accessible Accessibility mode: disable colors and render
tables in a borderless, screen-reader-friendly
format.
--color [auto|always|never] Colorize the output. A bare --color is the same
as --color=always. [default: auto]
--no-color Disable colorization (alias of --color=never).
--progress / --no-progress Show progress indicators during long operations.
Disabled for non-interactive output (pipes, dumb
terminals, CI) and by --accessible. [default:
progress]
--theme [auto|dark|dracula|light|manpage|monokai|nord|solarized-dark]
Color theme used for help screens. [default:
dark]
--table-format FORMAT Rendering style of tables. [default: rounded-
outline]
Logging options:
--verbosity LEVEL Either CRITICAL, ERROR, WARNING, INFO, DEBUG.
[default: WARNING]
-v, --verbose Increase the default WARNING verbosity by one
level for each additional repetition of the
option. [default: 0]
-q, --quiet Decrease the default WARNING verbosity by one
level for each additional repetition of the
option. [default: 0]
--debug Shorthand for --verbosity DEBUG.
Introspection options:
--time / --no-time Measure and print elapsed execution time.
[default: no-time]
--params Show all CLI parameters, their provenance,
defaults and value, then exit.
--tree Show the tree of nested subcommands and exit.
--man Read the command's manual page and exit.
--help-format [carapace|json|json-full|man|markdown|markdown-full]
Render the command in the given format and exit.
--version Show the version and exit.
Demo:
8color Render all standard 8-color foreground/background...
colors Render every foreground color against every background...
gradient Render 24-bit RGB gradients beside their 256-color...
palette Render a compact 256-color indexed swatch.
spinner Animate the spinner widget; --table lists the catalog...
styles Render every color with each text style (bold, dim,...
themes Render a sample help screen under each theme, one after...
trail Trace a simulated batch of operations behind an...
Other commands:
convert-to-myst Convert reST docstrings to MyST markdown in Python...
help Show help for a command.
prebake Pre-bake build-time metadata into Python source files.
refresh-directives Refresh the self-updating blocks embedded in Markdown...
screenshot Capture a command's colored output and write it as an...
snippet Highlight a source file and write it as an image or HTML.
test-suite Run declarative CLI test cases against a command or...
wrap (run) Run, or introspect, any Click CLI through Click Extra.
Examples:
Run any Click CLI through Click Extra's colored help:
$ click-extra wrap -- my-cli --help
Draw that help screen as a picture a README can show:
$ click-extra screenshot --output my-cli.svg -- my-cli --help
Report the parameters a CLI accepts, and where each value comes from:
$ click-extra wrap --params -- my-cli
Highlight a source file as a themed picture:
$ click-extra snippet --output basket.svg basket.py
See how a help screen reads under each built-in theme:
$ click-extra themes
Note
--version is safe in a live click:run block, and each block resolves it for itself. VersionOption finds the owning package by walking the call stack and memoizes the answer, which suits a CLI: one invocation, one process. A build renders many commands in one process, and a render carrying no CliRunner.invoke frame (click_extra_manpages writing roff before any page is read, on this project’s own docs) resolves the chain from a stack the walk cannot read. That answer used to stand for every later block, which published a version screen with no version on it. The runner now clears the memo before each invocation, so a documented --version reads as a reader’s own shell would render it.
{package_name} is a separate matter and still reports click_extra.sphinx for a CLI a page defines inline: such a command belongs to no importable module, so the walk correctly names the runner’s. The version page shows it.
Capture mode¶
click:run and click:tree execute the documented CLI through Click’s test runner. On Click 8.4 and later, the output is captured at the file-descriptor level (Click’s capture="fd" mode), so a CLI that writes through its stdout descriptor, such as one re-opening sys.stdout.fileno() to force UTF-8 output, renders normally instead of aborting the build with io.UnsupportedOperation.
Select the capture mode with the click_extra_run_capture value in conf.py:
conf.py¶click_extra_run_capture = "fd" # "fd" (default) or "sys"
Set it to "sys" to use Click’s legacy in-memory capture, which exposes no file descriptor. On Click releases older than 8.4 the value is ignored, as the capture parameter does not exist.
Inline tests¶
The click:run directive also embeds tests in your documentation.
Tests written there run at build time. They catch regressions early and keep the documentation up to date with the CLI, in the spirit of doctest and Docs as Tests.
For example, here is a simple CLI:
```{click:source}
from click import echo, command
@command
def yo_cli():
echo("Yo!")
```
.. click:source::
from click import echo, command
@command
def yo_cli():
echo("Yo!")
Put the code above in a click:source directive, and the following Python code in a click:run block:
```{click:run}
result = invoke(yo_cli, args=["--help"])
assert result.exit_code == 0, "CLI execution failed"
assert not result.stderr, "Found error messages in <stderr>"
assert "Usage: yo-cli [OPTIONS]" in result.stdout, "Usage line not found in help screen"
```
.. click:run::
result = invoke(yo_cli, args=["--help"])
assert result.exit_code == 0, "CLI execution failed"
assert not result.stderr, "Found error messages in <stderr>"
assert "Usage: yo-cli [OPTIONS]" in result.stdout, "Usage line not found in help screen"
The block collects the result of the invoke call, then inspects its exit_code, stderr and stdout with assert statements.
If the CLI changes and its help screen is no longer what the test expects, the build breaks with a message similar to:
Versions
========
* Platform: darwin; (macOS-15.5-arm64-64bit)
* Python version: 3.11.11 (CPython)
* Sphinx version: 8.2.3
* Docutils version: 0.21.2
* Jinja2 version: 3.1.6
* Pygments version: 2.19.2
Loaded Extensions
=================
(...)
* myst_parser (4.0.1)
* click_extra.sphinx (5.1.0)
Traceback
=========
File "(...)/click-extra/docs/sphinx.md:197", line 5, in <module>
AssertionError: Usage line not found in help screen
The full traceback has been saved in:
/var/folders/gr/1frk79j52flczzs2rrpfnkl80000gn/T/sphinx-err-5l6axu9g.log
Having your build fails when something unexpected happens is a great signal to catch regressions early.
On the other hand, if the build succeed, the click:run block will render as usual with the result of the invocation:
$ yo-cli --help
Usage: yo-cli [OPTIONS]
Options:
--help Show this message and exit.
Syntax highlight language¶
By default, code blocks produced by the directives are automatically highlighted with these languages:
click:source:pythonclick:run:ansi-shell-session
To override these defaults, pass the language as an optional parameter to the directive.
Take a CLI that only prints SQL queries:
from click_extra import echo, command, option
@command
@option("--name")
def sql_output(name):
sql_query = f"SELECT * FROM users WHERE name = '{name}';"
echo(sql_query)
Then you can force the SQL Pygments highlighter on its output by passing the short name of that lexer (sql) as the first argument to the directive:
```{click:run} sql
invoke(sql_output, args=["--name", "Joe"])
```
And renders to:
$ sql-output --name Joe
SELECT * FROM users WHERE name = 'Joe';
See how the output (the second line above) is now rendered with the sql Pygments lexer, which is more appropriate for SQL queries. But of course it also parse and renders the whole block as if it is SQL code, which mess up the rendering of the first line, as it is a shell command.
In fact, if you look at Sphinx logs, you will see that a warning has been raised because of that:
.../docs/sphinx.md:257: WARNING: Lexing literal_block "$ sql-output --name Joe\nSELECT * FROM users WHERE name = 'Joe';" as "sql" resulted in an error at token: '$'. Retrying in relaxed mode. [misc.highlighting_failure]
Hint
Alternatively, you can force syntax highlight with the :language: option, which takes precedence over the default language of the directive.
CLI reference tree¶
The click:tree directive walks a Click command group at build time and expands into a full CLI reference page: a summary table on top, then one --help capture per command, nested by depth. It is meant to replace per-project hand-rolled scripts that generate the same scaffolding (a summary table, anchors, one click:run per command) by hand.
The required argument is a Python expression evaluated in the per-document runner namespace; it must resolve to a click.Command. The optional body is Python preamble that runs in the same namespace before the expression is evaluated, so you can either rely on a prior click:source import or inline the import in the directive’s body.
Here is a small recipe CLI to demonstrate:
from click_extra import echo, command, group, option
@group()
def kitchen():
"""Manage kitchen tools and recipes."""
@kitchen.command()
@option("--minutes", type=int, default=5)
def boil(minutes):
"""Boil water for tea."""
echo(f"Boiling for {minutes} minutes.")
@kitchen.group()
def pantry():
"""Inspect pantry contents."""
@pantry.command()
def jars():
"""List jars on the shelf."""
echo("Olives, honey, pickles.")
@pantry.command()
@option("--fruit", default="apple")
def count(fruit):
"""Count fruits in the basket."""
echo(f"Three {fruit}s.")
A single click:tree invocation expands into a summary table plus one --help capture for kitchen, kitchen boil, kitchen pantry, kitchen pantry count, and kitchen pantry jars:
```{click:tree} kitchen
:root-label: kitchen --help
```
Which renders as:
kitchen –help¶
$ kitchen --help
Usage: kitchen [OPTIONS] COMMAND [ARGS]...
Manage kitchen tools and recipes.
Options:
-h, --help Show this message and exit.
Configuration options:
--config LOCATION Location of the configuration file. Supports
local path with glob patterns or remote URL.
[default: ~/.config/kitchen/]
--no-config Ignore all configuration files and only use
command line parameters and environment
variables.
--validate-config LOCATION Validate the configuration file and exit.
--export-config FORMAT Export the configuration in the selected format
to <stdout>, then exit.
Output options:
--accessible Accessibility mode: disable colors and render
tables in a borderless, screen-reader-friendly
format.
--color [auto|always|never] Colorize the output. A bare --color is the same
as --color=always. [default: auto]
--no-color Disable colorization (alias of --color=never).
--progress / --no-progress Show progress indicators during long operations.
Disabled for non-interactive output (pipes, dumb
terminals, CI) and by --accessible. [default:
progress]
--theme [auto|dark|dracula|light|manpage|monokai|nord|solarized-dark]
Color theme used for help screens. [default:
dark]
--table-format FORMAT Rendering style of tables. [default: rounded-
outline]
Logging options:
--verbosity LEVEL Either CRITICAL, ERROR, WARNING, INFO, DEBUG.
[default: WARNING]
-v, --verbose Increase the default WARNING verbosity by one
level for each additional repetition of the
option. [default: 0]
-q, --quiet Decrease the default WARNING verbosity by one
level for each additional repetition of the
option. [default: 0]
--debug Shorthand for --verbosity DEBUG.
Introspection options:
--time / --no-time Measure and print elapsed execution time.
[default: no-time]
--params Show all CLI parameters, their provenance,
defaults and value, then exit.
--tree Show the tree of nested subcommands and exit.
--man Read the command's manual page and exit.
--help-format [carapace|json|json-full|man|markdown|markdown-full]
Render the command in the given format and exit.
--version Show the version and exit.
Commands:
boil Boil water for tea.
help Show help for a command.
pantry Inspect pantry contents.
kitchen boil¶
$ kitchen boil --help
Usage: kitchen boil [OPTIONS]
Boil water for tea.
Options:
--minutes INTEGER [default: 5]
-h, --help Show this message and exit.
kitchen help¶
$ kitchen help --help
Usage: kitchen help [OPTIONS] [COMMAND_PATH]...
Show help for a command.
Options:
--search TEXT Search all subcommands for matching options or descriptions.
-h, --help Show this message and exit.
kitchen pantry¶
$ kitchen pantry --help
Usage: kitchen pantry [OPTIONS] COMMAND [ARGS]...
Inspect pantry contents.
Options:
-h, --help Show this message and exit.
Commands:
count Count fruits in the basket.
help Show help for a command.
jars List jars on the shelf.
kitchen pantry count¶
$ kitchen pantry count --help
Usage: kitchen pantry count [OPTIONS]
Count fruits in the basket.
Options:
--fruit TEXT [default: apple]
-h, --help Show this message and exit.
kitchen pantry help¶
$ kitchen pantry help --help
Usage: kitchen pantry help [OPTIONS] [COMMAND_PATH]...
Show help for a command.
Options:
--search TEXT Search all subcommands for matching options or descriptions.
-h, --help Show this message and exit.
kitchen pantry jars¶
$ kitchen pantry jars --help
Usage: kitchen pantry jars [OPTIONS]
List jars on the shelf.
Options:
-h, --help Show this message and exit.
Command |
Description |
|---|---|
Manage kitchen tools and recipes |
|
Boil water for tea |
|
Show help for a command |
|
Inspect pantry contents |
|
Count fruits in the basket |
|
Show help for a command |
|
List jars on the shelf |
Tree options¶
Option |
Description |
Default |
|---|---|---|
|
Maximum recursion depth into nested groups. |
|
|
Shift all generated headings down by N levels. Override when the auto-detected depth is wrong for the page layout. |
Surrounding section depth (root nested one level below the enclosing section). |
|
Slug prefix for every generated anchor. |
Slug of the CLI name. |
|
Display prefix for the command labels in the table and headings. |
The CLI name. |
|
Heading text for the root |
|
|
Skip the summary table. |
Table is rendered. |
|
Skip the root |
Root block is rendered. |
Inline import in the directive body¶
If the CLI lives in your package, you can skip the seed click:source block and import directly in the body:
```{click:tree} demo
from click_extra.cli import demo
```
The rendered output is identical to the kitchen example above: a summary table and one --help block per (sub)command. The only difference is where the CLI comes from: a package import instead of a preceding click:source block.
Note
click:tree is currently MyST-only because the expanded scaffolding uses MyST’s (label)= anchor syntax and pipe tables. An rST equivalent would emit .. _label: targets and list-table:: directives instead; it has not been implemented yet.
Configuration reference¶
The click:config directive documents a CLI’s config_schema at build time: a summary table linking each option to its section, then one heading per option with its docstring, type, default, and a TOML example pinned to the default value. Like click:tree, it replaces per-project hand-rolled generators that produce the same reference from the schema dataclass by hand.
The required argument is a Python expression evaluated in the per-document runner namespace. It accepts either a click.Command whose config_schema is wired (the schema is pulled off its ConfigOption), or a schema dataclass directly. The optional body is Python preamble, same as click:tree.
Click Extra’s own CLI declares a config_schema, so a single invocation documents its [tool.click-extra] section:
```{click:config} demo
from click_extra.cli import demo
```
Which renders as:
prebake.module¶
Path to the __init__.py to pre-bake, resolved relative to the project root. Overrides the [project.scripts] auto-discovery; leave unset to keep it.
Type: str | Default: (none)
test-suite.cases¶
Test cases written natively in the config format, an alternative to a file suite, taking precedence over it when both are set.
Type: list[dict] | Default: []
Each entry is a mapping of CLITestCase directive names, equivalent to one
item of a suite list. In TOML this reads as a [[tool.<cli>.test-suite.cases]]
array of tables. Declared as an extension point so the configuration engine
passes the raw mappings through unprocessed; the test-suite command turns
them into CLITestCase instances.
Example:
[tool.click-extra]
test-suite.cases = []
test-suite.file¶
Path to a test suite file, resolved relative to the project root.
Type: str | Default: "./tests/cli-test-suite.toml"
Its format is detected from the extension; the default is TOML, which (like JSON) parses with no optional dependency, unlike YAML and the others.
Example:
[tool.click-extra]
test-suite.file = "./tests/cli-test-suite.toml"
test-suite.timeout¶
Default timeout (seconds) for each case that does not set its own.
Type: int | Default: (none)
None leaves cases unbounded unless --timeout is passed.
Option |
Description |
Default |
|---|---|---|
Path to the |
(none) |
|
Test cases written natively in the config format, an alternative to a |
|
|
Path to a test suite file, resolved relative to the project root. |
|
|
Default timeout (seconds) for each case that does not set its own. |
(none) |
Config options¶
Option |
Description |
Default |
|---|---|---|
|
Shift all generated headings down by N levels. Override when the auto-detected depth is wrong for the page layout. |
Surrounding section depth (options nested one level below the enclosing section). |
|
TOML table header shown in the per-option examples. An explicitly empty value suppresses the header. |
|
|
Skip the summary table. |
Table is rendered. |
|
Skip the TOML example blocks. |
Examples are rendered. |
Option metadata comes from the schema_field_infos() introspection helper, which is also part of the public API for CLIs that render their own configuration reference (a show-config table, say): dotted kebab-case keys, type annotations, defaults from a pristine schema instance, and attribute docstrings. Docstrings are parsed as the host document’s markup, and their first paragraph doubles as the option’s summary in the table.
Caution
Attribute docstrings are recovered from the schema’s source file. A schema defined inline in a click:source block was born in an exec call, has no source file, and therefore documents its options without descriptions. Import the schema from a real module instead, as in the example above.
Note
click:config is currently MyST-only: place it in a .md document with myst_parser enabled. Like click:tree, an rST equivalent has not been implemented yet.
GitHub alerts¶
A GitHub-flavored Markdown alert is a blockquote GitHub renders as a colored callout. Click Extra’s Sphinx extension converts each one into a MyST admonition, so the same source renders on GitHub and in the built documentation.
Deprecated since version 7.16.0: myst-parser 5.1.0 ships a native alert syntax extension covering the same five alert types. On that release and above, Click Extra’s converter steps aside: add "alert" to myst_enable_extensions and the rest of this section no longer applies, colon_fence included.
Setup¶
On myst-parser below 5.1.0, you need to enable the colon_fence extension in your Sphinx configuration, as the converter renders each alert as a colon fence:
conf.py¶extensions = [
...
"click_extra.sphinx",
]
myst_enable_extensions = ["colon_fence"]
Supported alert types¶
GitHub supports five alert types, all of which are replaced behind the scenes with their corresponding MyST admonitions:
Type |
GitHub syntax |
MyST syntax |
Rendered |
|---|---|---|---|
Note |
> [!NOTE]
> Useful information.
|
:::{note}
Useful information.
:::
|
Note Useful information. |
Tip |
> [!TIP]
> Helpful advice.
|
:::{tip}
Helpful advice.
:::
|
Tip Helpful advice. |
Important |
> [!IMPORTANT]
> Key information.
|
:::{important}
Key information.
:::
|
Important Key information. |
Warning |
> [!WARNING]
> Potential issues.
|
:::{warning}
Potential issues.
:::
|
Warning Potential issues. |
Caution |
> [!CAUTION]
> Negative consequences.
|
:::{caution}
Negative consequences.
:::
|
Caution Negative consequences. |
Usage¶
Write alerts using GitHub’s blockquote syntax:
> [!NOTE]
> This is a note that will render as an admonition in Sphinx.
> [!WARNING]
> Reader discretion is strongly advised.
These will render in Sphinx as:
Note
This is a note that will render as an admonition in Sphinx.
Warning
Reader discretion is strongly advised.
Rules¶
Playing with alerts on various GitHub websites, I reverse-engineered the following specifications:
Alert type must be in uppercase:
[!TIP], not[!tip].No spaces in the directive:
[! NOTE],[!NOTE ]or[ !NOTE]are invalid.Must be the first thing in the blockquote:
> Hello [!NOTE] This is a note.is interpreted as a normal blockquote, not an alert.Only the first line of the blockquote is parsed for the alert type: subsequent lines are considered part of the alert content.
The alert content can span multiple lines, as long as they are part of the same blockquote.
Empty blockquotes are ignored:
> [!TIP]without any content is not rendered.Nested blockquotes are supported: the alert content can contain other blockquotes, lists, code blocks, etc.
Nested alerts¶
GitHub alerts support nested content, including other blockquotes, lists, code blocks, and even nested alerts. This allows for complex documentation structures that render correctly both on GitHub and in Sphinx.
You can include various Markdown elements inside an alert:
> [!NOTE]
> This alert contains:
> - A bullet list
> - With multiple items
>
> And a code block:
> ```python
> print("Hello, world!")
> ```
Which renders as:
Note
This alert contains:
A bullet list
With multiple items
And a code block:
print("Hello, world!")
You can nest alerts within alerts for hierarchical information:
> [!WARNING]
> Be careful with this operation.
>
> > [!TIP]
> > If you encounter issues, try restarting the service.
Which renders as:
Warning
Be careful with this operation.
Tip
If you encounter issues, try restarting the service.
Todo-list deduplication¶
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 one docstring several times:
A full-API page plus per-feature pages. A project that documents every module on one page, then documents the same modules again next to the prose explaining them, renders each docstring twice. Marking the second block
:no-index:does not help: that option suppresses the cross-reference target and the search-index entry, and leaves the docstring rendered in full.A package that re-exports its members.
automoduledocuments the imported names a package lists in__all__, so a symbol shows up once under the package and once under the module defining it. Both renderings can land on the same page.
The two multiply. Before this hook, Click Extra’s own todo-list showed 35 entries for 17 distinct {todo} directives, one of them repeated four times.
Nothing upstream deduplicates, so click_extra.sphinx trims the surplus nodes just before the list is rendered. No configuration is needed, and a project that enables neither the extension nor a todolist never notices the hook.
Among the renderings of one directive, the surviving entry is the first in (reached through a defining module, document name, position in the document) order. So a symbol reached through both its package and its module keeps the module’s attribution, and a reader following the original entry backlink lands on the same page from one build to the next.
Set the flag to get Sphinx’s raw output back, one entry per rendering:
conf.py¶click_extra_dedupe_todos = False
ANSI shell sessions¶
The extension registers Click Extra’s ANSI-capable lexers with Sphinx’s highlighter. Name one as the language of a code block, and the escape sequences render as colors instead of raw bytes:
```{code-block} ansi-shell-session
$ my-cli --help
[1mUsage:[0m [97mmy-cli[0m [36m[2m[OPTIONS][0m [36m[2mCOMMAND[0m [36m[2m[ARGS][0m...
Manage recipes and shopping lists.
[1mOptions:[0m
[36m--name[0m [36m[2mTEXT[0m Your name.
[36m--help[0m Show this message and exit.
```
.. code-block:: ansi-shell-session
$ my-cli --help
[1mUsage:[0m [97mmy-cli[0m [36m[2m[OPTIONS][0m [36m[2mCOMMAND[0m [36m[2m[ARGS][0m...
Manage recipes and shopping lists.
[1mOptions:[0m
[36m--name[0m [36m[2mTEXT[0m Your name.
[36m--help[0m Show this message and exit.
Either form renders in full color:
$ my-cli --help
[1mUsage:[0m [97mmy-cli[0m [36m[2m[OPTIONS][0m [36m[2mCOMMAND[0m [36m[2m[ARGS][0m...
Manage recipes and shopping lists.
[1mOptions:[0m
[36m--name[0m [36m[2mTEXT[0m Your name.
[36m--help[0m Show this message and exit.
The lexer variants table lists every language these lexers cover, and lexers usage shows what the same block looks like without them.
Legacy MyST + reStructuredText syntax¶
Before MyST was fully integrated into Sphinx, many projects used a mixed syntax setup with MyST and reStructuredText. If you are maintaining such a project or need to ensure compatibility with older documentation, you can use these legacy Sphinx snippets.
This rely on MyST’s ability to embed reStructuredText within MyST documents, via the {eval-rst} directive.
So instead of using the {click:source} and {click:run} MyST directive, you can wrap your reStructuredText code blocks with {eval-rst}:
```{eval-rst}
.. click:source::
from click import echo, command
@command
def yo_cli():
echo("Yo!")
.. click:run::
invoke(yo_cli)
```
Which renders to:
from click import echo, command
@command
def yo_cli():
echo("Yo!")
$ yo-cli
Yo!
Warning
CLI states and references are lost as soon as an {eval-rst} block ends. So a .. click:source:: directive needs to have all its associated .. click:run:: calls within the same rST block.
If not, you are likely to encounter execution tracebacks such as:
File ".../click-extra/docs/sphinx.md:372", line 1, in <module>
NameError: name 'yo_cli' is not defined
click_extra.sphinx API¶
classDiagram
StatelessDomain <|-- ClickDomain
StatelessDomain <|-- PythonDomain
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 MyST converters in
click_extra.myst_converter and click_extra.rst_to_myst, and the
marker-region primitives 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.MYST_NATIVE_ALERTS_VERSION = <Version('5.1.0')>
First
myst-parserrelease that ships the native"alert"syntax extension.Below this version,
click_extra.sphinx.alertspatches GitHub alert syntax into MyST admonitions via asource-read/include-readhook. At or above this version, the converter is skipped atsetup()time and projects should add"alert"tomyst_enable_extensionsinstead. A project with nomyst-parserinstalled writes no MyST document, so the converter is skipped there too.
- click_extra.sphinx.EXEC_DIRECTIVES_OPT_IN = 'click_extra_enable_exec_directives'
Name of the
conf.pyconfig flag that gates every code-execution directive.Default is
False. A project that addsclick_extra.sphinxto itsextensionslist gets the ANSI Pygments formatter unconditionally, plus the GitHub-alerts converter whenmyst-parseris belowMYST_NATIVE_ALERTS_VERSION(seealertsfor the deprecation rationale), but does not gain access to either theclick:*or thepython:*directive families until the maintainer opts in explicitly. Both familiesexecuser-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.SCREENSHOT_DIR_CONFIG = 'click_extra_screenshot_dir'
Name of the
conf.pyvalue locating the directoryclick:runwrites captures to.A path relative to the documentation source directory, holding the SVG a
click:runblock names with its:screenshot:option. Defaults toassets, 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.SCREENSHOT_PRESET_CONFIG = 'click_extra_screenshot_preset'
Name of the
conf.pyvalue naming the terminal every capture is drawn as.One of
PRESETS, applied to eachclick:runblock 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.SCREENSHOT_SYNTAX_STYLE_CONFIG = 'click_extra_screenshot_syntax_style'
Name of the
conf.pyvalue 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, seeDEFAULT_SYNTAX_STYLES. Unused by a block picturing what a command printed, whose colors that command already chose.
- click_extra.sphinx.SCREENSHOT_WATERMARK_CONFIG = 'click_extra_screenshot_watermark'
Name of the
conf.pyvalue crediting every capture aclick:runwrites.Empty by default, where the
screenshotcommand credits click-extra: a capture written by a documentation build is rewritten and committed on every build, so a mark naming a release would rewrite every image the day that release changes, and the page carrying the image already says what drew it. A project wanting one anyway states the text here, or per block with:screenshot-watermark:.
- click_extra.sphinx.RUN_CAPTURE_CONFIG = 'click_extra_run_capture'
Name of the
conf.pyvalue selecting the stream-capture mode for the CLIs thatclick:runandclick:treeexecute.Maps to the
captureparameter of Click’sCliRunner,"sys"or"fd"(added in Click 8.4). Defaults to"fd"so a command writing throughsys.stdout.fileno()is captured at the file-descriptor level and renders, instead of aborting the build withio.UnsupportedOperation. Ignored on Click releases older than 8.4, which lack the parameter.
- 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.PygmentsBridgewith one that renders ANSI colors in code blocks).GitHub-flavored alert syntax (
> [!NOTE], etc.) in included and regular source files, converted to MyST/reST admonitions. Registered only when the installedmyst-parseris belowMYST_NATIVE_ALERTS_VERSION(5.1.0). On newer versions, the converter is skipped and a one-shot info message points users atmyst-parser’s native"alert"extension; with nomyst-parserinstalled it is skipped without a message. Seeclick_extra.sphinx.alertsfor the deprecation plan.The
matrixdirective, which renders a package’s compatibility grid ({matrix} pythonor{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. Seeclick_extra.sphinx.matrix.Deduplication of the
todolistpage, whichsphinx.ext.todofills with one entry per rendering of a:todo:directive rather than one per directive. Inert on a project that enables neither the extension nor atodolist, and switched off withclick_extra.sphinx.todos.DEDUPE_TODOS_CONFIG. Seeclick_extra.sphinx.todos.
Opt-in features (gated behind
click_extra_enable_exec_directives):click:source/click:runto define and execute Click CLIs at build time.python:source/python:runto execute arbitrary Python at build time and render its source or capturedstdout.python:render/python:render-myst/python:render-rstto execute arbitrary Python and parse the capturedstdoutas 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 = Trueinconf.pyto register them.Caution
This function forces the Sphinx app to use
sphinx.highlighting.PygmentsBridgeinstead of the default HTML formatter to add support for ANSI colors in code blocks.- Return type:
ExtensionMetadata