Skip to content

jmcore.cli_help

jmcore.cli_help

Alphabetically sorted --help output for Typer-based CLIs.

Typer preserves the registration order of subcommands and the function signature order of options, which makes long help screens hard to scan. This module provides drop-in replacements that sort:

  • Subcommands alphabetically in group help.
  • Options alphabetically (by their first long name) in command help.
  • Positional arguments keep their declared order, since it is meaningful.

Usage::

from jmcore.cli_help import SortedTyper

app = SortedTyper(name="jm-example", no_args_is_help=True)

Implementation note: typer >= 0.16 vendors click as typer._click, so the parameters seen at help-rendering time are not instances of the installed click package's classes. Classification therefore relies on the stable Parameter.param_type_name attribute ("option" vs "argument"), which both real and vendored click provide.

Note: unlike :mod:jmcore.cli_common, this module imports typer at module level. It must only be imported by components that depend on typer (maker, taker, tumbler, jmwallet, jmwalletd); jmcore itself does not declare a typer dependency.

Attributes

__all__ = ['SortedHelpCommand', 'SortedHelpGroup', 'SortedTyper', 'find_unsorted_help', 'sort_params_for_help'] module-attribute

Classes

SortedHelpCommand

Bases: TyperCommand

Typer command that lists its options alphabetically in --help.

Source code in jmcore/src/jmcore/cli_help.py
74
75
76
77
78
class SortedHelpCommand(TyperCommand):
    """Typer command that lists its options alphabetically in ``--help``."""

    def get_params(self, ctx: Any) -> list[Any]:
        return sort_params_for_help(super().get_params(ctx))
Methods:
get_params(ctx: Any) -> list[Any]
Source code in jmcore/src/jmcore/cli_help.py
77
78
def get_params(self, ctx: Any) -> list[Any]:
    return sort_params_for_help(super().get_params(ctx))

SortedHelpGroup

Bases: TyperGroup

Typer group that sorts subcommands and its own options in --help.

Source code in jmcore/src/jmcore/cli_help.py
81
82
83
84
85
86
87
88
class SortedHelpGroup(TyperGroup):
    """Typer group that sorts subcommands and its own options in ``--help``."""

    def get_params(self, ctx: Any) -> list[Any]:
        return sort_params_for_help(super().get_params(ctx))

    def list_commands(self, ctx: Any) -> list[str]:
        return sorted(self.commands)
Methods:
get_params(ctx: Any) -> list[Any]
Source code in jmcore/src/jmcore/cli_help.py
84
85
def get_params(self, ctx: Any) -> list[Any]:
    return sort_params_for_help(super().get_params(ctx))
list_commands(ctx: Any) -> list[str]
Source code in jmcore/src/jmcore/cli_help.py
87
88
def list_commands(self, ctx: Any) -> list[str]:
    return sorted(self.commands)

SortedTyper

Bases: Typer

:class:typer.Typer with alphabetically sorted help output.

Defaults the group class to :class:SortedHelpGroup and every registered command to :class:SortedHelpCommand. Sub-apps attached with add_typer should themselves be SortedTyper instances.

Source code in jmcore/src/jmcore/cli_help.py
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
class SortedTyper(typer.Typer):
    """:class:`typer.Typer` with alphabetically sorted help output.

    Defaults the group class to :class:`SortedHelpGroup` and every registered
    command to :class:`SortedHelpCommand`. Sub-apps attached with
    ``add_typer`` should themselves be ``SortedTyper`` instances.
    """

    def __init__(self, *args: Any, **kwargs: Any) -> None:
        kwargs.setdefault("cls", SortedHelpGroup)
        super().__init__(*args, **kwargs)

    def command(self, *args: Any, **kwargs: Any) -> Callable[[Any], Any]:
        kwargs.setdefault("cls", SortedHelpCommand)
        return super().command(*args, **kwargs)
Methods:
__init__(*args: Any, **kwargs: Any) -> None
Source code in jmcore/src/jmcore/cli_help.py
 99
100
101
def __init__(self, *args: Any, **kwargs: Any) -> None:
    kwargs.setdefault("cls", SortedHelpGroup)
    super().__init__(*args, **kwargs)
command(*args: Any, **kwargs: Any) -> Callable[[Any], Any]
Source code in jmcore/src/jmcore/cli_help.py
103
104
105
def command(self, *args: Any, **kwargs: Any) -> Callable[[Any], Any]:
    kwargs.setdefault("cls", SortedHelpCommand)
    return super().command(*args, **kwargs)

Functions:

find_unsorted_help(app: typer.Typer) -> list[str]

Return help-order violations for app (empty list means sorted).

Walks the resolved click command tree and reports every group whose subcommands are not alphabetical and every command whose options are not alphabetical. Intended for tests guarding the sorted-help behavior.

Source code in jmcore/src/jmcore/cli_help.py
108
109
110
111
112
113
114
115
116
117
118
def find_unsorted_help(app: typer.Typer) -> list[str]:
    """Return help-order violations for ``app`` (empty list means sorted).

    Walks the resolved click command tree and reports every group whose
    subcommands are not alphabetical and every command whose options are not
    alphabetical. Intended for tests guarding the sorted-help behavior.
    """
    violations: list[str] = []
    root = typer.main.get_command(app)
    _check_command(root, root.name or "<root>", violations)
    return violations

sort_params_for_help(params: list[Any]) -> list[Any]

Return params with arguments first (declared order), then sorted options.

Positional arguments keep their declaration order because it defines the call syntax; options are listed alphabetically. Parsing is unaffected: click matches options by name and the relative argument order is kept.

Source code in jmcore/src/jmcore/cli_help.py
62
63
64
65
66
67
68
69
70
71
def sort_params_for_help(params: list[Any]) -> list[Any]:
    """Return params with arguments first (declared order), then sorted options.

    Positional arguments keep their declaration order because it defines the
    call syntax; options are listed alphabetically. Parsing is unaffected:
    click matches options by name and the relative argument order is kept.
    """
    arguments = [p for p in params if not _is_option(p)]
    options = sorted((p for p in params if _is_option(p)), key=_option_sort_key)
    return [*arguments, *options]