Sphinx extensions¶
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
:
extensions = ["click_extra.sphinx", ...]
Click directives¶
Click Extra adds two new directives:
.. click:example::
to display any Click-based Python code blocks in Sphinx (and renders like.. code-block:: python
).. click:run::
to invoke the CLI defined above, and display the results as if was executed in a terminmal (within a.. code-block:: ansi-shell-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.
Hint
These directives are based on the official Pallets-Sphinx-Themes
from Click’s authors, but augmented with support for ANSI coloring. That way you can show off your user-friendly CLI in all its glory. 🌈
See also
Click Extra’s own documentation extensively use .. click:example::
and .. click:run::
directives. Look around
in its Markdown source files for advanced examples and
inspiration.
Usage¶
Here is how to define a simple Click-based CLI with the .. click:example::
directive:
Attention
As you can see in the example below, these Click directives are not recognized as-is by the MyST parser, so you need to wrap them in {eval-rst}
blocks.
```{eval-rst}
.. click:example::
from click_extra import echo, extra_command, option, style
@extra_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')}!")
```
Thanks to the .. click:run::
directive, we can invoke this CLI with its --help
option:
```{eval-rst}
.. click:run::
invoke(hello_world, args=["--help"])
```
Warning
CLI states and references are lost as soon as an {eval-rst}
block ends. If you need to run a .. click:example::
definition multiple times, all its .. click:run::
calls must happens within the same rST block.
A symptom of that issue is the execution failing with tracebacks such as:
Exception occurred:
File "<docs>", line 1, in <module>
NameError: name 'hello_world' is not defined
.. click:example::
from click_extra import echo, extra_command, option, style
@extra_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')}!")
Thanks to the .. click:run::
directive, we can invoke this CLI with its --help
option:
.. click:run::
invoke(hello_world, args=["--help"])
Placed in your Sphinx documentation, the two blocks above renders to:
from click_extra import echo, extra_command, option, style
@extra_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]
-C, --config CONFIG_PATH Location of the configuration file. Supports glob
pattern of local path and remote URL. [default:
~/.config/hello-
world/*.{toml,yaml,yml,json,ini,xml}]
--show-params Show all CLI parameters, their provenance, defaults
and value, then exit.
-v, --verbosity LEVEL Either CRITICAL, ERROR, WARNING, INFO, DEBUG.
[default: WARNING]
--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.
See for instance 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"])
Which renders in Sphinx like it was executed in a terminal block:
$ hello-world --name Joe
Hello, Joe!
Tip
.. click:example::
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.
Inline tests¶
The .. click:run::
directive can also be used to embed tests in your documentation.
These blocks are Python code executed at build time, so you can use them to validate the behavior of your CLI. This allow you to catch regressions, outdated documentation or changes in terminal output.
For example, here is a simple CLI:
from click_extra import echo, extra_command, option, style
@extra_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')}!")
If we put this CLI code in a .. click:example::
directive, we can associate it with the following .. click:run::
block:
.. click:run::
result = invoke(hello_world, args=["--help"])
assert result.exit_code == 0, "CLI execution failed"
assert not result.stderr, "error message in <stderr>"
assert "--show-params" in result.stdout, "--show-params not found in help screen"
See how we collect the result
of the invoke
command, and inspect the exit_code
, stderr
and stdout
of the CLI 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:
Exception occurred:
File "<docs>", line 5, in <module>
AssertionError: --show-params not found in help screen
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:
$ 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]
-C, --config CONFIG_PATH Location of the configuration file. Supports glob
pattern of local path and remote URL. [default:
~/.config/hello-
world/*.{toml,yaml,yml,json,ini,xml}]
--show-params Show all CLI parameters, their provenance, defaults
and value, then exit.
-v, --verbosity LEVEL Either CRITICAL, ERROR, WARNING, INFO, DEBUG.
[default: WARNING]
--version Show the version and exit.
-h, --help Show this message and exit.
Tip
In a way, you can consider this kind of inline tests as like doctests, but for Click CLIs.
Look around in the sources of Click Extra’s documentation for more examples of inline tests.
Hint
The CLI runner used by .. click:run::
is a custom version derived from the original click.testing.CliRunner
.
It is called ExtraCliRunner
and is patched so you can refine your tests by inspecting both <stdout>
and <stderr>
independently. It also provides an additional <output>
stream which simulates what the user sees in its terminal.
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:
```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
[38;5;0m 0 [38;5;1m 1 [38;5;2m 2 [38;5;3m 3 [38;5;4m 4 [38;5;5m 5 [38;5;6m 6 [38;5;7m 7 [38;5;8m 8 [38;5;9m 9 [38;5;10m 10 [38;5;11m 11 [38;5;12m 12 [38;5;13m 13 [38;5;14m 14 [38;5;15m 15 [0m
[38;5;16m 16 [38;5;17m 17 [38;5;18m 18 [38;5;19m 19 [38;5;20m 20 [38;5;21m 21 [38;5;22m 22 [38;5;23m 23 [38;5;24m 24 [38;5;25m 25 [38;5;26m 26 [38;5;27m 27 [38;5;28m 28 [38;5;29m 29 [38;5;30m 30 [38;5;31m 31 [38;5;32m 32 [38;5;33m 33 [0m
[38;5;34m 34 [38;5;35m 35 [38;5;36m 36 [38;5;37m 37 [38;5;38m 38 [38;5;39m 39 [38;5;40m 40 [38;5;41m 41 [38;5;42m 42 [38;5;43m 43 [38;5;44m 44 [38;5;45m 45 [38;5;46m 46 [38;5;47m 47 [38;5;48m 48 [38;5;49m 49 [38;5;50m 50 [38;5;51m 51 [0m
[38;5;52m 52 [38;5;53m 53 [38;5;54m 54 [38;5;55m 55 [38;5;56m 56 [38;5;57m 57 [38;5;58m 58 [38;5;59m 59 [38;5;60m 60 [38;5;61m 61 [38;5;62m 62 [38;5;63m 63 [38;5;64m 64 [38;5;65m 65 [38;5;66m 66 [38;5;67m 67 [38;5;68m 68 [38;5;69m 69 [0m
[38;5;70m 70 [38;5;71m 71 [38;5;72m 72 [38;5;73m 73 [38;5;74m 74 [38;5;75m 75 [38;5;76m 76 [38;5;77m 77 [38;5;78m 78 [38;5;79m 79 [38;5;80m 80 [38;5;81m 81 [38;5;82m 82 [38;5;83m 83 [38;5;84m 84 [38;5;85m 85 [38;5;86m 86 [38;5;87m 87 [0m
[38;5;88m 88 [38;5;89m 89 [38;5;90m 90 [38;5;91m 91 [38;5;92m 92 [38;5;93m 93 [38;5;94m 94 [38;5;95m 95 [38;5;96m 96 [38;5;97m 97 [38;5;98m 98 [38;5;99m 99 [38;5;100m100 [38;5;101m101 [38;5;102m102 [38;5;103m103 [38;5;104m104 [38;5;105m105 [0m
[38;5;106m106 [38;5;107m107 [38;5;108m108 [38;5;109m109 [38;5;110m110 [38;5;111m111 [38;5;112m112 [38;5;113m113 [38;5;114m114 [38;5;115m115 [38;5;116m116 [38;5;117m117 [38;5;118m118 [38;5;119m119 [38;5;120m120 [38;5;121m121 [38;5;122m122 [38;5;123m123 [0m
[38;5;124m124 [38;5;125m125 [38;5;126m126 [38;5;127m127 [38;5;128m128 [38;5;129m129 [38;5;130m130 [38;5;131m131 [38;5;132m132 [38;5;133m133 [38;5;134m134 [38;5;135m135 [38;5;136m136 [38;5;137m137 [38;5;138m138 [38;5;139m139 [38;5;140m140 [38;5;141m141 [0m
[38;5;142m142 [38;5;143m143 [38;5;144m144 [38;5;145m145 [38;5;146m146 [38;5;147m147 [38;5;148m148 [38;5;149m149 [38;5;150m150 [38;5;151m151 [38;5;152m152 [38;5;153m153 [38;5;154m154 [38;5;155m155 [38;5;156m156 [38;5;157m157 [38;5;158m158 [38;5;159m159 [0m
[38;5;160m160 [38;5;161m161 [38;5;162m162 [38;5;163m163 [38;5;164m164 [38;5;165m165 [38;5;166m166 [38;5;167m167 [38;5;168m168 [38;5;169m169 [38;5;170m170 [38;5;171m171 [38;5;172m172 [38;5;173m173 [38;5;174m174 [38;5;175m175 [38;5;176m176 [38;5;177m177 [0m
[38;5;178m178 [38;5;179m179 [38;5;180m180 [38;5;181m181 [38;5;182m182 [38;5;183m183 [38;5;184m184 [38;5;185m185 [38;5;186m186 [38;5;187m187 [38;5;188m188 [38;5;189m189 [38;5;190m190 [38;5;191m191 [38;5;192m192 [38;5;193m193 [38;5;194m194 [38;5;195m195 [0m
[38;5;196m196 [38;5;197m197 [38;5;198m198 [38;5;199m199 [38;5;200m200 [38;5;201m201 [38;5;202m202 [38;5;203m203 [38;5;204m204 [38;5;205m205 [38;5;206m206 [38;5;207m207 [38;5;208m208 [38;5;209m209 [38;5;210m210 [38;5;211m211 [38;5;212m212 [38;5;213m213 [0m
[38;5;214m214 [38;5;215m215 [38;5;216m216 [38;5;217m217 [38;5;218m218 [38;5;219m219 [38;5;220m220 [38;5;221m221 [38;5;222m222 [38;5;223m223 [38;5;224m224 [38;5;225m225 [38;5;226m226 [38;5;227m227 [38;5;228m228 [38;5;229m229 [38;5;230m230 [38;5;231m231 [0m
[38;5;232m232 [38;5;233m233 [38;5;234m234 [38;5;235m235 [38;5;236m236 [38;5;237m237 [38;5;238m238 [38;5;239m239 [38;5;240m240 [38;5;241m241 [38;5;242m242 [38;5;243m243 [38;5;244m244 [38;5;245m245 [38;5;246m246 [38;5;247m247 [38;5;248m248 [38;5;249m249 [0m
[38;5;250m250 [38;5;251m251 [38;5;252m252 [38;5;253m253 [38;5;254m254 [38;5;255m255
```
.. 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
[38;5;0m 0 [38;5;1m 1 [38;5;2m 2 [38;5;3m 3 [38;5;4m 4 [38;5;5m 5 [38;5;6m 6 [38;5;7m 7 [38;5;8m 8 [38;5;9m 9 [38;5;10m 10 [38;5;11m 11 [38;5;12m 12 [38;5;13m 13 [38;5;14m 14 [38;5;15m 15 [0m
[38;5;16m 16 [38;5;17m 17 [38;5;18m 18 [38;5;19m 19 [38;5;20m 20 [38;5;21m 21 [38;5;22m 22 [38;5;23m 23 [38;5;24m 24 [38;5;25m 25 [38;5;26m 26 [38;5;27m 27 [38;5;28m 28 [38;5;29m 29 [38;5;30m 30 [38;5;31m 31 [38;5;32m 32 [38;5;33m 33 [0m
[38;5;34m 34 [38;5;35m 35 [38;5;36m 36 [38;5;37m 37 [38;5;38m 38 [38;5;39m 39 [38;5;40m 40 [38;5;41m 41 [38;5;42m 42 [38;5;43m 43 [38;5;44m 44 [38;5;45m 45 [38;5;46m 46 [38;5;47m 47 [38;5;48m 48 [38;5;49m 49 [38;5;50m 50 [38;5;51m 51 [0m
[38;5;52m 52 [38;5;53m 53 [38;5;54m 54 [38;5;55m 55 [38;5;56m 56 [38;5;57m 57 [38;5;58m 58 [38;5;59m 59 [38;5;60m 60 [38;5;61m 61 [38;5;62m 62 [38;5;63m 63 [38;5;64m 64 [38;5;65m 65 [38;5;66m 66 [38;5;67m 67 [38;5;68m 68 [38;5;69m 69 [0m
[38;5;70m 70 [38;5;71m 71 [38;5;72m 72 [38;5;73m 73 [38;5;74m 74 [38;5;75m 75 [38;5;76m 76 [38;5;77m 77 [38;5;78m 78 [38;5;79m 79 [38;5;80m 80 [38;5;81m 81 [38;5;82m 82 [38;5;83m 83 [38;5;84m 84 [38;5;85m 85 [38;5;86m 86 [38;5;87m 87 [0m
[38;5;88m 88 [38;5;89m 89 [38;5;90m 90 [38;5;91m 91 [38;5;92m 92 [38;5;93m 93 [38;5;94m 94 [38;5;95m 95 [38;5;96m 96 [38;5;97m 97 [38;5;98m 98 [38;5;99m 99 [38;5;100m100 [38;5;101m101 [38;5;102m102 [38;5;103m103 [38;5;104m104 [38;5;105m105 [0m
[38;5;106m106 [38;5;107m107 [38;5;108m108 [38;5;109m109 [38;5;110m110 [38;5;111m111 [38;5;112m112 [38;5;113m113 [38;5;114m114 [38;5;115m115 [38;5;116m116 [38;5;117m117 [38;5;118m118 [38;5;119m119 [38;5;120m120 [38;5;121m121 [38;5;122m122 [38;5;123m123 [0m
[38;5;124m124 [38;5;125m125 [38;5;126m126 [38;5;127m127 [38;5;128m128 [38;5;129m129 [38;5;130m130 [38;5;131m131 [38;5;132m132 [38;5;133m133 [38;5;134m134 [38;5;135m135 [38;5;136m136 [38;5;137m137 [38;5;138m138 [38;5;139m139 [38;5;140m140 [38;5;141m141 [0m
[38;5;142m142 [38;5;143m143 [38;5;144m144 [38;5;145m145 [38;5;146m146 [38;5;147m147 [38;5;148m148 [38;5;149m149 [38;5;150m150 [38;5;151m151 [38;5;152m152 [38;5;153m153 [38;5;154m154 [38;5;155m155 [38;5;156m156 [38;5;157m157 [38;5;158m158 [38;5;159m159 [0m
[38;5;160m160 [38;5;161m161 [38;5;162m162 [38;5;163m163 [38;5;164m164 [38;5;165m165 [38;5;166m166 [38;5;167m167 [38;5;168m168 [38;5;169m169 [38;5;170m170 [38;5;171m171 [38;5;172m172 [38;5;173m173 [38;5;174m174 [38;5;175m175 [38;5;176m176 [38;5;177m177 [0m
[38;5;178m178 [38;5;179m179 [38;5;180m180 [38;5;181m181 [38;5;182m182 [38;5;183m183 [38;5;184m184 [38;5;185m185 [38;5;186m186 [38;5;187m187 [38;5;188m188 [38;5;189m189 [38;5;190m190 [38;5;191m191 [38;5;192m192 [38;5;193m193 [38;5;194m194 [38;5;195m195 [0m
[38;5;196m196 [38;5;197m197 [38;5;198m198 [38;5;199m199 [38;5;200m200 [38;5;201m201 [38;5;202m202 [38;5;203m203 [38;5;204m204 [38;5;205m205 [38;5;206m206 [38;5;207m207 [38;5;208m208 [38;5;209m209 [38;5;210m210 [38;5;211m211 [38;5;212m212 [38;5;213m213 [0m
[38;5;214m214 [38;5;215m215 [38;5;216m216 [38;5;217m217 [38;5;218m218 [38;5;219m219 [38;5;220m220 [38;5;221m221 [38;5;222m222 [38;5;223m223 [38;5;224m224 [38;5;225m225 [38;5;226m226 [38;5;227m227 [38;5;228m228 [38;5;229m229 [38;5;230m230 [38;5;231m231 [0m
[38;5;232m232 [38;5;233m233 [38;5;234m234 [38;5;235m235 [38;5;236m236 [38;5;237m237 [38;5;238m238 [38;5;239m239 [38;5;240m240 [38;5;241m241 [38;5;242m242 [38;5;243m243 [38;5;244m244 [38;5;245m245 [38;5;246m246 [38;5;247m247 [38;5;248m248 [38;5;249m249 [0m
[38;5;250m250 [38;5;251m251 [38;5;252m252 [38;5;253m253 [38;5;254m254 [38;5;255m255
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
click_extra.sphinx
API¶
classDiagram ViewList <|-- PatchedViewList
Helpers and utilities for Sphinx rendering of CLI based on Click Extra.
Danger
This module is quite janky but does the job. Still, it would benefits from a total
clean rewrite. This would require a better understanding of Sphinx, Click and MyST
internals. And as a side effect will eliminate the dependency on
pallets_sphinx_themes
.
If you’re up to the task, you can try to refactor it. I’ll probably start by moving
the whole pallets_sphinx_themes.themes.click.domain
code here, merge it with
the local collection of monkey-patches below, then clean the whole code to make it
more readable and maintainable. And finally, address all the todo-list below.
Todo
Add support for plain MyST directives to remove the need of wrapping rST into an
{eval-rst}
block. Ideally, this would allow for the following simpler syntax in
MyST:
```{click-example}
from click_extra import echo, extra_command, option, style
@extra_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-run}
invoke(hello_world, args=["--help"])
```
Todo
Fix the need to have both .. click:example::
and .. click:run::
directives
in the same {eval-rst}
block in MyST. This is required to have both directives
shares states and context.
- class click_extra.sphinx.PatchedViewList(initlist=None, source=None, items=None, parent=None, parent_offset=None)[source]¶
Bases:
ViewList
Force the rendering of ANSI shell session.
Replaces the
.. sourcecode:: shell-session
code block produced by.. click:run::
directive with an ANSI Shell Session:.. code-block:: ansi-shell-session
... sourcecode:: shell-session
has been released in Pallets-Sphinx-Themes 2.1.0.
- click_extra.sphinx.setup(app)[source]¶
Register new directives, augmented with ANSI coloring.
- Return type:
- New directives:
.. click:example::
.. click:run::
Danger
This function activates lots of monkey-patches:
sphinx.highlighting.PygmentsBridge
is updated to set its default HTML formatter to an ANSI capable one for the whole Sphinx app.pallets_sphinx_themes.themes.click.domain.ViewList
is patched to force an ANSI lexer on the rST code block.pallets_sphinx_themes.themes.click.domain.ExampleRunner
is replaced withclick_extra.testing.ExtraCliRunner
to have full control of contextual color settings by the way of thecolor
parameter. It also produce unfiltered ANSI codes so that the otherPatchedViewList
monkey-patch can do its job and render colors in the HTML output.