26

Is there a Python module for doing gem/git-style command line arguments? What I mean by gem/git style is:

$ ./MyApp.py
The most commonly used MyApp commands are:
  add        Add file contents to the index
  bisect     Find by binary search the change that introduced a bug
  branch     List, create, or delete branches
  checkout   Checkout a branch or paths to the working tree
  ...

$ ./MyApp.py branch
  * current-branch
    master

With no arguments, the output tells you how you can proceed. And there is a special "help" command:

$ ./MyApp.py help branch

Which gets you deeper tips about the "branch" command.

Edit: And by doing I mean it does the usage printing for you, exits with invalid input, runs your functions according to your CLI specification. Sort of a "URL mapper" for the command line.

David Stolarsky
  • 439
  • 5
  • 13

3 Answers3

38

Yes, argparse with add_subparsers().

It's all well explained in the Sub-commands section.

Copying one of the examples from there:

>>> parser = argparse.ArgumentParser()
>>> subparsers = parser.add_subparsers()
>>> checkout = subparsers.add_parser('checkout', aliases=['co'])
>>> checkout.add_argument('foo')
>>> parser.parse_args(['checkout', 'bar'])
Namespace(foo='bar')

Edit: Unfortunately there's no self generated special help command, but you can get the verbose help message (that you seem to want) with -h or --help like one normally would after the command:

$ ./MyApp.py branch --help

By verbose I don't mean that is like a man page, it's like every other --help kind of help: listing all the arguments, etc...

Example:

>>> parser = argparse.ArgumentParser()
>>> subparsers = parser.add_subparsers(description='Sub description')
>>> checkout = subparsers.add_parser('checkout', description='Checkout description')
>>> checkout.add_argument('foo', help='This is the foo help')
>>> parser.parse_args(['checkout', '--help'])
usage:  checkout [-h] foo

Checkout description

positional arguments:
  foo         This is the foo help

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

If you need to, it should be easy to implement an help command that redirects to --help.

Ronan Boiteau
  • 9,608
  • 6
  • 34
  • 56
Rik Poggi
  • 28,332
  • 6
  • 65
  • 82
  • It might be worthwhile pointing out that the `aliases` keyword to `subparsers.add_parser()` is new to Python 3 and not available in Python 2.7. – Juan Jun 11 '13 at 05:15
  • Careful with argparse: once you start adding nested subparsers things get very messy. See for example: http://bugs.python.org/issue9253 – Federico Mar 19 '15 at 13:03
  • The great [click](http://click.pocoo.org/) package offers this functionality out of the box! Check the *complex* tutorial [here](http://click.pocoo.org/5/complex/) – Jaime Rodríguez-Guerra Nov 05 '15 at 17:53
  • ```Note that the object returned by parse_args() will only contain attributes for the main parser and the subparser that was selected by the command line (and not any other subparsers). So in the example above, when the a command is specified, only the foo and bar attributes are present, and when the b command is specified, only the foo and baz attributes are present.``` Do you know any way around this that would allow me to detect which subparser has been called? – MoralCode Aug 19 '18 at 22:19
  • nevermind. if you want to check which subparser was called add the `dest` option to your `add_subparsers()` call : https://stackoverflow.com/a/9286586 – MoralCode Aug 19 '18 at 22:21
6

A reasonable hack to get the gem/git style "help" behavior (I just wrote this for what I'm working on anyway):

parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='sub_commands')
parser_branch = subparsers.add_parser('branch', description='list of branches')
parser_help = subparsers.add_parser('help')
parser_help.add_argument('command', nargs="?", default=None)

# I can't find a legitimate way to set a default subparser in the docs
#   If you know of one, please let me know!
if len(sys.argv) < 2:
    sys.argv.append('--help')

parsed = parser.parse_args()

if parsed.sub_commands == "help":
    if not parsed.command:
        parser.parse_args(['--help'])
    else:
        parser.parse_args([parsed.command, '--help'])

argparse is definitely a step up from optparse and other python solutions I've come across. But IMO the gem/git style of handling args is just a more logical and safer way to do things so it's annoying that it's not supported.

danny
  • 10,103
  • 10
  • 50
  • 57
1

I wanted to do something similar to git commands, where I would load a second script based off of one of the command line options, and have that script populate more command line options, and also have the help work.

I was able to do this by disabling the help option, parse known args, add more arguments, re-enable the help option, and then parse the rest of the arguments.

This is what I came up with.

import argparse 
#Note add_help=False
arg_parser = argparse.ArgumentParser(description='Add more arguments after parsing.',add_help=False)
arg_parser.add_argument('MODE',  default='default',type=str, help='What commands to use')


args = arg_parser.parse_known_args()[0]

if args.MODE == 'branch':
   arg_parser.add_argument('-d', '--delete', default='Delete a branch')
   arg_parser.add_argument('-m', '--move', default='move a branch')
elif args.MODE == 'clone' :
   arg_parser.add_argument('--local', '-l')
   arg_parser.add_argument('--shared')

#Finally re-enable the help option, and reparse the arguments
arg_parser.add_argument(
            '-h', '--help',
            action='help', default=argparse.SUPPRESS,
            help=argparse._('show this help message and exit'))

args = arg_parser.parse_args()
Sam P
  • 681
  • 5
  • 19