Mercurial > hg > hq
view hq/command.py @ 0:b5671297a0db
initial commit of hq
author | Jeff Hammel <jhammel@mozilla.com> |
---|---|
date | Fri, 30 Apr 2010 14:31:35 -0700 |
parents | |
children | 321721b581f1 |
line wrap: on
line source
""" a command-line interface to the command line, a la pythonpaste """ import inspect import sys from optparse import OptionParser from pprint import pprint class CommandParser(OptionParser): # TODO: add `help` command def __init__(self, _class, description=None): self._class = _class self.commands = {} usage = '%prog [options] command [command-options]' description = description or _class.__doc__ OptionParser.__init__(self, usage=usage, description=description) commands = [ getattr(_class, i) for i in dir(_class) if not i.startswith('_') ] commands = [ method for method in commands if hasattr(method, '__call__') ] for _command in commands: self.command(_command) self.disable_interspersed_args() def print_help(self): # XXX should probably use the optparse formatters to help out here OptionParser.print_help(self) # short descriptions for commands command_descriptions = [dict(name=i, description=self.commands[i]['doc'].strip().split('\n',1)[0]) for i in sorted(self.commands.keys())] max_len = max([len(i['name']) for i in command_descriptions]) description = "Commands: \n%s" % ('\n'.join([' %s%s %s' % (description['name'], ' ' * (max_len - len(description['name'])), description['description']) for description in command_descriptions])) print print description def parse(self, args=sys.argv[1:]): """global parse step""" self.options, args = self.parse_args(args) # help/sanity check -- should probably be separated if not len(args): self.print_help() sys.exit(0) if args[0] == 'help': if len(args) == 2: if args[1] in self.commands: name = args[1] commandparser = self.command2parser(name) commandparser.print_help() else: self.error("No command '%s'" % args[1]) else: self.print_help() sys.exit(0) command = args[0] if command not in self.commands: self.error("No command '%s'" % command) return command, args[1:] def invoke(self, args=sys.argv[1:]): """ invoke """ # parse name, args = self.parse(args) # setup _object = self._class(self, self.options) # command specific args command = self.commands[name] commandparser = self.command2parser(name) command_options, command_args = commandparser.parse_args(args) if len(command_args) < len(command['args']): commandparser.error("Not enough arguments given") if len(command_args) != len(command['args']) and not command['varargs']: commandparser.error("Too many arguments given") # invoke the command retval = getattr(_object, name)(*command_args, **command_options.__dict__) if isinstance(retval, basestring): print retval elif retval is None: pass else: pprint(retval) return retval def command(self, function): name = function.func_name if function.__doc__: doc = inspect.cleandoc(function.__doc__) else: doc = '' argspec = inspect.getargspec(function) defaults = argspec.defaults if defaults: args = argspec.args[1:-len(defaults)] optional = dict(zip(argspec.args[-len(defaults):], defaults)) else: args = argspec.args[1:] optional = None self.commands[name] = { 'doc': doc, 'args': args, 'optional': optional, 'varargs': argspec.varargs } return function # XXX to restructure??? def commandargs2str(self, command): if isinstance(command, basestring): command = self.commands[command] retval = [] retval.extend(['<%s>' % arg for arg in command['args']]) varargs = command['varargs'] if varargs: retval.append('<%s> [%s] [...]' % (varargs, varargs)) if command['optional']: retval.append('[options]') return ' '.join(retval) def doc2arghelp(self, docstring, decoration='-', delimeter=':'): """ Parse a docstring and get at the section describing arguments - decoration: decoration character - delimeter: delimter character Yields a tuple of the stripped docstring and the arguments help dictionary """ lines = [ i.strip() for i in docstring.split('\n') ] argdict = {} doc = [] option = None for line in lines: if not line and option: # blank lines terminate [?] break if line.startswith(decoration) and delimeter in line: name, description = line.split(delimeter, 1) name = name.lstrip(decoration).strip() description = description.strip() argdict[name] = [ description ] option = name else: if option: argdict[name].append(line) else: doc.append(line) argdict = dict([(key, ' '.join(value)) for key, value in argdict.items()]) return ('\n'.join(doc), argdict) def command2parser(self, command): doc, argdict = self.doc2arghelp(self.commands[command]['doc']) parser = OptionParser('%%prog %s %s' % (command, self.commandargs2str(command)), description=doc, add_help_option=False) if self.commands[command]['optional']: for key, value in self.commands[command]['optional'].items(): help = argdict.get(key, '') if value is True: parser.add_option('--no-%s' % key, dest=key, action='store_false', default=True, help=help) elif value is False: parser.add_option('--%s' % key, action='store_true', default=False, help=help) else: help += ' [DEFAULT: %s]' % value parser.add_option('--%s' % key, help=help, default=value) return parser