Categorize help output in Python Click
Asked Answered
O

1

2

I am trying to figure out how to categorize commands in Click to resemble something close to the structure that kubectl uses in the way it separate commands out.

For instance, in a vanilla Click help output we have:

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

  A CLI tool

Options:
  -h, --help  Show this message and exit.

Commands:
  command1   This is command1
  command2   This is command2
  command3   This is command3
  command4   This is command4

Instead, what would be ideal for my usage is to have a separation to better categorize the command structure.

For instance:

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

  A CLI tool

Options:
  -h, --help  Show this message and exit.

Specific Commands for X:

  command1   This is command1
  command2   This is command2

Specific Commands for Y:

  command3   This is command3
  command4   This is command4

Global Commands:

  version    Shows version

I am using the latest Python and latest version of Click also for this.

I have tried looking into hooking into various Click classes to change this behaviour but have been unsuccessful in doing so. Closest I have gotten is being able to structure commands based on priority but I am not able to logically separate them out as in the example above.

Any help would be greatly appreciated.

Oceanid answered 21/5, 2020 at 11:39 Comment(0)
C
5

I've achieved this by creating my own click.Group:

class OrderedGroup(click.Group):
    def __init__(self, name=None, commands=None, **attrs):
        super(OrderedGroup, self).__init__(name, commands, **attrs)
        self.commands = commands or collections.OrderedDict()

    def list_commands(self, ctx):
        return self.commands

    def format_commands(self, ctx, formatter):
        super().get_usage(ctx)

        formatter.write_paragraph()
        with formatter.section("Specific Commands for X:"):
            formatter.write_text(
                f'{self.commands.get("command1").name}\t\t{self.commands.get("command1").get_short_help_str()}')
            formatter.write_text(
                f"{self.commands.get('command2').name}\t\t{self.commands.get('command2').get_short_help_str()}")

        with formatter.section("Specific Commands for Y:"):
            formatter.write_text(
                f'{self.commands.get("command3").name}\t\t{self.commands.get("command3").get_short_help_str()}')
            formatter.write_text(
                f'{self.commands.get("command4").name}\t\t{self.commands.get("command4").get_short_help_str()}')

        with formatter.section("Global Commands"):
            formatter.write_text(
                f'{self.commands.get("version").name}\t\t{self.commands.get("version").get_short_help_str()}')

And created the cli group as such:

@click.group(cls=OrderedGroup)
def cli():
    pass

Does this help?

Crinkumcrankum answered 22/5, 2020 at 13:0 Comment(2)
Thank you so much for replying! Is there any chance you could dive deeper into how it would be possible to lift this and shift it into a classes file so that it can just be imported? Or was there another way to use this? Ideally this would be just put into a classes file. However, I think, in my environment, what is choking is the self.commands part but I will see what suggestion you come back with.Oceanid
It should be as simple as having the OrderedGroup into a separate file, ordered_group.py, and just do from ordered_group import OrderedGroup. Why is the self.commands causing issues?Crinkumcrankum

© 2022 - 2024 — McMap. All rights reserved.