python-cmd2 / cmd2

cmd2 - quickly build feature-rich and user-friendly interactive command line applications in Python
https://cmd2.readthedocs.io/en/stable/
MIT License
593 stars 112 forks source link

Help text formatting question #1306

Open xuoguoto opened 1 week ago

xuoguoto commented 1 week ago

Hello all,

I am looking for some help in formatting the help text automatically generated by the cmd2. I have a program which produces a help message as follows:

(Cmd) help foo
Usage: foo [-h] {ro-community, ip-address} ...

Commands for configuration

optional arguments:
  -h, --help            show this help message and exit

List of sub commands:
  {ro-community, ip-address}
    ro-community        Remove ro-community string
    ip-address          Remove ip-address

Is it possible to format the help as follows:

(Cmd) help foo
Usage: foo [-h] (ro-community | ip-address) ...

Commands for configuration

optional arguments:
  -h, --help            show this help message and exit

List of sub commands:
    ro-community        Remove ro-community string
    ip-address          Remove ip-address

Or even:

(Cmd) help foo
Usage: foo [-h] COMMANDS OPTIONS

Commands for configuration

optional arguments:
  -h, --help            show this help message and exit

List of sub commands:
    ro-community        Remove ro-community string
    ip-address          Remove ip-address

An ideal scenario would be displaying all the help in a single screen some thing like the following

(Cmd) help foo
Usage: foo [-h] COMMANDS OPTIONS

Commands for configuration

optional arguments:
  -h, --help            show this help message and exit

List of sub commands:
    ro-community        [-h] <community-name>
    ip-address          [-h] <ip-address>

Also for this command help:

(Cmd) help foo ip-address 
Usage: foo ip-address [-h] ip-address

positional arguments:
  ip-address  ip-address to remove

optional arguments:
  -h, --help  show this help message and exit

Is it possible to format this as:

(Cmd) help foo ip-address 
Usage: foo ip-address [-h] <ip-address>

positional arguments:
  ip-address  ip-address to remove

optional arguments:
  -h, --help  show this help message and exit

The sample program I am testing with is as follows:

import cmd2
from cmd2 import (
    CommandSet,
    Cmd2ArgumentParser,
    with_argparser,
    with_default_category
)

class BasicApp(cmd2.Cmd):
    def __init__(self):
        super().__init__()

    # Commands                                                                                                                                  
    listen_ip_parser = Cmd2ArgumentParser(
        description="Set listen ip address", add_help=False
    )
    listen_ip_parser.add_argument("ip", help="ip address")
    @with_argparser(listen_ip_parser)
    def do_listen_ip(self, args):
        """Exit from harware interface."""
        self._cmd.cmd_logger.info(f"listen-ip {args.ip}")

    # foo command and its sub commands                                                                                                          
    remove_parser = Cmd2ArgumentParser()
    remove_subparsers = remove_parser.add_subparsers(title="List of sub commands")

    parser_ro_community = remove_subparsers.add_parser("ro-community", help="Remove ro-community string")
    parser_ro_community.add_argument("community-name",help="ro-community name to remove")

    def remove_ro_community(self, args):
        """Remove ro-community user"""
        self._cmd.cmd_logger.info(f"foo ro-community {args.get('community-name')}")

    parser_ro_community.set_defaults(func=remove_ro_community)

    parser_ip_address = remove_subparsers.add_parser("ip-address", help="Remove ip-address")
    parser_ip_address.add_argument("ip-address",help="ip-address to remove")

    def remove_ip_address(self, args):
        """Remove ip-address user"""
        self._cmd.cmd_logger.info(f"foo ip-address {args.get('ip-address')}")

    parser_ip_address.set_defaults(func=remove_ip_address)

    @with_argparser(remove_parser)
    def do_foo(self,args):
        """Commands for configuration"""
        func = getattr(args, 'func', None)
        if func is not None:
            # Call whatever subcommand function was selected                                                                                    
            return func(self, args)
        else:
            # Foo subcommand was provided, so call help                                                                                         
            return self._cmd.do_help('foo')

if __name__ == '__main__':
    app = BasicApp()
    app.cmdloop()
kotfu commented 1 week ago

https://cmd2.readthedocs.io/en/stable/features/argument_processing.html#help-messages

kotfu commented 1 week ago

Also, cmd2 doesn't handle help for sub-parsers very well. In this context when I say sub-parsers I mean commands with sub-commands like you have shown above, ie "foo ip-address" and "foo community" where "ip-address" and "community" are sub-commands. I had the same problem in an app I build using cmd2, I wanted a "theme" command and sub-commands for "edit", "delete", "list", "clone", etc.

To get the behavior I wanted, I had to over-ride the entire cmd2 help system. The good news is that it wasn't very hard. Here's a link to some code so you can see how I did it.

https://github.com/tomcatmanager/tomcatmanager/blob/main/src/tomcatmanager/interactive_tomcat_manager.py#L666C4-L681C53

And here's the output it generates:

tomcat-manager> help theme
usage: theme [-h] action ...

manage themes

positional arguments:
  action
    list      list all themes
    clone     clone a theme from the gallery or from one of the built-in themes
    edit      edit a user theme
    create    create a new user theme
    delete    delete a user theme
    dir       show the theme directory

options:
  -h, --help  show this help message and exit

tomcat-manager> help theme clone
usage: theme clone [-h] name [new_name]

clone a theme from the gallery or from one of the built-in themes

positional arguments:
  name        name of the gallery or built-in theme to clone to user theme directory
  new_name    new name of the theme

options:
  -h, --help  show this help message and exit

tomcat-manager>
kmvanbrunt commented 1 week ago

Question 1

How to remove {ro-community, ip-address} from the following help output.

(Cmd) help foo
Usage: foo [-h] {ro-community, ip-address} ...

Commands for configuration

optional arguments:
  -h, --help            show this help message and exit

List of sub commands:
  {ro-community, ip-address}
    ro-community        Remove ro-community string
    ip-address          Remove ip-address

The braced-list of subcommands is the default value for metavar, which is set when calling add_subparsers(). You can set it to any string you want. If you set metavar to a blank string it will just print a blank line. cmd2 commands like alias set metavar to SUBCOMMAND. It's really up to you what you want it to be. If you don't want the metavar line to print at all, you'd have to write custom argparse help generation code.

remove_subparsers = remove_parser.add_subparsers(title="List of sub commands", metavar="YOUR STRING HERE")

Question 2

How to surround required arguments with < and > characters.

Usage: foo ip-address [-h] <ip-address>

Argparse by default surrounds optional arguments with braces, e.g. [optional_arg], and does not surround required arguments with any characters. To achieve this, you'd have to override some argparse functions.

Kotfu's Custom Help Code

@kotfu Perhaps I'm misunderstanding your change, but did you write that custom help code a while ago? I don't think it's needed anymore. cmd2 prints the help text for argparse subcommands.

For example:

(Cmd) help alias 
Usage: alias [-h] SUBCOMMAND ...

Manage aliases

An alias is a command that enables replacement of a word by another string.

optional arguments:
  -h, --help  show this help message and exit

subcommands:
  SUBCOMMAND
    create    create or overwrite an alias
    delete    delete aliases
    list      list aliases

See also:
  macro

(Cmd) help alias list 
Usage: alias list [-h] [names [...]]

List specified aliases in a reusable form that can be saved to a startup
script to preserve aliases across sessions

Without arguments, all aliases will be listed.

positional arguments:
  names       alias(es) to list

optional arguments:
  -h, --help  show this help message and exit
xuoguoto commented 1 week ago

Thanks for the insightful answers, let me check and get back!

kotfu commented 1 day ago

@kmvanbrunt Yes, I have had custom help code for quite a while. While cmd2 now can handle the subparsers help, I also have code to apply a theme to help text, and some other minor tweaks: Screenshot 2024-07-01 iTerm2-000124@2x