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) |
Guido van Rossum | b940e11 | 2007-01-10 16:19:56 +0000 | [diff] [blame] | 45 | except getopt.error as msg: |
Tim Peters | e6ddc8b | 2004-07-18 05:56:09 +0000 | [diff] [blame] | 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) |
Guido van Rossum | b940e11 | 2007-01-10 16:19:56 +0000 | [diff] [blame] | 65 | except getopt.error as msg: |
Tim Peters | e6ddc8b | 2004-07-18 05:56:09 +0000 | [diff] [blame] | 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: |
Collin Winter | 6f2df4d | 2007-07-17 20:59:35 +0000 | [diff] [blame] | 75 | print("-"*40) |
| 76 | print("Options:") |
Tim Peters | e6ddc8b | 2004-07-18 05:56:09 +0000 | [diff] [blame] | 77 | for o, a in opts: |
Collin Winter | 6f2df4d | 2007-07-17 20:59:35 +0000 | [diff] [blame] | 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).""" |
Collin Winter | 6f2df4d | 2007-07-17 20:59:35 +0000 | [diff] [blame] | 87 | if msg: print(msg) |
| 88 | print(self.UsageMessage % {'name': self.__class__.__name__}) |
Tim Peters | e6ddc8b | 2004-07-18 05:56:09 +0000 | [diff] [blame] | 89 | docstrings = {} |
| 90 | c = self.__class__ |
| 91 | while 1: |
| 92 | for name in dir(c): |
| 93 | if name[:3] == 'do_': |
Collin Winter | 6f2df4d | 2007-07-17 20:59:35 +0000 | [diff] [blame] | 94 | if name in docstrings: |
Tim Peters | e6ddc8b | 2004-07-18 05:56:09 +0000 | [diff] [blame] | 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: |
Collin Winter | 6f2df4d | 2007-07-17 20:59:35 +0000 | [diff] [blame] | 106 | print("where subcommand can be:") |
| 107 | names = list(docstrings.keys()) |
Tim Peters | e6ddc8b | 2004-07-18 05:56:09 +0000 | [diff] [blame] | 108 | names.sort() |
| 109 | for name in names: |
Collin Winter | 6f2df4d | 2007-07-17 20:59:35 +0000 | [diff] [blame] | 110 | print(docstrings[name]) |
Tim Peters | e6ddc8b | 2004-07-18 05:56:09 +0000 | [diff] [blame] | 111 | if self.PostUsageMessage: |
Collin Winter | 6f2df4d | 2007-07-17 20:59:35 +0000 | [diff] [blame] | 112 | print(self.PostUsageMessage) |
Tim Peters | e6ddc8b | 2004-07-18 05:56:09 +0000 | [diff] [blame] | 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.""" |
Collin Winter | 6f2df4d | 2007-07-17 20:59:35 +0000 | [diff] [blame] | 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" |
Collin Winter | 6f2df4d | 2007-07-17 20:59:35 +0000 | [diff] [blame] | 127 | print("Hello, world") |
Tim Peters | e6ddc8b | 2004-07-18 05:56:09 +0000 | [diff] [blame] | 128 | x = Hello() |
| 129 | tests = [ |
| 130 | [], |
| 131 | ['hello'], |
| 132 | ['spam'], |
| 133 | ['-x'], |
| 134 | ['hello', '-x'], |
| 135 | None, |
| 136 | ] |
| 137 | for t in tests: |
Collin Winter | 6f2df4d | 2007-07-17 20:59:35 +0000 | [diff] [blame] | 138 | print('-'*10, t, '-'*10) |
Tim Peters | e6ddc8b | 2004-07-18 05:56:09 +0000 | [diff] [blame] | 139 | sts = x.run(t) |
Collin Winter | 6f2df4d | 2007-07-17 20:59:35 +0000 | [diff] [blame] | 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() |