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

时间:2017-11-22 14:43:44

标签: python python-click

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

python myprogram.py argument-value subcommand1 --option-1=value

我可以使用点击这样写

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

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

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

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

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.

但是,我希望获得子命令帮助,而无需用户传入伪参数。我希望能够运行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

有没有办法获得我想要的行为?我意识到Click非常重视每个命令都是自包含的,但这似乎是一种常见的情况。

3 个答案:

答案 0 :(得分:2)

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

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

当找到与子命令名称匹配的参数值时,建议的解决方案将搜索--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)

使用自定义类:

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

@click.argument("argument", cls=PerCommandArgWantSubCmdHelp)

这是如何工作的?

这是有效的,因为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

测试结果:

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

答案 1 :(得分:0)

另一种可能的方法是定义一个自定义装饰器,该装饰器添加公用参数/选项,并在每个子命令上使用此装饰器。这样,在发出subcommand --help时,click不会要求输入参数/选项。

click's GitHub Issue 295中概述了一种可能的实现。示例代码显示了如何添加通用装饰器,该装饰器可以添加不同的但共享的选项。但是,它确实解决了OP的问题。

答案 2 :(得分:0)

我设法做到了这一点

def create(config):
if config is None:
    click.echo('ERROR: Configuration File not found!')
    main(datalake(create(['--help'])))