• programming in python • a course for the curious •

Command line user interface with Python

There are two options for parsing the command line that you might think off at first: the optparse module (which is obsolte by the way) and the argparse module. But there is a much simpler and more efficient tool: docopt module. It gives you just one function docopt.docopt(), which can make the magic. You just pass a usage message and optionally an argv list. For example let us implement on of the iptables interfaces. The man page of iptables specification contains these lines:

  1. iptables [-t table] {-A|-C|-D} chain rule-specification
  2. rule-specification = [matches...] [target]
  3. match = -m matchname [per-match-options]
  4. target = -j targetname [per-target-options]

I will only ignore per-match-options and per-target-options to keep things simple. The usage string that will work with docopt module in this case is almost a copy of the above specification:
  1. doc="""Usage:
  2. iptables [-t=<table>] (-A|-C|-D) <chain> [-m=<match_name>]... [-j=<target_name>]
  3. Options:
  4. -t=<table> table [default: table]
  5. -m=<match_name> match
  6. -j=<target_name> target
  7. """

The docopt function can parse both `Usage:` and `Options:` part and get the relevant information. The usage message should be compliant with the POSIX standard which most of you are familiar with, since many (though not all) linux man pages are written using it.

Now let us test the result:

  1. >>> from docopt import docopt
  2. >>> cmd = docopt(doc, ['-t', 'nat', '-D', 'INPUT', '-m', 'conntrack', '-j', 'ACCEPT'])
  3. >>> cmd # it is just a dictionary:
  4. {'-A': False,
  5. '-C': False,
  6. '-D': True,
  7. '-j': 'ACCEPT',
  8. '-m': ['conntrack'],
  9. '-t': 'nat',
  10. '<chain>': 'INPUT'}

Let me explain it in more detail: the [ ] means that this part is optional (you can also set the default value), the (-A|-C|-D) means that one of the switches -A, -C, -D is required and the ellipsis ... means that you can repeat the previous option:

  1. >>> cmd = docopt(doc, ['-A', 'CHAIN', '-m', 'MATCH_1', '-m', 'MATCH_2'])
  2. >>> cmd
  3. {'-A': True,
  4. '-C': False,
  5. '-D': False,
  6. '-j': None,
  7. '-m': ['MATCH_1', 'MATCH_2'], # here is a list of all -m values
  8. '-t': 'table',
  9. '<chain>': 'CHAIN'}

The limitation I have found is the parsing of the following git command:

  1. >>> git_cmd = """Usage:
  2. ... git [--version] [--help] [-c <option>=<value>]...
  3. ..."""
  4. >>> docopt(git_cmd, ['-c', 'name=value'])
  5. {"--help": false,
  6. "--version": false,
  7. "-c": true,
  8. "<name>=<value>": "name=value"}

But if we remove the '=' we can get a very similar interface:

  1. >>> git_cmd = """Usage:
  2. ... git [--version] [--help] [-c <name> <value>]...
  3. ..."""
  4. ...
  5. >>> cmd = docopt(git_cmd, ['-c', 'user.name', 'me', '-c', 'user.email', 'me@email.org'])
  6. >>> cmd
  7. {"-c": 2,
  8. "<option>": ["user.name", "user.email"],
  9. "<value>": ["me", "me@email.org"]}

Now you can use href="http://docs.python.org/2/library/functions.html?highlight=zip#zip">zip
to get a dictionary of options:

  1. >>> config_opts = dic(zip(cmd['<name>'], cmd['<value>']))
  2. >>> config_opts
  3. { 'user.name': 'me', 'user.email': 'me@email.org'}

You can find more examples a youtube video from PyCon2012 and the documentation at docopt.org.

Contact us.
Created using , and
we are not associated with Python Software Foundation
© Copyright 2012, 2013 Accorda Institute.