A Generic CLI Dispatcher module for Python May 30, 2007
Posted by samwyse in Scripting.trackback
Here’s a little script that makes it easy to create command line interpreters (CLIs). Creating a CLI framework is one of my usual benchmarks when I learn a new programming language. It usually requires a bit of in-depth knowlege, yet is easy to code in only a day or so. In this case, it took a bit longer but it was worth it in the end because it taught me more about decorators.
I encountered decorators when I was first learning Python about a year ago, and apparently promptly forgot about them. Then, as I was searching for an answer to some question or another, I stumbled across Jeremy Lowery’s blog, THENSYS, where he had just posted “Demystifying Programmable Dispatch with Python“. There I was not only reminded of how decorators worked but given one that doesn’t decorate at all; it exists purely for its side-effects. Of course, Jeremy’s example was for dispatching in web-based applications, so I had to rewrite it for my needs. Still, I tried to make it a bit more general than I needed, so perhaps it will be useful to others as well.
Here’s an example of how to use it to set up a command with two aliases, “help” and “man”.
from GenericDispatcher import GenericDispatcher command = GenericDispatcher()@command(('help', 'man')) def cmdHelp(*argv): """Usage: %s [command] List available commands or explain their usage. """ dt = command.dispatch_table if len(argv): for arg in argv: try: print dt[arg][0].__doc__ % arg except: print "No help available for", arg else: print "Valid commands are:" print ', '.join(sorted(dt.keys())) print "Use 'help ' for more information."
I chose this as my example because (IMHO) it illustrates some other neat tricks. First of all, the “@command” decorator will add the “cmdHelp” function to the dispatch table twice, once under the “help” key and once under “man” (for any Unix users). The body of the command does one of two things: If you just say “help”, then a list of the dispatch table’s keys is displayed, so the user knows what commands are available. If you say “help” followed by one or more command names, then we look up the entry in the dispatch table, get the code pointer for the procedure (that’s the “[0]” part), and grab the doc-string, which is then printed with the command name interpolated at the appropriate spot. The try statement catches everything from user misspellings to forgotten doc-strings.
Enough talking, here’s the code:
#!/usr/bin/python """A class taht wraps a dispatch table, and a function/method decorator to make it easy to use. :Copyright: 2007 :Author: Sam Denton samwyse at gmail dot com) :Concept by: Jeremy Lowery :License: GPL, http://www.gnu.org/copyleft/gpl.html """ # based on code from: # http://www.thensys.com/index.php?title=demystifying_programmable_dispatch class GenericDispatcher(object): """cli_command = GenericDispatcher() Creates a dispatch table.""" def __init__(self): """Create an empty table.""" self.dispatch_table = {} def __call__(self, trigger, *argv): """@cli_command(trigger, [additional args]...) Creates a decorator that adds a procedure to the dispatch table. 'Trigger' may be a hashable object, which is then used as the key, or it may be an iteratable object, in which case the procedure is added to the table multiple times. (This is to support command aliases.) The additional args are also saved in the dispatch table, and are appended to the argument list when the procedure is dispatched. """ def stash(proc): """Actually add the function to the table""" if hasattr(trigger, '__iter__'): for item in trigger: self.dispatch_table[item] = (proc,) + argv else: self.dispatch_table[trigger] = (proc,) + argv return proc return stash def dispatch(self, trigger, *argv): """cli_command.dispatch(trigger, [optional args]...) Look up a procedure in the table, and invoke it with both the optional args and any addtional args stored in the table. """ entry = self.dispatch_table[trigger] proc, more = entry[0], entry[1:] return proc(*argv+more)
Hey Sam,
I’m glad that you had some ideas about customized dispatch routines in Python. A lot of those articles on thensys need quite a bit of clean up. I think it’s called rough draft hell.
Anyway, I like your CLI dispatch example. Nice and clever.