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 http://ec2-198-51-100-36.us-west-2.compute.amazonaws.com
$ 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
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:
naval_fate.py ship new <name>...
naval_fate.py ship <name> move <x> <y> [--speed=<kn>]
naval_fate.py mine (set|remove) <x> <y> [--moored | --drifting]
naval_fate.py (-h | --help)
-h --help Show this screen.
--speed=<kn> 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.
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
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.