I'm using this snippet for my custom group (from here) to allow prefixes.
class AliasedGroup(click.Group):
def get_command(self, ctx, cmd_name):
rv = click.Group.get_command(self, ctx, cmd_name)
if rv is not None:
return rv
matches = [x for x in self.list_commands(ctx)
if x.startswith(cmd_name)]
if not matches:
return None
elif len(matches) == 1:
return click.Group.get_command(self, ctx, matches[0])
ctx.fail('Too many matches: %s' % ', '.join(sorted(matches)))
The usage output becomes really dumb however: it shows the prefixes of the commands instead of showing them fully:
Usage: test_core a c [OPTIONS]
I would like to see
Usage: test_core add combined [OPTIONS]
even when I call test_core a c -h
.
I've looked into it and it doesn't look like there is an obvious solution. Formatter logic doesn't know about their original names. Maybe MultiCommand.resolve_command
could be overridden to handle an overridden version of MultiCommand/Group.get_command
that returns the original command name as well. But that might break some things, maybe there's some easier way.
Full code:
import click
class AliasedGroup(click.Group):
def get_command(self, ctx, cmd_name):
rv = click.Group.get_command(self, ctx, cmd_name)
if rv is not None:
return rv
matches = [x for x in self.list_commands(ctx)
if x.startswith(cmd_name)]
if not matches:
return None
elif len(matches) == 1:
return click.Group.get_command(self, ctx, matches[0])
ctx.fail('Too many matches: %s' % ', '.join(sorted(matches)))
@click.group(cls=AliasedGroup, context_settings={'help_option_names': ['-h', '--help']})
def cli():
pass
@cli.group(cls=AliasedGroup)
def add():
pass
@add.command()
@click.option('--yarr')
def combined():
pass
cli(['a', 'c', '-h'], prog_name='test_core')
You need to keep track of the aliases used.
The aliases are kept in a global variable because click
uses a lot of context instances.
And you need to implement your own HelpFormatter. This covers all uses of the help construction.
In the write_usage
replace the aliases with the full command names. Keep track of aliases filled to cover the case of test_core a a -h
as a command for test_core add auto -h
. If an alias is not found in prog
don't try the next alias used (while
instead of for
).
import click
clickAliases = []
class AliasedGroup(click.Group):
def get_command(self, ctx, cmd_name):
rv = click.Group.get_command(self, ctx, cmd_name)
if rv is not None:
return rv
matches = [x for x in self.list_commands(ctx)
if x.startswith(cmd_name)]
if not matches:
return None
elif len(matches) == 1:
clickAliases.append((cmd_name, matches[0]))
return click.Group.get_command(self, ctx, matches[0])
ctx.fail('Too many matches: %s' % ', '.join(sorted(matches)))
class MyHelpFormatter(click.HelpFormatter):
def write_usage(self, prog, args="", prefix="Usage: "):
if clickAliases:
parts = prog.split()
partIdx = 0
for alias,cmd in clickAliases:
while partIdx < len(parts):
if parts[partIdx] == alias:
parts[partIdx] = cmd
partIdx += 1
break
partIdx += 1
prog = ' '.join(parts)
click.HelpFormatter.write_usage(self, prog, args, prefix)
def make_formatter(self):
return MyHelpFormatter(width=self.terminal_width, max_width=self.max_content_width)
click.Context.make_formatter = make_formatter
# version 8.x makes if easier with
# click.Context.formatter_class = MyHelpFormatter
@click.group(cls=AliasedGroup, context_settings={'help_option_names': ['-h', '--help']})
def cli():
pass
@cli.group(cls=AliasedGroup)
def add():
click.echo("add command")
@add.command()
@click.option('--yarr')
def combined(yarr):
click.echo(f"combined command: {yarr}")
# simulate command arguments - for debugging
# cli(['a', 'c', '-h'], prog_name='test_core')
# normal start
cli(prog_name='test_core')
Terminal output
$ python test_core.py a c -h
add command
Usage: test_core add combined [OPTIONS]
Options:
--yarr TEXT
-h, --help Show this message and exit.