Command Line Bliss with Docopt

16 Aug 2013

Starting to write the command-line client for the Deis open source PaaS, I was intimidated. I've written many command-line tools, most of them in Python, but the Deis CLI is more of a "swiss army knife" than I had attempted with argparse:

<code>$ deis register
$ deis create --flavor=ec2-eu-west-1
$ deis containers:scale web=4 worker=2
$ deis layers:create redis ec2-eu-west-1 --ssh_username=deisuser
$ deis help releases
Valid commands for releases:

releases:list        list a formation's release history
releases:info        print information about a specific release
releases:rollback    coming soon!

Use `deis help [command]` to learn more

Enter docopt

Docopt transforms conventional Usage text into a formal parser for command-line arguments. Simple as that. For many command- line tools, simply declaring a single Usage string and having docopt parse it can handle everything:

<code>"""Naval Fate.

Usage: ship new &lt;name&gt;... ship &lt;name&gt; move &lt;x&gt; &lt;y&gt; [--speed=&lt;kn&gt;] mine (set|remove) &lt;x&gt; &lt;y&gt; [--moored | --drifting] (-h | --help)

  -h --help     Show this screen.
  --speed=&lt;kn&gt;  Speed in knots [default: 10].
  --moored      Moored (anchored) mine.
  --drifting    Drifting mine.

from docopt import docopt

if __name__ == '__main__':
    arguments = docopt(__doc__, version='Naval Fate 2.0')

This example Python program will now enforce the command line arguments given in the module's triple-quoted docstring. Arguments are returned as a simple dict, not a custom object. There is no further abstraction, just the declaration of intent.

Double down

Using docopt in this straightforward way covered nearly all our requirements for the Deis client, except for colon-separated "compound" commands that we wanted to phrase this way:

<code>$ deis flavors:delete ec2-eu-west-1
$ deis nodes:list

Here's the strategy @gabrtv came up with:

  • parse the command line using the module docstring
  • transform colons to underscores and expand shortcuts to full commands ("logout" is really "auth:logout," for example)
  • find a client method with the same name as the command
  • re-parse the command line using the method's docstring
  • call the method with the resulting arguments

Deis' approach here has several advantages:

  • Command syntax is the docstring for the relevant method, so there's no mismatch and no searching for definitions
  • Minimal coding required
  • With a bit of care, the same docstrings work well as readable documentation for a tool such as Sphinx

Docopt is a fresh approach to command-line parsing that immediately made us wonder why no one had thought of this before. We use Python, but there are implementations of docopt available for Ruby, JavaScript, and PHP.

Have a look at the single python file that implements the Deis client. I'm not sure how it could be more terse and self-documenting. You may find docopt a good fit for writing your next command-line tool.

Posted in Deis v1 PaaS

triangle square circle

Did you enjoy this post?