Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
ee6932c
Add support for commands' rich_help_panel in generated docs
kinuax May 9, 2024
b802512
Fix type
kinuax May 10, 2024
80f27a3
Add tests with combination of panels
kinuax May 10, 2024
760cf3c
Avoid assignment expressions
kinuax May 17, 2024
99e1477
Mix default and custom commands in same app
kinuax May 17, 2024
87f7e2d
Merge branch 'master' into add-support-for-rich_help_panel-in-generat…
kinuax May 17, 2024
c0af3cd
Merge branch 'master' into add-support-for-rich_help_panel-in-generat…
kinuax Aug 16, 2024
bc937f2
Merge branch 'master' into add-support-for-rich_help_panel-in-generat…
svlandeg Aug 28, 2025
ac013b3
update expected output according to new functionality on master
svlandeg Aug 28, 2025
90d1b3d
remove duplicate part
svlandeg Aug 28, 2025
6386af3
fix output according to new behaviour on master
svlandeg Aug 28, 2025
b19abe9
Merge branch 'master' into add-support-for-rich_help_panel-in-generat…
svlandeg Sep 9, 2025
a0598b5
ensure robustness against rich imports
svlandeg Sep 9, 2025
7d3e71c
fix type annotations
svlandeg Sep 9, 2025
354fe9b
cleanup
svlandeg Sep 9, 2025
27445a8
Merge branch 'master' into add-support-for-rich_help_panel-in-generat…
kinuax Sep 22, 2025
59108e5
Fix name
kinuax Sep 22, 2025
9d691e6
Merge branch 'master' into add-support-for-rich_help_panel-in-generat…
kinuax Sep 30, 2025
b58ae12
Merge branch 'master' into add-support-for-rich_help_panel-in-generat…
svlandeg Nov 25, 2025
ed0164b
Merge branch 'master' into add-support-for-rich_help_panel-in-generat…
kinuax Dec 9, 2025
82b3853
Merge branch 'master' into add-support-for-rich_help_panel-in-generat…
svlandeg Jan 9, 2026
9b4e73c
🎨 Auto format
pre-commit-ci-lite[bot] Jan 9, 2026
a7007dd
fix separator
svlandeg Jan 9, 2026
9d53174
Merge branch 'add-support-for-rich_help_panel-in-generated-docs' of h…
svlandeg Jan 9, 2026
bed0785
fix import
svlandeg Jan 9, 2026
c06352a
don't show the headers if Rich is disabled
svlandeg Jan 9, 2026
d4c82b0
🎨 Auto format
pre-commit-ci-lite[bot] Jan 9, 2026
ad2211b
fix formatting
svlandeg Jan 9, 2026
011a918
Merge branch 'add-support-for-rich_help_panel-in-generated-docs' of h…
svlandeg Jan 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions tests/assets/cli/multi_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@ def hello(name: str = "World", age: int = typer.Option(0, help="The age of the u
typer.echo(f"Hello {name}")


@sub_app.command()
@sub_app.command(rich_help_panel="Greet")
def hi(user: str = typer.Argument("World", help="The name of the user to greet")):
"""
Say Hi
"""


@sub_app.command()
@sub_app.command(rich_help_panel="Farewell")
def bye():
"""
Say bye
Expand All @@ -32,7 +32,7 @@ def bye():
app.add_typer(sub_app, name="sub")


@app.command()
@app.command(rich_help_panel="")
def top():
"""
Top command
Expand Down
102 changes: 102 additions & 0 deletions tests/assets/cli/multiapp-docs-norich.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# `multiapp`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is basically the same file as what multiapp-docs.md was before, without the "Greet" and "Farewell" headers.


Demo App

**Usage**:

```console
$ multiapp [OPTIONS] COMMAND [ARGS]...
```

**Options**:

* `--install-completion`: Install completion for the current shell.
* `--show-completion`: Show completion for the current shell, to copy it or customize the installation.
* `--help`: Show this message and exit.

The end

**Commands**:

* `top`: Top command
* `sub`

## `multiapp top`

Top command

**Usage**:

```console
$ multiapp top [OPTIONS]
```

**Options**:

* `--help`: Show this message and exit.

## `multiapp sub`

**Usage**:

```console
$ multiapp sub [OPTIONS] COMMAND [ARGS]...
```

**Options**:

* `--help`: Show this message and exit.

**Commands**:

* `hello`: Say Hello
* `hi`: Say Hi
* `bye`: Say bye

### `multiapp sub hello`

Say Hello

**Usage**:

```console
$ multiapp sub hello [OPTIONS]
```

**Options**:

* `--name TEXT`: [default: World]
* `--age INTEGER`: The age of the user [default: 0]
* `--help`: Show this message and exit.

### `multiapp sub hi`

Say Hi

**Usage**:

```console
$ multiapp sub hi [OPTIONS] [USER]
```

**Arguments**:

* `[USER]`: The name of the user to greet [default: World]

**Options**:

* `--help`: Show this message and exit.

### `multiapp sub bye`

Say bye

**Usage**:

```console
$ multiapp sub bye [OPTIONS]
```

**Options**:

* `--help`: Show this message and exit.
6 changes: 6 additions & 0 deletions tests/assets/cli/multiapp-docs-title.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,13 @@ $ multiapp sub [OPTIONS] COMMAND [ARGS]...
**Commands**:

* `hello`: Say Hello

**Greet**:

* `hi`: Say Hi

**Farewell**:

* `bye`: Say bye

### `multiapp sub hello`
Expand Down
6 changes: 6 additions & 0 deletions tests/assets/cli/multiapp-docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,13 @@ $ multiapp sub [OPTIONS] COMMAND [ARGS]...
**Commands**:

* `hello`: Say Hello

**Greet**:

* `hi`: Say Hi

**Farewell**:

* `bye`: Say bye

### `multiapp sub hello`
Expand Down
6 changes: 4 additions & 2 deletions tests/test_cli/test_doc.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,9 @@ def test_doc_no_rich():
capture_output=True,
encoding="utf-8",
)
docs_path: Path = Path(__file__).parent.parent / "assets/cli/multiapp-docs.md"
docs_path: Path = (
Path(__file__).parent.parent / "assets/cli/multiapp-docs-norich.md"
)
docs = docs_path.read_text()
assert docs in result.stdout
assert "**Arguments**" in result.stdout
Expand Down Expand Up @@ -138,7 +140,7 @@ def test_doc_no_typer():
"run",
"-m",
"typer",
"tests/assets/cli/empty_script.py",
"tests.assets.cli.empty_script",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

drive-by edit that is not necessary for this PR 🤷

"utils",
"docs",
],
Expand Down
56 changes: 41 additions & 15 deletions typer/cli.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import importlib.util
import re
import sys
from collections import defaultdict
from itertools import chain
from pathlib import Path
from typing import Any, Optional

Expand Down Expand Up @@ -244,26 +246,50 @@ def get_docs_for_click(
if isinstance(obj, Group):
group = obj
commands = group.list_commands(ctx)
default_panel_name = "Commands"
if HAS_RICH:
from . import rich_utils

default_panel_name = rich_utils.COMMANDS_PANEL_TITLE
if commands:
docs += "**Commands**:\n\n"
panel_to_commands: dict[str, list[click.Command]] = defaultdict(list)
for command in commands:
command_obj = group.get_command(ctx, command)
assert command_obj
docs += f"* `{command_obj.name}`"
command_help = command_obj.get_short_help_str()
if command_help:
docs += f": {_parse_html(to_parse, command_help)}"
panel_name = default_panel_name
if HAS_RICH:
from . import rich_utils

panel_name = rich_utils.get_panel_name(
command_obj, default_panel_name
)
panel_to_commands[panel_name].append(command_obj)
if HAS_RICH:
# Ensure that the ungrouped commands show up first
default_command_objs = panel_to_commands.pop(default_panel_name, [])
if len(default_command_objs) > 0:
panel_to_commands = {
default_panel_name: default_command_objs,
**panel_to_commands,
}
for panel_name, command_objs in panel_to_commands.items():
docs += f"**{panel_name}**:\n\n"
for command_obj in command_objs:
docs += f"* `{command_obj.name}`"
command_help = command_obj.get_short_help_str()
if command_help:
docs += f": {_parse_html(to_parse, command_help)}"
docs += "\n"
docs += "\n"
docs += "\n"
for command in commands:
command_obj = group.get_command(ctx, command)
assert command_obj
use_prefix = ""
if command_name:
use_prefix += f"{command_name}"
docs += get_docs_for_click(
obj=command_obj, ctx=ctx, indent=indent + 1, call_prefix=use_prefix
)
for command_obj in chain.from_iterable(
command_objs for command_objs in panel_to_commands.values()
):
use_prefix = ""
if command_name:
use_prefix += f"{command_name}"
docs += get_docs_for_click(
obj=command_obj, ctx=ctx, indent=indent + 1, call_prefix=use_prefix
)
return docs


Expand Down
19 changes: 9 additions & 10 deletions typer/rich_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -574,14 +574,10 @@ def rich_format_help(
if getattr(param, "hidden", False):
continue
if isinstance(param, click.Argument):
panel_name = (
getattr(param, _RICH_HELP_PANEL_NAME, None) or ARGUMENTS_PANEL_TITLE
)
panel_name = get_panel_name(param, ARGUMENTS_PANEL_TITLE)
panel_to_arguments[panel_name].append(param)
elif isinstance(param, click.Option):
panel_name = (
getattr(param, _RICH_HELP_PANEL_NAME, None) or OPTIONS_PANEL_TITLE
)
panel_name = get_panel_name(param, OPTIONS_PANEL_TITLE)
panel_to_options[panel_name].append(param)
default_arguments = panel_to_arguments.get(ARGUMENTS_PANEL_TITLE, [])
_print_options_panel(
Expand Down Expand Up @@ -627,10 +623,7 @@ def rich_format_help(
for command_name in obj.list_commands(ctx):
command = obj.get_command(ctx, command_name)
if command and not command.hidden:
panel_name = (
getattr(command, _RICH_HELP_PANEL_NAME, None)
or COMMANDS_PANEL_TITLE
)
panel_name = get_panel_name(command, COMMANDS_PANEL_TITLE)
panel_to_commands[panel_name].append(command)

# Identify the longest command name in all panels
Expand Down Expand Up @@ -750,3 +743,9 @@ def get_traceback(
width=MAX_WIDTH,
)
return rich_tb


def get_panel_name(
obj: Union[click.Command, click.Argument, click.Option], default_name: str
) -> str:
return getattr(obj, _RICH_HELP_PANEL_NAME, None) or default_name