Guido van Rossum | 318b80d | 1995-04-27 23:00:17 +0000 | [diff] [blame] | 1 | "Framework for command line interfaces like CVS. See class CmdFrameWork." |
| 2 | |
| 3 | |
Guido van Rossum | 78016d8 | 1995-04-27 23:32:47 +0000 | [diff] [blame] | 4 | class CommandFrameWork: |
Guido van Rossum | 318b80d | 1995-04-27 23:00:17 +0000 | [diff] [blame] | 5 | |
Tim Peters | e6ddc8b | 2004-07-18 05:56:09 +0000 | [diff] [blame] | 6 | """Framework class for command line interfaces like CVS. |
Guido van Rossum | 318b80d | 1995-04-27 23:00:17 +0000 | [diff] [blame] | 7 | |
Tim Peters | e6ddc8b | 2004-07-18 05:56:09 +0000 | [diff] [blame] | 8 | The general command line structure is |
Guido van Rossum | 318b80d | 1995-04-27 23:00:17 +0000 | [diff] [blame] | 9 | |
Tim Peters | e6ddc8b | 2004-07-18 05:56:09 +0000 | [diff] [blame] | 10 | command [flags] subcommand [subflags] [argument] ... |
Guido van Rossum | 318b80d | 1995-04-27 23:00:17 +0000 | [diff] [blame] | 11 | |
Tim Peters | e6ddc8b | 2004-07-18 05:56:09 +0000 | [diff] [blame] | 12 | There's a class variable GlobalFlags which specifies the |
| 13 | global flags options. Subcommands are defined by defining |
| 14 | methods named do_<subcommand>. Flags for the subcommand are |
| 15 | defined by defining class or instance variables named |
| 16 | flags_<subcommand>. If there's no command, method default() |
| 17 | is called. The __doc__ strings for the do_ methods are used |
| 18 | for the usage message, printed after the general usage message |
| 19 | which is the class variable UsageMessage. The class variable |
| 20 | PostUsageMessage is printed after all the do_ methods' __doc__ |
| 21 | strings. The method's return value can be a suggested exit |
| 22 | status. [XXX Need to rewrite this to clarify it.] |
Guido van Rossum | 318b80d | 1995-04-27 23:00:17 +0000 | [diff] [blame] | 23 | |
Tim Peters | e6ddc8b | 2004-07-18 05:56:09 +0000 | [diff] [blame] | 24 | Common usage is to derive a class, instantiate it, and then call its |
| 25 | run() method; by default this takes its arguments from sys.argv[1:]. |
| 26 | """ |
Guido van Rossum | 318b80d | 1995-04-27 23:00:17 +0000 | [diff] [blame] | 27 | |
Tim Peters | e6ddc8b | 2004-07-18 05:56:09 +0000 | [diff] [blame] | 28 | UsageMessage = \ |
| 29 | "usage: (name)s [flags] subcommand [subflags] [argument] ..." |
Guido van Rossum | 318b80d | 1995-04-27 23:00:17 +0000 | [diff] [blame] | 30 | |
Tim Peters | e6ddc8b | 2004-07-18 05:56:09 +0000 | [diff] [blame] | 31 | PostUsageMessage = None |
Guido van Rossum | 78016d8 | 1995-04-27 23:32:47 +0000 | [diff] [blame] | 32 | |
Tim Peters | e6ddc8b | 2004-07-18 05:56:09 +0000 | [diff] [blame] | 33 | GlobalFlags = '' |
Guido van Rossum | 318b80d | 1995-04-27 23:00:17 +0000 | [diff] [blame] | 34 | |
Tim Peters | e6ddc8b | 2004-07-18 05:56:09 +0000 | [diff] [blame] | 35 | def __init__(self): |
| 36 | """Constructor, present for completeness.""" |
| 37 | pass |
Guido van Rossum | 318b80d | 1995-04-27 23:00:17 +0000 | [diff] [blame] | 38 | |
Tim Peters | e6ddc8b | 2004-07-18 05:56:09 +0000 | [diff] [blame] | 39 | def run(self, args = None): |
| 40 | """Process flags, subcommand and options, then run it.""" |
| 41 | import getopt, sys |
| 42 | if args is None: args = sys.argv[1:] |
| 43 | try: |
| 44 | opts, args = getopt.getopt(args, self.GlobalFlags) |
| 45 | except getopt.error, msg: |
| 46 | return self.usage(msg) |
| 47 | self.options(opts) |
| 48 | if not args: |
| 49 | self.ready() |
| 50 | return self.default() |
| 51 | else: |
| 52 | cmd = args[0] |
| 53 | mname = 'do_' + cmd |
| 54 | fname = 'flags_' + cmd |
| 55 | try: |
| 56 | method = getattr(self, mname) |
| 57 | except AttributeError: |
| 58 | return self.usage("command %r unknown" % (cmd,)) |
| 59 | try: |
| 60 | flags = getattr(self, fname) |
| 61 | except AttributeError: |
| 62 | flags = '' |
| 63 | try: |
| 64 | opts, args = getopt.getopt(args[1:], flags) |
| 65 | except getopt.error, msg: |
| 66 | return self.usage( |
| 67 | "subcommand %s: " % cmd + str(msg)) |
| 68 | self.ready() |
| 69 | return method(opts, args) |
Guido van Rossum | 318b80d | 1995-04-27 23:00:17 +0000 | [diff] [blame] | 70 | |
Tim Peters | e6ddc8b | 2004-07-18 05:56:09 +0000 | [diff] [blame] | 71 | def options(self, opts): |
| 72 | """Process the options retrieved by getopt. |
| 73 | Override this if you have any options.""" |
| 74 | if opts: |
| 75 | print "-"*40 |
| 76 | print "Options:" |
| 77 | for o, a in opts: |
| 78 | print 'option', o, 'value', repr(a) |
| 79 | print "-"*40 |
Guido van Rossum | 318b80d | 1995-04-27 23:00:17 +0000 | [diff] [blame] | 80 | |
Tim Peters | e6ddc8b | 2004-07-18 05:56:09 +0000 | [diff] [blame] | 81 | def ready(self): |
| 82 | """Called just before calling the subcommand.""" |
| 83 | pass |
Guido van Rossum | 78016d8 | 1995-04-27 23:32:47 +0000 | [diff] [blame] | 84 | |
Tim Peters | e6ddc8b | 2004-07-18 05:56:09 +0000 | [diff] [blame] | 85 | def usage(self, msg = None): |
| 86 | """Print usage message. Return suitable exit code (2).""" |
| 87 | if msg: print msg |
| 88 | print self.UsageMessage % {'name': self.__class__.__name__} |
| 89 | docstrings = {} |
| 90 | c = self.__class__ |
| 91 | while 1: |
| 92 | for name in dir(c): |
| 93 | if name[:3] == 'do_': |
| 94 | if docstrings.has_key(name): |
| 95 | continue |
| 96 | try: |
| 97 | doc = getattr(c, name).__doc__ |
| 98 | except: |
| 99 | doc = None |
| 100 | if doc: |
| 101 | docstrings[name] = doc |
| 102 | if not c.__bases__: |
| 103 | break |
| 104 | c = c.__bases__[0] |
| 105 | if docstrings: |
| 106 | print "where subcommand can be:" |
| 107 | names = docstrings.keys() |
| 108 | names.sort() |
| 109 | for name in names: |
| 110 | print docstrings[name] |
| 111 | if self.PostUsageMessage: |
| 112 | print self.PostUsageMessage |
| 113 | return 2 |
Guido van Rossum | 318b80d | 1995-04-27 23:00:17 +0000 | [diff] [blame] | 114 | |
Tim Peters | e6ddc8b | 2004-07-18 05:56:09 +0000 | [diff] [blame] | 115 | def default(self): |
| 116 | """Default method, called when no subcommand is given. |
| 117 | You should always override this.""" |
| 118 | print "Nobody expects the Spanish Inquisition!" |
Guido van Rossum | 318b80d | 1995-04-27 23:00:17 +0000 | [diff] [blame] | 119 | |
| 120 | |
| 121 | def test(): |
Tim Peters | e6ddc8b | 2004-07-18 05:56:09 +0000 | [diff] [blame] | 122 | """Test script -- called when this module is run as a script.""" |
| 123 | import sys |
| 124 | class Hello(CommandFrameWork): |
| 125 | def do_hello(self, opts, args): |
| 126 | "hello -- print 'hello world', needs no arguments" |
| 127 | print "Hello, world" |
| 128 | x = Hello() |
| 129 | tests = [ |
| 130 | [], |
| 131 | ['hello'], |
| 132 | ['spam'], |
| 133 | ['-x'], |
| 134 | ['hello', '-x'], |
| 135 | None, |
| 136 | ] |
| 137 | for t in tests: |
| 138 | print '-'*10, t, '-'*10 |
| 139 | sts = x.run(t) |
| 140 | print "Exit status:", repr(sts) |
Guido van Rossum | 318b80d | 1995-04-27 23:00:17 +0000 | [diff] [blame] | 141 | |
| 142 | |
| 143 | if __name__ == '__main__': |
Tim Peters | e6ddc8b | 2004-07-18 05:56:09 +0000 | [diff] [blame] | 144 | test() |