""" Extension for ``click`` to provide a group with a git-like *did-you-mean* feature. """ import difflib import typing import pipenv.vendor.click as click class DYMMixin: """ Mixin class for click MultiCommand inherited classes to provide git-like *did-you-mean* functionality when a certain command is not registered. """ def __init__(self, *args: typing.Any, **kwargs: typing.Any) -> None: self.max_suggestions = kwargs.pop("max_suggestions", 3) self.cutoff = kwargs.pop("cutoff", 0.5) super().__init__(*args, **kwargs) # type: ignore def resolve_command( self, ctx: click.Context, args: typing.List[str] ) -> typing.Tuple[ typing.Optional[str], typing.Optional[click.Command], typing.List[str] ]: """ Overrides clicks ``resolve_command`` method and appends *Did you mean ...* suggestions to the raised exception message. """ try: return super(DYMMixin, self).resolve_command(ctx, args) # type: ignore except click.exceptions.UsageError as error: error_msg = str(error) original_cmd_name = click.utils.make_str(args[0]) matches = difflib.get_close_matches( original_cmd_name, self.list_commands(ctx), # type: ignore self.max_suggestions, self.cutoff, ) if matches: fmt_matches = "\n ".join(matches) error_msg += "\n\n" error_msg += f"Did you mean one of these?\n {fmt_matches}" raise click.exceptions.UsageError(error_msg, error.ctx) class DYMGroup(DYMMixin, click.Group): """ click Group to provide git-like *did-you-mean* functionality when a certain command is not found in the group. """ class DYMCommandCollection(DYMMixin, click.CommandCollection): """ click CommandCollection to provide git-like *did-you-mean* functionality when a certain command is not found in the group. """