Sphinx extension that automatically extracts documentation from Click-based command-line applications and integrates it into Sphinx documentation systems
npx @tessl/cli install tessl/pypi-sphinx-click@6.0.0A Sphinx extension that automatically extracts documentation from Click-based command-line applications and integrates it into Sphinx documentation systems. It enables developers to document their CLI tools by automatically generating formatted help text, command descriptions, and option details directly from the Click application code.
pip install sphinx-clickimport sphinx_clickFor Sphinx extension setup:
# In your Sphinx conf.py
extensions = ['sphinx_click']Add sphinx-click to your Sphinx configuration file (conf.py):
extensions = ['sphinx_click']Use the .. click:: directive in your documentation:
.. click:: mypackage.cli:main
:prog: my-cli-tool
:nested: fullGiven a Click application like this:
# mypackage/cli.py
import click
@click.group()
def main():
"""A sample CLI application."""
pass
@main.command()
@click.option('--name', '-n', help='Name to greet')
@click.option('--count', default=1, help='Number of greetings')
def hello(name, count):
"""Say hello to someone."""
for _ in range(count):
click.echo(f'Hello {name}!')
@main.command()
def goodbye():
"""Say goodbye."""
click.echo('Goodbye!')Document it with:
.. click:: mypackage.cli:main
:prog: my-cli-tool
:nested: fullThis automatically generates formatted documentation showing the command structure, options, arguments, and help text.
Register the extension with Sphinx and configure its behavior.
def setup(app: Sphinx) -> Dict[str, Any]:
"""
Set up the sphinx-click extension.
Args:
app: The Sphinx application instance
Returns:
Dict containing extension metadata with keys:
- 'parallel_read_safe': True
- 'parallel_write_safe': True
"""The main Sphinx directive for documenting Click applications.
class ClickDirective(rst.Directive):
"""
A Sphinx directive for documenting Click commands.
Usage:
.. click:: module:parser
:prog: program-name
:nested: full|short|none
:commands: cmd1,cmd2,cmd3
"""
has_content = False
required_arguments = 1
option_spec = {
'prog': 'directives.unchanged_required',
'nested': 'nested', # Custom validation function
'commands': 'directives.unchanged',
'show-nested': 'directives.flag'
}
def _load_module(self, module_path: str) -> Union[click.Command, click.Group]:
"""Load the module and return the Click command/group."""
def _generate_nodes(
self,
name: str,
command: click.Command,
parent: Optional[click.Context],
nested: NestedT,
commands: Optional[List[str]] = None,
semantic_group: bool = False,
) -> List[nodes.section]:
"""Generate the relevant Sphinx nodes for documentation."""
def run(self) -> Sequence[nodes.section]:
"""Execute the directive and return documentation nodes."""Extension configuration options available in Sphinx conf.py.
# Configuration value for mocking imports during documentation build
sphinx_click_mock_imports: List[str]
"""
List of module names to mock during import.
Defaults to autodoc_mock_imports if not specified.
Example in conf.py:
sphinx_click_mock_imports = ['some_module', 'another_module']
"""Custom Sphinx events emitted during documentation generation for customization.
# Event: sphinx-click-process-description
def on_process_description(ctx: click.Context, lines: List[str]) -> None:
"""
Emitted when processing command descriptions.
Args:
ctx: Click context for the command
lines: List of description lines (modifiable)
"""
# Event: sphinx-click-process-usage
def on_process_usage(ctx: click.Context, lines: List[str]) -> None:
"""
Emitted when processing usage information.
Args:
ctx: Click context for the command
lines: List of usage lines (modifiable)
"""
# Event: sphinx-click-process-options
def on_process_options(ctx: click.Context, lines: List[str]) -> None:
"""
Emitted when processing command options.
Args:
ctx: Click context for the command
lines: List of option lines (modifiable)
"""
# Event: sphinx-click-process-arguments
def on_process_arguments(ctx: click.Context, lines: List[str]) -> None:
"""
Emitted when processing command arguments.
Args:
ctx: Click context for the command
lines: List of argument lines (modifiable)
"""
# Event: sphinx-click-process-envars
def on_process_envvars(ctx: click.Context, lines: List[str]) -> None:
"""
Emitted when processing environment variables.
Args:
ctx: Click context for the command
lines: List of environment variable lines (modifiable)
"""
# Event: sphinx-click-process-epilog
def on_process_epilog(ctx: click.Context, lines: List[str]) -> None:
"""
Emitted when processing command epilog text.
Args:
ctx: Click context for the command
lines: List of epilog lines (modifiable)
"""# :prog: option (required)
prog: str
"""
The program name to display in usage examples.
Example:
.. click:: mypackage.cli:main
:prog: my-tool
"""def nested(argument: Optional[str]) -> NestedT:
"""
Validate nested directive option values.
Args:
argument: The option value from the directive
Returns:
Validated nested option value
Raises:
ValueError: If argument is not a valid nested value
"""# :nested: option
nested: Literal['full', 'short', 'none', None]
"""
Control the level of nested command documentation.
Values:
- 'full': Show complete documentation for all subcommands
- 'short': Show only command names and short descriptions
- 'none': Don't show subcommands at all
- None: Default behavior (same as 'short')
Example:
.. click:: mypackage.cli:main
:prog: my-tool
:nested: full
"""
# :commands: option
commands: str
"""
Comma-separated list of specific commands to document.
If not specified, all commands are documented.
Example:
.. click:: mypackage.cli:main
:prog: my-tool
:commands: hello,goodbye
"""
# :show-nested: option (deprecated)
show_nested: bool
"""
Deprecated: Use :nested: full instead.
When present, shows full nested command documentation.
Example (deprecated):
.. click:: mypackage.cli:main
:prog: my-tool
:show-nested:
"""Helper functions for formatting and processing Click command data.
def _get_usage(ctx: click.Context) -> str:
"""
Alternative, non-prefixed version of 'get_usage'.
Args:
ctx: Click context for the command
Returns:
Usage string without command name prefix
"""
def _get_help_record(ctx: click.Context, opt: click.core.Option) -> Tuple[str, str]:
"""
Re-implementation of click.Option.get_help_record compatible with Sphinx.
Formats option arguments using angle brackets instead of uppercase,
and uses comma-separated opts instead of slashes for Sphinx compatibility.
Args:
ctx: Click context for the command
opt: Click option to format
Returns:
Tuple of (option_signature, help_text)
"""
def _format_help(help_string: str) -> Generator[str, None, None]:
"""
Format help string by cleaning ANSI sequences and processing special markers.
Args:
help_string: Raw help text from Click command
Yields:
Formatted help lines
"""
def _indent(text: str, level: int = 1) -> str:
"""
Indent text by specified number of levels (4 spaces per level).
Args:
text: Text to indent
level: Number of indentation levels
Returns:
Indented text
"""
def _filter_commands(
ctx: click.Context,
commands: Optional[List[str]] = None,
) -> List[click.Command]:
"""
Return filtered list of commands from a Click group.
Args:
ctx: Click context for the command group
commands: Optional list of specific command names to include
Returns:
List of Click commands, sorted by name
"""
def _get_lazyload_commands(ctx: click.Context) -> Dict[str, click.Command]:
"""
Get lazy-loaded commands from a Click multi-command.
Args:
ctx: Click context for the multi-command
Returns:
Dictionary mapping command names to command objects
"""
def _process_lines(event_name: str) -> Callable[[_T_Formatter], _T_Formatter]:
"""
Decorator that emits Sphinx events during line processing.
Args:
event_name: Name of the event to emit
Returns:
Decorator function for formatter functions
"""Internal functions that generate reStructuredText for different parts of Click commands.
def _format_description(ctx: click.Context) -> Generator[str, None, None]:
"""
Format the description for a given Click Command.
Parses help text as reStructuredText, allowing rich information
in help messages.
Args:
ctx: Click context for the command
Yields:
Formatted description lines
"""
def _format_usage(ctx: click.Context) -> Generator[str, None, None]:
"""
Format the usage for a Click Command.
Args:
ctx: Click context for the command
Yields:
Formatted usage lines as code block
"""
def _format_options(ctx: click.Context) -> Generator[str, None, None]:
"""
Format all Click Options for a Click Command.
Args:
ctx: Click context for the command
Yields:
Formatted option documentation lines
"""
def _format_arguments(ctx: click.Context) -> Generator[str, None, None]:
"""
Format all Click Arguments for a Click Command.
Args:
ctx: Click context for the command
Yields:
Formatted argument documentation lines
"""
def _format_envvars(ctx: click.Context) -> Generator[str, None, None]:
"""
Format all environment variables for a Click Command.
Args:
ctx: Click context for the command
Yields:
Formatted environment variable documentation lines
"""
def _format_epilog(ctx: click.Context) -> Generator[str, None, None]:
"""
Format the epilog for a given Click Command.
Parses epilog text as reStructuredText.
Args:
ctx: Click context for the command
Yields:
Formatted epilog lines
"""
def _format_command(
ctx: click.Context,
nested: NestedT,
commands: Optional[List[str]] = None,
) -> Generator[str, None, None]:
"""
Format the complete output of a Click Command.
Args:
ctx: Click context for the command
nested: The granularity of subcommand details
commands: Optional list of specific commands to document
Yields:
Complete formatted command documentation lines
"""
def _format_option(
ctx: click.Context, opt: click.core.Option
) -> Generator[str, None, None]:
"""
Format the output for a single Click Option.
Args:
ctx: Click context for the command
opt: Click option to format
Yields:
Formatted option lines
"""
def _format_argument(arg: click.Argument) -> Generator[str, None, None]:
"""
Format the output of a Click Argument.
Args:
arg: Click argument to format
Yields:
Formatted argument lines
"""
def _format_envvar(
param: Union[click.core.Option, click.Argument]
) -> Generator[str, None, None]:
"""
Format the environment variables of a Click Option or Argument.
Args:
param: Click parameter with environment variable
Yields:
Formatted environment variable lines
"""
def _format_subcommand(command: click.Command) -> Generator[str, None, None]:
"""
Format a sub-command of a Click Command or Group.
Args:
command: Click subcommand to format
Yields:
Formatted subcommand lines
"""from typing import Literal, Sequence, Dict, Any, List, Optional, Union, Callable, Generator, Pattern, Tuple
from sphinx.application import Sphinx
from docutils import nodes
from docutils.parsers.rst import Directive
import click
import logging
# Type aliases used in the API
NestedT = Literal['full', 'short', 'none', None]
_T_Formatter = Callable[[click.Context], Generator[str, None, None]]
# Constants for nested option values
NESTED_FULL: str = 'full'
NESTED_SHORT: str = 'short'
NESTED_NONE: str = 'none'
# Pattern for removing ANSI escape sequences
ANSI_ESC_SEQ_RE: Pattern[str]
# Logger instance
LOG: logging.LoggerThe extension handles several common error conditions:
# Module import failures are caught and reported with helpful messages
# Example error messages:
# - "Failed to import 'parser' from 'mymodule'. The following exception was raised: ..."
# - "Module 'mymodule' has no attribute 'parser'"
# - "The module appeared to call sys.exit()"# Validates that imported objects are Click commands or groups
# Example error message:
# - "'<type>' of type 'str' is not click.Command or click.Group"# Validates directive options and provides clear error messages
# Example error messages:
# - ":prog: must be specified"
# - "'invalid' is not a valid value for ':nested:'; allowed values: full, short, none, None"
# - "':nested:' and ':show-nested:' are mutually exclusive"Register event handlers to customize the generated documentation:
# In your Sphinx conf.py
def customize_description(ctx, lines):
"""Modify command descriptions during processing."""
if ctx.command.name == 'special-command':
lines.insert(0, '**This is a special command!**')
lines.append('')
def setup(app):
app.connect('sphinx-click-process-description', customize_description)Handle missing dependencies during documentation builds:
# In your Sphinx conf.py
sphinx_click_mock_imports = [
'expensive_dependency',
'optional_module',
'development_only_package'
]Document multi-level command groups:
.. click:: mypackage.cli:main
:prog: my-complex-tool
:nested: full
.. click:: mypackage.admin:admin_cli
:prog: my-complex-tool admin
:nested: short
:commands: user,permissions