首页 > 解决方案 > How can command list display be categorised within a Click chained group?

问题描述

I'm starting a CLI pipe-type application project which will eventually have a rather large collection of commands (which will be further extensible with plug-in). As a result, I would like to categorise them in the --help text:

Here is how it looks now:

Usage: my_pipe [OPTIONS] COMMAND1 [ARGS]... [COMMAND2 [ARGS]...]...

Options:
  --help         Show this message and exit.

Commands:
  another_filter     help about that filter
  another_generator  help about that generator
  another_sink       help about that sink
  some_filter        help about this filter
  some_generator     help about this generator
  some_sink          help about this sink

This is more or less how I would like it to look:

Usage: my_pipe [OPTIONS] COMMAND1 [ARGS]... [COMMAND2 [ARGS]...]...

Options:
  --help         Show this message and exit.

Commands:

  Generators:
     some_generator     help about this generator
     another_generator  help about that generator

  Filters:
     some_filter        help about this filter
     another_filter     help about that filter

  Sinks:
     some_sink          help about this sink
     another_sink       help about that sink

How can this be achieved? Note that apart from the look of --help, I'm happy with the flat logical command organisation. Also, sub-groups are not an option as they are not allowed inside a chain=True group.

标签: pythoncommand-line-interfacepython-click

解决方案


如果您继承自click.Group您可以添加一些代码来对命令进行分组,然后在帮助中显示这些组。

自定义类

class GroupedGroup(click.Group):

    def command(self, *args, **kwargs):
        """Gather the command help groups"""
        help_group = kwargs.pop('group', None)
        decorator = super(GroupedGroup, self).command(*args, **kwargs)

        def wrapper(f):
            cmd = decorator(f)
            cmd.help_group = help_group
            return cmd

        return wrapper

    def format_commands(self, ctx, formatter):
        # Modified fom the base class method

        commands = []
        for subcommand in self.list_commands(ctx):
            cmd = self.get_command(ctx, subcommand)
            if not (cmd is None or cmd.hidden):
                commands.append((subcommand, cmd))

        if commands:
            longest = max(len(cmd[0]) for cmd in commands)
            # allow for 3 times the default spacing
            limit = formatter.width - 6 - longest

            groups = {}
            for subcommand, cmd in commands:
                help_str = cmd.get_short_help_str(limit)
                subcommand += ' ' * (longest - len(subcommand))
                groups.setdefault(
                    cmd.help_group, []).append((subcommand, help_str))

            with formatter.section('Commands'):
                for group_name, rows in groups.items():
                    with formatter.section(group_name):
                        formatter.write_dl(rows)

使用自定义类

要使用自定义类,请使用cls参数将类传递给click.group()装饰器。

@click.group(cls=GroupedGroup)
def cli():
    """My awesome cli"""

然后为每个命令标记要包含的命令的帮助组,如下所示:

@cli.command(group='A Help Group')
def command():
    """This is a command"""

这是如何运作的?

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

在这种情况下,我们覆盖click.Group.command()装饰器以收集每个命令所需的帮助组。然后我们在构建帮助时覆盖click.Group.format_commands()使用这些组的方法。

测试代码

import click

@click.group(cls=GroupedGroup)
def cli():
    """My awesome cli"""

@cli.command(group='Generators')
def some_generator():
    """This is Some Generator"""

@cli.command(group='Generators')
def another_generator():
    """This is Another Generator"""

@cli.command(group='Filters')
def some_filter():
    """This is Some Filter"""

@cli.command(group='Filters')
def another_filter():
    """This is Another Filter"""

cli()

结果

Usage: test.py [OPTIONS] COMMAND [ARGS]...

  My awesome cli

Options:
  --help  Show this message and exit.

Commands:

  Filters:
    another-filter     This is Another Filter
    some-filter        This is Some Filter

  Generators:
    another-generator  This is Another Generator
    some-generator     This is Some Generator

推荐阅读