使用 Click 命令别名的常规用法输出

Regular usage output with Click command aliases

我正在为我的自定义组(来自 here)使用此代码段以允许前缀。

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)))

然而,用法输出变得非常愚蠢:它显示命令的前缀而不是完整显示它们:

Usage: test_core a c [OPTIONS]

我想看看

Usage: test_core add combined [OPTIONS]

即使我打电话给 test_core a c -h.

我已经调查过了,但似乎没有明显的解决方案。格式化程序逻辑不知道它们的原始名称。也许 MultiCommand.resolve_command 可以被覆盖以处理 MultiCommand/Group.get_command 的覆盖版本,returns 也是原始命令名称。但这可能会破坏一些东西,也许有更简单的方法。

完整代码:

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')

您需要跟踪使用的别名。

别名保存在全局变量中,因为 click 使用了很多上下文实例。

并且您需要实现自己的 HelpFormatter。这涵盖了帮助结构的所有用途。

write_usage 中用完整的命令名称替换别名。跟踪填充的别名以涵盖 test_core a a -h 的情况作为 test_core add auto -h 的命令。如果在 prog 中找不到别名,请不要尝试使用下一个别名(while 而不是 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')

终端输出

$ 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.