~brenns10/subc

919e84653828d22d91aefda1961f7664e970deb8 — Stephen Brennan 1 year, 6 months ago 7c9d321
Hide aliases from help output, or completion
1 files changed, 50 insertions(+), 1 deletions(-)

M subc/__init__.py
M subc/__init__.py => subc/__init__.py +50 -1
@@ 51,6 51,55 @@ def _unique_prefixes(strings: t.Iterable[str]) -> t.Dict[str, t.List[str]]:
    }


class _SneakyDict(collections.UserDict):
    """
    A dictionary which can have "hidden" keys that only show up if you know
    about them. The keys are just aliases to other keys. They show up with
    "getitem" and "contains" operations, but not in list / len operations.
    """

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._aliases = {}

    def __getitem__(self, key):
        key = self._aliases.get(key, key)
        return super().__getitem__(key)

    def __contains__(self, key):
        key = self._aliases.get(key, key)
        return super().__contains__(key)

    def add_aliases(self, alias_map: t.Dict[str, t.List[str]]):
        alias_to_name = {a: n for n, l in alias_map.items() for a in l}
        self._aliases.update(alias_to_name)


def _wrap_subparser_aliases(
        option: argparse._SubParsersAction,
        alias_map: t.Dict[str, t.List[str]]
) -> None:
    """
    Unfortunately, this mucks around with an internal implementation of
    argparse. However, the API seems pretty stable, and I hope to catch any
    compatibility issues with testing on each new version.

    The "choices" and "_name_parser_map" fields are used to determine which
    subcommands are allowed, and also to list out all of the subcommands for the
    help output (or even to generate completions with something like
    argcomplete).

    For the purposes of lookup (or membership testing), we want the aliases to
    be reflected in these variables. But for the purposes of listing, the
    aliases should be hidden. Thus, use a the _SneakyDict from above to hide the
    aliases.
    """
    new_choices = _SneakyDict(option.choices)
    new_choices.add_aliases(alias_map)
    option.choices = new_choices  # type: ignore
    option._name_parser_map = option.choices


class Command(ABC):
    """
    A simple class for implementing sub-commands in your command line


@@ 147,13 196,13 @@ class Command(ABC):
        for cmd in to_add:
            cmd_parser = subparsers.add_parser(
                cmd.name, description=cmd.description,
                aliases=aliases[cmd.name],
            )
            cmd.add_args(cmd_parser)
            cmd_parser.set_defaults(func=cmd.base_run)
            if cmd.name == default:
                parser.set_defaults(func=cmd.base_run)
                default_set = True
        _wrap_subparser_aliases(subparsers, aliases)

        if not default_set:
            def default_func(*args, **kwargs):