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', 'version_callback', 'version_options'] module-attribute

Classes

SortedHelpCommand

Bases: TyperCommand

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

Source code in jmcore/src/jmcore/cli_help.py
 98
 99
100
101
102
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
101
102
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
105
106
107
108
109
110
111
112
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
108
109
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
111
112
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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
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
123
124
125
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
127
128
129
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
132
133
134
135
136
137
138
139
140
141
142
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
86
87
88
89
90
91
92
93
94
95
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]

version_callback(value: bool) -> None

Print the installed project version and exit before command execution.

Source code in jmcore/src/jmcore/cli_help.py
53
54
55
56
57
def version_callback(value: bool) -> None:
    """Print the installed project version and exit before command execution."""
    if value:
        typer.echo(f"JoinMarket NG {get_version()}")
        raise typer.Exit()

version_options(version: bool = typer.Option(False, '--version', callback=version_callback, is_eager=True, help='Show the installed JoinMarket NG version and exit.')) -> None

Source code in jmcore/src/jmcore/cli_help.py
61
62
63
64
65
66
67
68
69
70
def version_options(
    version: bool = typer.Option(
        False,
        "--version",
        callback=version_callback,
        is_eager=True,
        help="Show the installed JoinMarket NG version and exit.",
    ),
) -> None:
    pass