|
1 "Framework for command line interfaces like CVS. See class CmdFrameWork." |
|
2 |
|
3 |
|
4 class CommandFrameWork: |
|
5 |
|
6 """Framework class for command line interfaces like CVS. |
|
7 |
|
8 The general command line structure is |
|
9 |
|
10 command [flags] subcommand [subflags] [argument] ... |
|
11 |
|
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.] |
|
23 |
|
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 """ |
|
27 |
|
28 UsageMessage = \ |
|
29 "usage: (name)s [flags] subcommand [subflags] [argument] ..." |
|
30 |
|
31 PostUsageMessage = None |
|
32 |
|
33 GlobalFlags = '' |
|
34 |
|
35 def __init__(self): |
|
36 """Constructor, present for completeness.""" |
|
37 pass |
|
38 |
|
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) |
|
70 |
|
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 |
|
80 |
|
81 def ready(self): |
|
82 """Called just before calling the subcommand.""" |
|
83 pass |
|
84 |
|
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 |
|
114 |
|
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!" |
|
119 |
|
120 |
|
121 def test(): |
|
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) |
|
141 |
|
142 |
|
143 if __name__ == '__main__': |
|
144 test() |