Does this win the prize? :)
Custom parameters
Rob Kennedy has a better customization.
In [158]: parser=argparse.ArgumentParser(usage='tool [command] [options]',
description= "Available commands:\n\n foo foo.\n bar bar.\n",
epilog= 'Use "tool help" to get full list of supported commands',
formatter_class=argparse.RawDescriptionHelpFormatter, add_help=False)
In [159]: parser.print_help()
usage: tool [command] [options]
Available commands:
foo foo.
bar bar.
Use "tool help" to get full list of supported commands
What I've done is customize the help with available parameters.
Alternative API and/or parser?
But your other lines, the parse.add() ones suggest you don't like the argparse method of defining 'commands'. You could add some methods to your parser that use this more compact syntax, but still end up calling the existing subparser mechanism.
But maybe you want to replace the whole parsing scheme with your own. One, for example, that expects the first argument to be a 'command'. What about other 'positionals'? Who or what handles the 'options'?
Do you realize that the argparse subparser scheme is built on top of the more basic optionals and positionals parsing scheme. The parser.add_subparsers command is a specialized form of add_argument. The subparsers object is a positional argument, with a special Action class. {foo,bar} is actually a list of the choices values that you defined for this argument (names or aliases of the subcommands). The subcommands themselves are parsers.
Custom front end command parser
If the sys.argv[1] item will always be a command name, you could set up something like this:
if sys.argv[1:]:
cmd = sys.argv[1]
rest = sys.argv[2:]
parser = parser_dict.get(cmd, None)
if parser:
args = parser.parse_args(rest)
else:
print_default_help()
Where parser_dict is a dictionary matching cmd strings to defined parsers. In effect this is just a front end that captures the first argument string, and dispatches the handling of the rest to other defined parsers. They could be a mix of argparse, optparse, and custom parsers. This front end does not have to be fancy if all it handles is the first 'command' string.
print_default_help would be little more than a pretty print of the parser_dict.
On further thought, I realized that the sp.choices attribute of an argparse subparsers object is just such a dictionary - with command strings as keys, and parsers as values.
Custom format_help methods
Here are a couple of custom help formatters.
A simple one that only gets the prog and _choices_actions from the parser. subparsers._choices_actions is a list of objects that contain help and aliases information for the individual sub parsers.
def simple_help(parser, subparsers):
# format a help message with just the subparser choices
usage = "Usage: %s command [options]"%parser.prog
desc = "Available commands:\n"
epilog = '\nUse "%s help" to get full list of supported commands.'%parser.prog
choices = fmt_choices(subparsers._choices_actions)
astr = [usage]
astr.append(desc)
astr.extend(choices)
astr.append(epilog)
return '\n'.join(astr)
def fmt_choices(choices):
# format names and help in 2 columns
x = max(len(k.metavar) for k in choices)
fmt = ' {:<%s} {}'%x
astr = []
for k in choices:
# k.metavar lists aliases as well
astr.append(fmt.format(k.dest, k.help))
return astr
This one is modeled on parser.format_help, and makes uses of the Formatter and all of its wrapping and spacing information. I wrote it to use non-default parameters where possible. It is hard, though, to suppress blank lines.
def special_help(parser, subparsers=None, usage=None, epilog=None):
# format help message using a Formatter
# modeled on parser.format_help
# uses nondefault parameters where possible
if usage is None:
usage = "%(prog)s command [options]"
if epilog is None:
epilog = "Use '%(prog)s help' for command list"
if subparsers is None:
# find the subparsers action in the parser
for action in parser._subparsers._group_actions:
if hasattr(action, '_get_subactions'):
subparsers = action
break
# if none found, subparsers is still None?
if parser._subparsers != parser._positionals:
title = parser._subparsers.title
desc = parser._subparsers.description
else:
title = "Available commands"
desc = None
if subparsers.metavar is None:
subparsers.metavar = '_________'
# restore to None at end?
formatter = parser._get_formatter()
if parser.usage is None:
formatter.add_usage(usage, [], [])
else:
formatter.add_usage(parser.usage,
parser._actions, parser._mutually_exclusive_groups)
# can I get rid of blank line here?
formatter.start_section(title)
formatter.add_text(desc)
formatter.add_arguments([subparsers])
formatter.end_section()
formatter.add_text(epilog)
return formatter.format_help()
These could be invoked in different ways. Either could replace the parser's format_help method, and thus be produced by the -h option, as well as with parser.print_help().
Or you could include a help subcommand. This would fit with the epilog message. -h would still produce the full, ugly help.
sp3 = sp.add_parser('help') # help='optional help message'
and test args:
if args.cmd in ['help']:
print(simple_help(parser, sp))
# print(special_help(parser))
Another option is to check sys.argv before parser.parser_args, and call the help function if that list isn't long enough, or includes a help string. This is roughly what Ipython does to bypass the regular argparse help.