| 1 | # Copyright (c) 2004-2007 LOGILAB S.A. (Paris, FRANCE). |
|---|
| 2 | # http://www.logilab.fr/ -- mailto:contact@logilab.fr |
|---|
| 3 | # |
|---|
| 4 | # This program is free software; you can redistribute it and/or modify it under |
|---|
| 5 | # the terms of the GNU General Public License as published by the Free Software |
|---|
| 6 | # Foundation; either version 2 of the License, or (at your option) any later |
|---|
| 7 | # version. |
|---|
| 8 | # |
|---|
| 9 | # This program is distributed in the hope that it will be useful, but WITHOUT |
|---|
| 10 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
|---|
| 11 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. |
|---|
| 12 | # |
|---|
| 13 | # You should have received a copy of the GNU General Public License along with |
|---|
| 14 | # this program; if not, write to the Free Software Foundation, Inc., |
|---|
| 15 | # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
|---|
| 16 | """provides helper functions to handle a command line tool providing more than |
|---|
| 17 | one command |
|---|
| 18 | e.g called as "tool command [options] args..." where <options> and <args> are |
|---|
| 19 | command'specific |
|---|
| 20 | """ |
|---|
| 21 | |
|---|
| 22 | import sys |
|---|
| 23 | from os.path import basename |
|---|
| 24 | |
|---|
| 25 | from logilab.common.configuration import Configuration |
|---|
| 26 | |
|---|
| 27 | |
|---|
| 28 | DEFAULT_COPYRIGHT = '''\ |
|---|
| 29 | Copyright (c) 2004-2007 LOGILAB S.A. (Paris, FRANCE), all rights reserved. |
|---|
| 30 | http://www.logilab.fr/ -- mailto:contact@logilab.fr''' |
|---|
| 31 | |
|---|
| 32 | |
|---|
| 33 | class BadCommandUsage(Exception): |
|---|
| 34 | """Raised when an unknown command is used or when a command is not |
|---|
| 35 | correctly used |
|---|
| 36 | """ |
|---|
| 37 | |
|---|
| 38 | |
|---|
| 39 | class Command(Configuration): |
|---|
| 40 | """base class for command line commands""" |
|---|
| 41 | arguments = '' |
|---|
| 42 | name = '' |
|---|
| 43 | # max/min args, None meaning unspecified |
|---|
| 44 | min_args = None |
|---|
| 45 | max_args = None |
|---|
| 46 | def __init__(self, __doc__=None, version=None): |
|---|
| 47 | if __doc__: |
|---|
| 48 | usage = __doc__ % (self.name, self.arguments, |
|---|
| 49 | self.__doc__.replace(' ', '')) |
|---|
| 50 | else: |
|---|
| 51 | usage = self.__doc__.replace(' ', '') |
|---|
| 52 | Configuration.__init__(self, usage=usage, version=version) |
|---|
| 53 | |
|---|
| 54 | def check_args(self, args): |
|---|
| 55 | """check command's arguments are provided""" |
|---|
| 56 | if self.min_args is not None and len(args) < self.min_args: |
|---|
| 57 | raise BadCommandUsage('missing argument') |
|---|
| 58 | if self.max_args is not None and len(args) > self.max_args: |
|---|
| 59 | raise BadCommandUsage('too many arguments') |
|---|
| 60 | |
|---|
| 61 | def run(self, args): |
|---|
| 62 | """run the command with its specific arguments""" |
|---|
| 63 | raise NotImplementedError() |
|---|
| 64 | |
|---|
| 65 | |
|---|
| 66 | def pop_arg(args_list, expected_size_after=0, msg="Missing argument"): |
|---|
| 67 | """helper function to get and check command line arguments""" |
|---|
| 68 | try: |
|---|
| 69 | value = args_list.pop(0) |
|---|
| 70 | except IndexError: |
|---|
| 71 | raise BadCommandUsage(msg) |
|---|
| 72 | if expected_size_after is not None and len(args_list) > expected_size_after: |
|---|
| 73 | raise BadCommandUsage('Too much arguments') |
|---|
| 74 | return value |
|---|
| 75 | |
|---|
| 76 | |
|---|
| 77 | _COMMANDS = {} |
|---|
| 78 | |
|---|
| 79 | def register_commands(commands): |
|---|
| 80 | """register existing commands""" |
|---|
| 81 | for command_klass in commands: |
|---|
| 82 | _COMMANDS[command_klass.name] = command_klass |
|---|
| 83 | |
|---|
| 84 | |
|---|
| 85 | def main_usage(status=0, __doc__=None, copyright=DEFAULT_COPYRIGHT): |
|---|
| 86 | """display usage for the main program (ie when no command supplied) |
|---|
| 87 | and exit |
|---|
| 88 | """ |
|---|
| 89 | commands = _COMMANDS.keys() |
|---|
| 90 | commands.sort() |
|---|
| 91 | doc = __doc__ % ('<command>', '<command arguments>', |
|---|
| 92 | '''\ |
|---|
| 93 | Type "%prog <command> --help" for more information about a specific |
|---|
| 94 | command. Available commands are :\n''') |
|---|
| 95 | doc = doc.replace('%prog', basename(sys.argv[0])) |
|---|
| 96 | print 'usage:', doc |
|---|
| 97 | max_len = max([len(cmd) for cmd in commands]) # list comprehension for py 2.3 support |
|---|
| 98 | padding = ' '*max_len |
|---|
| 99 | for command in commands: |
|---|
| 100 | cmd = _COMMANDS[command] |
|---|
| 101 | title = cmd.__doc__.split('.')[0] |
|---|
| 102 | print ' ', (command+padding)[:max_len], title |
|---|
| 103 | print '\n', copyright |
|---|
| 104 | sys.exit(status) |
|---|
| 105 | |
|---|
| 106 | |
|---|
| 107 | def cmd_run(cmdname, *args): |
|---|
| 108 | try: |
|---|
| 109 | command = _COMMANDS[cmdname](__doc__='%%prog %s %s\n\n%s') |
|---|
| 110 | except KeyError: |
|---|
| 111 | raise BadCommandUsage('no %s command' % cmdname) |
|---|
| 112 | args = command.load_command_line_configuration(args) |
|---|
| 113 | command.check_args(args) |
|---|
| 114 | try: |
|---|
| 115 | command.run(args) |
|---|
| 116 | except KeyboardInterrupt: |
|---|
| 117 | print 'interrupted' |
|---|
| 118 | except BadCommandUsage, err: |
|---|
| 119 | print 'ERROR: ', err |
|---|
| 120 | print command.help() |
|---|
| 121 | |
|---|
| 122 | |
|---|
| 123 | def main_run(args, doc): |
|---|
| 124 | """command line tool""" |
|---|
| 125 | try: |
|---|
| 126 | arg = args.pop(0) |
|---|
| 127 | except IndexError: |
|---|
| 128 | main_usage(status=1, __doc__=doc) |
|---|
| 129 | if arg in ('-h', '--help'): |
|---|
| 130 | main_usage(__doc__=doc) |
|---|
| 131 | try: |
|---|
| 132 | cmd_run(arg, *args) |
|---|
| 133 | except BadCommandUsage, err: |
|---|
| 134 | print 'ERROR: ', err |
|---|
| 135 | main_usage(1, doc) |
|---|