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]

Setup

Once Click Extra is installed, you can enable its extensions in your Sphinx’s conf.py:

conf.py
extensions = [
    ...
    "click_extra.sphinx",
]

Tip

I recommend using one of these themes, which works well with Click Extra:

click: directives

Click Extra adds two new directives:

  • click:source to define and show the source code of a Click CLI in Sphinx.

  • click:run to invoke the CLI defined above, and display the results as if 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.

Hint

click:source directive has an alias called click:example. Both behaves exactly the same way, but click:example is deprecated and will be removed in a future major release.

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 we invoke our 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.
  --time / --no-time    Measure and print elapsed execution time.  [default: no-
                        time]
  --color, --ansi / --no-color, --no-ansi
                        Strip out all colors and all ANSI codes from output.
                        [default: color]
  --config CONFIG_PATH  Location of the configuration file. Supports local path
                        with glob patterns or remote URL.  [default:
                        ~/.config/hello-world/*.toml|*.yaml|*.yml|*.json|*.json5
                        |*.jsonc|*.hjson|*.ini|*.xml]
  --no-config           Ignore all configuration files and only use command line
                        parameters and environment variables.
  --show-params         Show all CLI parameters, their provenance, defaults and
                        value, then exit.
  --table-format [asciidoc|csv|csv-excel|csv-excel-tab|csv-unix|double-grid|double-outline|fancy-grid|fancy-outline|github|grid|heavy-grid|heavy-outline|html|jira|latex|latex-booktabs|latex-longtable|latex-raw|mediawiki|mixed-grid|mixed-outline|moinmoin|orgtbl|outline|pipe|plain|presto|pretty|psql|rounded-grid|rounded-outline|rst|simple|simple-grid|simple-outline|textile|tsv|unsafehtml|vertical|youtrack]
                        Rendering style of tables.  [default: rounded-outline]
  --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]
  --version             Show the version and exit.
  -h, --help            Show this message 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

click:source and click:run directives works well with standard vanilla click-based CLIs.

In the example above, we choose to import our CLI primitives from the click-extra module instead, to demonstrate the coloring of terminal session outputs, as click-extra provides fancy coloring of help screens by default.

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

:linenos:

Display line numbers.

:linenos:

:lineno-start:

Specify the starting line number.

:lineno-start: 10

:emphasize-lines:

Highlight specific lines.

:emphasize-lines: 2,4-6

:force:

Ignore minor errors on highlighting.

:force:

:caption:

Set a caption for the code block.

:caption: My Code Example

:name:

Set a name for the code block (useful for cross-referencing).

:name: example-1

:class:

Set a CSS class for the code block.

:class: highlight

:dedent:

Specify the number of spaces to remove from the beginning of each line.

:dedent: 4

:language:

Specify the programming language for syntax highlighting. This can be used as an alternative to passing the language as an argument.

:language: sql

:show-source:/:hide-source:

Flags to force the source code within the directive to be rendered or not.

:show-source: or :hide-source:

:show-results:/:hide-results:

Flags to force the results of the CLI invocation to be rendered or not. Only applies to click:run. Is silently ignored in click:source.

:show-results: or :hide-results:

:show-prompt:/:hide-prompt:

TODO

TODO

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:

A magnificent ✨ Hello World CLI!
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:source displays 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:run displays 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.

But you can override this behavior by explicitly setting the options. Let’s say you only want to display the result of the CLI invocation, without showing the source code defining that CLI. Then you can 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.

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=["--version"])
```
.. click:run::

   from click_extra.cli import demo
   invoke(demo, args=["--version"])

And the execution of that CLI renders just fine:

$ demo --version
demo, version None

Inline tests

The click:run directive can also be used to embed tests in your documentation.

You can write tests in your documentation, and they will be executed at build time. This allows you to catch regressions early, and ensure that your documentation is always up-to-date with the latest version of your 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!")

Let’s put the code above in a click:source directive. And then put the following Python code into 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"

See how we collect here the result of the invoke command, and separately inspect the exit_code, stderr and stdout of with assert statements.

If for any reason our CLI changes and its help screen is no longer what we expect, the test will fail and the documentation build will break 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:

If for any reason you want to override these defaults, you can pass the language as an optional parameter to the directive.

Let’s say you have a CLI that is only printing SQL queries in its output:

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 (i.e. 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 (i.e. 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.

GitHub alerts

Click Extra’s Sphinx extension automatically converts GitHub-flavored Markdown alerts into MyST admonitions.

This allows you to write documentation that renders correctly both on GitHub and in your Sphinx-generated documentation.

Setup

To use GitHub alerts, you need to enable the colon_fence extension in your Sphinx configuration:

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.

You can also mix GitHub alerts with MyST directives inside container directives:

````{note}
> [!TIP]
> First alert.

```{warning}
Nested MyST warning.
```

> [!CAUTION]
> Second alert after nested directive.
````

Note

Tip

First alert.

Warning

Nested MyST warning.

Caution

Second alert after nested directive.

For more complex documentation, you can combine multiple nested elements such as blockquotes, numbered lists, nested alerts, and code blocks:

> [!IMPORTANT]
> Before proceeding, ensure you have:
>
> 1. Backed up your data
> 2. Reviewed the changelog
>
> > This is important context that applies to all the steps above.
>
> > [!CAUTION]
> > This action cannot be undone.
>
> ```bash
> $ make backup
> ```

Which renders as:

Important

Before proceeding, ensure you have:

  1. Backed up your data

  2. Reviewed the changelog

This is important context that applies to all the steps above.

Caution

This action cannot be undone.

$ make backup

ANSI shell sessions

Sphinx extensions from Click Extra automaticcaly integrates the new ANSI-capable lexers for Pygments.

This allows you to render colored shell sessions in code blocks by referring to the ansi- prefixed lexers:

```{code-block} ansi-shell-session
$ # Print ANSI foreground colors.
$ for i in {0..255}; do \
>     printf '\e[38;5;%dm%3d ' $i $i \
>     (((i+3) % 18)) || printf '\e[0m\n' \
> done
  0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15 
 16  17  18  19  20  21  22  23  24  25  26  27  28  29  30  31  32  33 
 34  35  36  37  38  39  40  41  42  43  44  45  46  47  48  49  50  51 
 52  53  54  55  56  57  58  59  60  61  62  63  64  65  66  67  68  69 
 70  71  72  73  74  75  76  77  78  79  80  81  82  83  84  85  86  87 
 88  89  90  91  92  93  94  95  96  97  98  99 100 101 102 103 104 105 
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 
142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 
178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 
196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 
214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 
232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 
250 251 252 253 254 255
```
.. code-block:: ansi-shell-session

    $ # Print ANSI foreground colors.
    $ for i in {0..255}; do \
    >     printf '\e[38;5;%dm%3d ' $i $i \
    >     (((i+3) % 18)) || printf '\e[0m\n' \
    > done
      0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15 
     16  17  18  19  20  21  22  23  24  25  26  27  28  29  30  31  32  33 
     34  35  36  37  38  39  40  41  42  43  44  45  46  47  48  49  50  51 
     52  53  54  55  56  57  58  59  60  61  62  63  64  65  66  67  68  69 
     70  71  72  73  74  75  76  77  78  79  80  81  82  83  84  85  86  87 
     88  89  90  91  92  93  94  95  96  97  98  99 100 101 102 103 104 105 
    106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 
    124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 
    142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 
    160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 
    178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 
    196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 
    214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 
    232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 
    250 251 252 253 254 255

In Sphinx, the snippet above renders to:

$ # Print ANSI foreground colors.
$ for i in {0..255}; do \
>     printf '\e[38;5;%dm%3d ' $i $i \
>     (((i+3) % 18)) || printf '\e[0m\n' \
> done
  0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15 
 16  17  18  19  20  21  22  23  24  25  26  27  28  29  30  31  32  33 
 34  35  36  37  38  39  40  41  42  43  44  45  46  47  48  49  50  51 
 52  53  54  55  56  57  58  59  60  61  62  63  64  65  66  67  68  69 
 70  71  72  73  74  75  76  77  78  79  80  81  82  83  84  85  86  87 
 88  89  90  91  92  93  94  95  96  97  98  99 100 101 102 103 104 105 
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 
142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 
178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 
196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 
214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 
232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 
250 251 252 253 254 255

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
  Domain <|-- ClickDomain
    

Helpers and utilities for Sphinx.

click_extra.sphinx.setup(app)[source]

Register extensions to Sphinx.

  • New directives, augmented with ANSI coloring.

  • Support for GitHub alerts syntax in included and regular source files.

Caution

This function forces the Sphinx app to use sphinx.highlighting.PygmentsBridge instead of the default HTML formatter to add support for ANSI colors in code blocks.

Return type:

ExtensionMetadata