在 Python 中单击如何查看父级具有必需参数的子命令的 --help?

W.P*_*ill 6 python python-click

我的程序使用Click进行命令行处理。它有一个带有必需参数的主命令。此命令具有采用可选参数的子命令。不同的子命令采用不同的选项,但它们都需要来自其父命令的相同参数。我想让命令行看起来像这样:

python myprogram.py argument-value subcommand1 --option-1=value
Run Code Online (Sandbox Code Playgroud)

我可以像这样使用 Click 写这个

import click

@click.group()
@click.argument("argument")
@click.pass_context
def main(context, argument):
    """ARGUMENT is required for both subcommands"""
    context.obj = {"argument": argument}


@click.command()
@click.option("--option-1", help="option for subcommand 1")
@click.pass_context
def subcommand1(context, option_1):
    print("subcommand 1: %s %s" % (context.obj["argument"], option_1))


@click.command()
@click.option("--option-2", help="option for subcommand 2")
@click.pass_context
def subcommand2(context, option_2):
    print("subcommand 2: %s %s" % (context.obj["argument"], option_2))


main.add_command(subcommand1)
main.add_command(subcommand2)

if __name__ == "__main__":
    main()
Run Code Online (Sandbox Code Playgroud)

顶级帮助信息正是我想要的。

python myprogram.py --help
Usage: myprogram.py [OPTIONS] ARGUMENT COMMAND [ARGS]...

  ARGUMENT is required for both subcommands

Options:
  --help  Show this message and exit.

Commands:
  subcommand1
  subcommand2
Run Code Online (Sandbox Code Playgroud)

如果我传入所需的参数,我可以获得子命令的帮助。

python myprogram.py dummy-argument subcommand1 --help
Usage: myprogram.py subcommand1 [OPTIONS]

Options:
  --option-1 TEXT  option for subcommand 1
  --help           Show this message and exit.
Run Code Online (Sandbox Code Playgroud)

但是,我想获得子命令帮助,而无需用户传入虚拟参数。我希望能够运行python myprogram.py subcommand1 --help并看到与上面相同的输出,但我只是获得了顶层的帮助文本。

python myprogram.py subcommand1 --help
Usage: myprogram.py [OPTIONS] ARGUMENT COMMAND [ARGS]...

  ARGUMENT is required for both subcommands

Options:
  --help  Show this message and exit.

Commands:
  subcommand1
  subcommand2
Run Code Online (Sandbox Code Playgroud)

有没有办法获得我想要的行为?我意识到 Click 非常重视让它的每个命令都是独立的,但这似乎是一个常见的场景。

Ste*_*uch 3

您的要求存在固有的歧义,因为子命令名称可能与公共参数的有效值相同。

因此,需要某种消除歧义的方法。我在下面提出一种可能的解决方案。

当找到与子命令名称匹配的参数值时,建议的解决方案将搜索 是否存在--help。如果找到,则假定正在请求子命令的帮助,并将dummy-argument自动填充。

定制类:

import click

class PerCommandArgWantSubCmdHelp(click.Argument):

    def handle_parse_result(self, ctx, opts, args):
        # check to see if there is a --help on the command line
        if any(arg in ctx.help_option_names for arg in args):

            # if asking for help see if we are a subcommand name
            for arg in opts.values():
                if arg in ctx.command.commands:

                    # this matches a sub command name, and --help is
                    # present, let's assume the user wants help for the
                    # subcommand
                    args = [arg] + args

        return super(PerCommandArgWantSubCmdHelp, self).handle_parse_result(
            ctx, opts, args)
Run Code Online (Sandbox Code Playgroud)

使用自定义类:

要使用自定义类,请将cls参数传递给@click.argument()装饰器,如下所示:

@click.argument("argument", cls=PerCommandArgWantSubCmdHelp)
Run Code Online (Sandbox Code Playgroud)

这是如何运作的?

这是可行的,因为 click 是一个设计良好的 OO 框架。装饰@click.argument()器通常会实例化一个click.Argument对象,但允许使用 cls 参数覆盖此行为。因此,在我们自己的类中继承click.Argument并覆盖所需的方法是一件相对容易的事情。

在本例中,我们覆盖click.Argument.handle_parse_result()并查找子命令名称后跟 的模式--help。找到后,我们会修改参数列表以获取模式,单击需要以显示子命令帮助的方式解析此模式。

测试代码:

@click.group()
@click.argument("argument", cls=PerCommandArgWantSubCmdHelp)
@click.pass_context
def main(context, argument):
    """ARGUMENT is required for both subcommands"""
    context.obj = {"argument": argument}


@click.command()
@click.option("--option-1", help="option for subcommand 1")
@click.pass_context
def subcommand1(context, option_1):
    print("subcommand 1: %s %s" % (context.obj["argument"], option_1))


@click.command()
@click.option("--option-2", help="option for subcommand 2")
@click.pass_context
def subcommand2(context, option_2):
    print("subcommand 2: %s %s" % (context.obj["argument"], option_2))


main.add_command(subcommand1)
main.add_command(subcommand2)

if __name__ == "__main__":
    commands = (
        'subcommand1 --help',
        'subcommand2 --help',
        'dummy-argument subcommand1 --help',
    )

    for cmd in commands:
        try:
            print('-----------')
            print('> ' + cmd)
            main(cmd.split())
        except:
            pass
Run Code Online (Sandbox Code Playgroud)

检测结果:

-----------
> subcommand1 --help
Backend TkAgg is interactive backend. Turning interactive mode on.
Usage: test.py subcommand1 [OPTIONS]

Options:
  --option-1 TEXT  option for subcommand 1
  --help           Show this message and exit.
-----------
> subcommand2 --help
Usage: test.py subcommand2 [OPTIONS]

Options:
  --option-2 TEXT  option for subcommand 2
  --help           Show this message and exit.
-----------
> dummy-argument subcommand1 --help
Usage: test.py subcommand1 [OPTIONS]

Options:
  --option-1 TEXT  option for subcommand 1
  --help           Show this message and exit.                
Run Code Online (Sandbox Code Playgroud)