15
votes

What is the "cleanest" way to implement an command-line UI, similar to git's, for example:

git push origin/master
git remote add origin git://example.com master

Ideally also allowing the more flexible parsing, for example,

jump_to_folder app theappname v2
jump_to_folder app theappname source
jump_to_folder app theappname source v2
jump_to_folder app theappname build v1
jump_to_folder app theappname build 1
jump_to_folder app theappname v2 build

jump_to_folder is the scripts name, app is the command, theappname is a "fixed-location" parameter, "build" and "v2" etc are arguments (For example, possible arguments would be any number/any number prefixed with a v, or build/source/tmp/config)

I could just manually parse the arguments with a series of if/else/elifs, but there must be a more elegant way to do this?

As an entirely theoretically example, I could describe the UI schema..

app:
    fixed: application_name

    optional params:
        arg subsection:
            "build"
            "source"
            "tmp"
            "config"

        arg version:
            integer
            "v" + integer

Then parse the supplied arguments though the above schema, and get a dictionary:

>>> print schema.parse(["app", "theappname", "v1", "source"])
{
    "application_name": "theappname",
    "params":{
        "subsection": "source",
        "version":"v1"
    }
}

Does such a system exist? If not, how would I go about implementing something along these lines?

6

6 Answers

17
votes

argparse is perfect for this, specifically "sub-commands" and positional args

import argparse


def main():
    arger = argparse.ArgumentParser()

    # Arguments for top-level, e.g "subcmds.py -v"
    arger.add_argument("-v", "--verbose", action="count", default=0)

    subparsers = arger.add_subparsers(dest="command")

    # Make parser for "subcmds.py info ..."
    info_parser = subparsers.add_parser("info")
    info_parser.add_argument("-m", "--moo", dest="moo")

    # Make parser for "subcmds.py create ..."
    create_parser = subparsers.add_parser("create")
    create_parser.add_argument("name")
    create_parser.add_argument("additional", nargs="*")

    # Parse
    opts = arger.parse_args()

    # Print option object for debug
    print opts

    if opts.command == "info":
        print "Info command"
        print "--moo was %s" % opts.moo

    elif opts.command == "create":
        print "Creating %s" % opts.name
        print "Additional: %s" % opts.additional

    else:
        # argparse will error on unexpected commands, but
        # in case we mistype one of the elif statements...
        raise ValueError("Unhandled command %s" % opts.command)


if __name__ == '__main__':
    main()

This can be used like so:

$ python subcmds.py create myapp v1 blah
Namespace(additional=['v1', 'blah'], command='create', name='myapp', verbose=0)
Creating myapp
Additional: ['v1', 'blah']
$ python subcmds.py info --moo
usage: subcmds.py info [-h] [-m MOO]
subcmds.py info: error: argument -m/--moo: expected one argument
$ python subcmds.py info --moo 1
Namespace(command='info', moo='1', verbose=0)
Info command
--moo was 1
9
votes

The cmd module would probably work well for this.

Example:

import cmd

class Calc(cmd.Cmd):
    def do_add(self, arg):
        print sum(map(int, arg.split()))

if __name__ == '__main__':
    Calc().cmdloop()

Run it:

$python calc.py
(Cmd) add 4 5
9
(Cmd) help

Undocumented commands:
======================
add  help

(Cmd)

See the Python docs or PyMOTW site for more info.

2
votes

Straight from one of my scripts:

import sys

def prog1_func1_act1(): print "pfa1"
def prog2_func2_act2(): print "pfa2"

commands = {
    "prog1 func1 act1": prog1_func1_act1,
    "prog2 func2 act2": prog2_func2_act2
}

try:
    commands[" ".join(sys.argv[1:])]()
except KeyError:
    print "Usage: ", commands.keys()

It's a pretty quick and dirty solution, but works great for my usage. If I were to clean it up a bit, I would probably add argparse to the mix for parsing positional and keyword arguments.

1
votes

Python has a module for parsing command line options, optparse.

0
votes

Here's my suggestion.

  1. Change your grammar slightly.

  2. Use optparse.

Ideally also allowing the more flexible parsing, for example,

jump_to_folder -n theappname -v2 cmd 
jump_to_folder -n theappname cmd source 
jump_to_folder -n theappname -v2 cmd source 
jump_to_folder -n theappname -v1 cmd build 
jump_to_folder -n theappname -1 cmd build 
jump_to_folder -n theappname -v2 cmd build

Then you have 1 or 2 args: the command is always the first arg. It's optional argument is always the second arg.

Everything else is options, in no particular order.