Skip to content

rich-click CLI tool

Overview

rich-click comes with a CLI tool that allows you to format the Click help output for any CLI that uses Click.

rich-click --help

To use, simply prefix rich-click to the command. Here are a few real world examples:

If the CLI is not installed as a script, you can also pass the location with: <module_name>:<click_command_name>.

For example, if you have a file located at path/to/my/cli.py, and the Click Command object is named main, then you can run: rich-click path.to.my.cli:main.

Warning

If you are experiencing any unexpected issues with the rich-click CLI, first make sure you are not calling your command on load of the module.

For example, the following could cause a strange No such option: --output error when attempting to run rich-click --output html my_cli:cli:

import rich_click as click

@click.command("my-cli")
@click.argument("x")
def cli(x):
    ...

cli()

To make it so rich-click --output html works on the above code, add a if __name__ == "__main__":

import rich_click as click

@click.command("my-cli")
@click.argument("x")
def cli(x):
    ...

if __name__ == "__main__":
    cli()

Render help text as HTML or SVG

You can also use rich-click --output=html [command] to render rich HTML for help text, or rich-click --output=svg [command] to generate an SVG.

This works for RichCommands as well as normal click Commands.

SVG example:

rich-click --output svg app:main --help | grep -Eo '.{1,120}'

HTML example:

rich-click --output html app:main --help | grep -Eo '.{1,120}'

SVG and HTML generated from docs/code_snippets/rich_click_cli/app.py

Notes on how the rich-click CLI works

Under the hood, the rich-click CLI is patching the click module, and replacing the Click decorators and click.Command, click.Group, etc. objects with their equivalent rich-click versions.

Sometimes, a subclassed click.Command will overwrite one of these methods:

  • click.Command.format_usage
  • click.Command.format_help_text
  • click.Command.format_options
  • click.MultiCommand.format_commands
  • click.Command.format_epilog

Patching Click internals can mess with method resolution order, since by the time the downstream library subclasses the click.Command, it will be a RichCommand, and the subclass's method will take precedence over the RichCommand's methods. The problem is that rich-click's methods can be incompatible or at least stylistically incongruous with the base Click help text rendering.

To solve this, rich-click checks whether a method comes from a "true" RichCommand subclass or if it just looks that way due to patching. If RichCommand is "properly" subclassed, the override is allowed. If the subclass is only a result of the patching operation, we ignore the aforementioned methods and use the rich-click implementation.

Long story short, the rich-click CLI is safe to subclassing when it is the user's intent to subclass a rich-click object. (This is so that you can use other nifty features of the CLI such as the --output option on your own rich-click CLIs) That said, custom, non-rich-click implementations are ignored.

Using patch() as an end user

The functionality that rich-click uses to patch Click internals is available for use by rich-click end users, and it occasionally comes in handy outside of the rich-click CLI.

In some situations, you might be registering a command from another Click CLI that does not use rich-click:

import rich_click as click
from some_library import another_cli

@click.group("my-cli")
def cli():
    pass

# `another_cli` will NOT have rich-click markup. :(
cli.add_command(another_cli)

In this situation, another_cli retains its original help text behavior. In order to make another_cli work with rich-click, you need to patch click before you import another_cli. You can patch Click with rich_click.patch.patch like this:

import rich_click as click
from rich_click.patch import patch

patch()

from some_library import another_cli  # noqa: E402

@click.group("my-cli")
def cli():
    pass

# `another_cli` will have rich-click markup. :)
cli.add_command(another_cli)