"""Helper functions to support command line tools providing more than one command. e.g called as "tool command [options] args..." where and are command'specific :copyright: 2003-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved. :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr :license: General Public License version 2 - http://www.gnu.org/licenses """ __docformat__ = "restructuredtext en" # XXX : merge with optparser ? import sys from os.path import basename from logilab.common.configuration import Configuration DEFAULT_COPYRIGHT = '''\ Copyright (c) 2004-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved. http://www.logilab.fr/ -- mailto:contact@logilab.fr''' DEFAULT_DOC = '''\ Type "%prog --help" for more information about a specific command. Available commands are :\n''' class BadCommandUsage(Exception): """Raised when an unknown command is used or when a command is not correctly used. """ class Command(Configuration): """Base class for command line commands.""" arguments = '' name = '' # hidden from help ? hidden = False # max/min args, None meaning unspecified min_args = None max_args = None def __init__(self, __doc__=None, version=None): if __doc__: usage = __doc__ % (self.name, self.arguments, self.__doc__.replace(' ', '')) else: usage = self.__doc__.replace(' ', '') Configuration.__init__(self, usage=usage, version=version) def check_args(self, args): """check command's arguments are provided""" if self.min_args is not None and len(args) < self.min_args: raise BadCommandUsage('missing argument') if self.max_args is not None and len(args) > self.max_args: raise BadCommandUsage('too many arguments') def run(self, args): """run the command with its specific arguments""" raise NotImplementedError() def pop_arg(args_list, expected_size_after=0, msg="Missing argument"): """helper function to get and check command line arguments""" try: value = args_list.pop(0) except IndexError: raise BadCommandUsage(msg) if expected_size_after is not None and len(args_list) > expected_size_after: raise BadCommandUsage('too many arguments') return value _COMMANDS = {} def register_commands(commands): """register existing commands""" for command_klass in commands: _COMMANDS[command_klass.name] = command_klass def main_usage(status=0, __doc__=DEFAULT_DOC, copyright=DEFAULT_COPYRIGHT): """display usage for the main program (ie when no command supplied) and exit """ commands = _COMMANDS.keys() commands.sort() doc = __doc__ if doc != DEFAULT_DOC: try: doc = __doc__ % ('', '', '''\ Type "%prog --help" for more information about a specific command. Available commands are :\n''') except TypeError: print 'could not find the "command", "arguments" and "default" slots' doc = doc.replace('%prog', basename(sys.argv[0])) print 'usage:', doc max_len = max([len(cmd) for cmd in commands]) # list comprehension for py 2.3 support padding = ' '*max_len for command in commands: cmd = _COMMANDS[command] if not cmd.hidden: title = cmd.__doc__.split('.')[0] print ' ', (command+padding)[:max_len], title print '\n', copyright sys.exit(status) def cmd_run(cmdname, *args): try: command = _COMMANDS[cmdname](__doc__='%%prog %s %s\n\n%s') except KeyError: raise BadCommandUsage('no %s command' % cmdname) args = command.load_command_line_configuration(args) command.check_args(args) try: command.run(args) except KeyboardInterrupt: print 'interrupted' except BadCommandUsage, err: print 'ERROR: ', err print command.help() def main_run(args, doc=DEFAULT_DOC): """command line tool""" try: arg = args.pop(0) except IndexError: main_usage(status=1, __doc__=doc) if arg in ('-h', '--help'): main_usage(__doc__=doc) try: cmd_run(arg, *args) except BadCommandUsage, err: print 'ERROR: ', err main_usage(1, doc) class ListCommandsCommand(Command): """list available commands, useful for bash completion.""" name = 'listcommands' arguments = '[command]' hidden = True def run(self, args): """run the command with its specific arguments""" if args: command = pop_arg(args) cmd = _COMMANDS[command] for optname, optdict in cmd.options: print '--help' print '--' + optname else: commands = _COMMANDS.keys() commands.sort() for command in commands: cmd = _COMMANDS[command] if not cmd.hidden: print command register_commands([ListCommandsCommand])