Mercurial > hg > gut
comparison gut/command.py @ 0:9688c72a93c3
initial commit of gut, probably doesnt actually work
author | Jeff Hammel <jhammel@mozilla.com> |
---|---|
date | Fri, 16 Jul 2010 13:55:58 -0700 |
parents | |
children | 6cf716c40bb6 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:9688c72a93c3 |
---|---|
1 """ | |
2 a command-line interface to the command line, a la pythonpaste | |
3 """ | |
4 | |
5 import inspect | |
6 import sys | |
7 from optparse import OptionParser | |
8 from pprint import pprint | |
9 | |
10 class CommandParser(OptionParser): | |
11 # TODO: add `help` command | |
12 | |
13 def __init__(self, _class, description=None): | |
14 self._class = _class | |
15 self.commands = {} | |
16 usage = '%prog [options] command [command-options]' | |
17 description = description or _class.__doc__ | |
18 OptionParser.__init__(self, usage=usage, description=description) | |
19 commands = [ getattr(_class, i) for i in dir(_class) | |
20 if not i.startswith('_') ] | |
21 commands = [ method for method in commands | |
22 if hasattr(method, '__call__') ] | |
23 for _command in commands: | |
24 c = self.command(_command) | |
25 self.commands[c['name']] = c | |
26 | |
27 # get class options | |
28 init = self.command(_class.__init__) | |
29 self.command2parser(init, self) | |
30 | |
31 self.disable_interspersed_args() | |
32 | |
33 def print_help(self): | |
34 # XXX should probably use the optparse formatters to help out here | |
35 | |
36 OptionParser.print_help(self) | |
37 | |
38 # short descriptions for commands | |
39 command_descriptions = [dict(name=i, | |
40 description=self.commands[i]['doc'].strip().split('\n',1)[0]) | |
41 for i in sorted(self.commands.keys())] | |
42 max_len = max([len(i['name']) for i in command_descriptions]) | |
43 description = "Commands: \n%s" % ('\n'.join([' %s%s %s' % (description['name'], ' ' * (max_len - len(description['name'])), description['description']) | |
44 for description in command_descriptions])) | |
45 | |
46 print | |
47 print description | |
48 | |
49 def parse(self, args=sys.argv[1:]): | |
50 """global parse step""" | |
51 | |
52 self.options, args = self.parse_args(args) | |
53 | |
54 # help/sanity check -- should probably be separated | |
55 if not len(args): | |
56 self.print_help() | |
57 sys.exit(0) | |
58 if args[0] == 'help': | |
59 if len(args) == 2: | |
60 if args[1] in self.commands: | |
61 name = args[1] | |
62 commandparser = self.command2parser(name) | |
63 commandparser.print_help() | |
64 else: | |
65 self.error("No command '%s'" % args[1]) | |
66 else: | |
67 self.print_help() | |
68 sys.exit(0) | |
69 command = args[0] | |
70 if command not in self.commands: | |
71 self.error("No command '%s'" % command) | |
72 return command, args[1:] | |
73 | |
74 def invoke(self, args=sys.argv[1:]): | |
75 """ | |
76 invoke | |
77 """ | |
78 | |
79 # parse | |
80 name, args = self.parse(args) | |
81 | |
82 # setup | |
83 _object = self._class(**self.options.__dict__) | |
84 | |
85 # command specific args | |
86 command = self.commands[name] | |
87 commandparser = self.command2parser(name) | |
88 command_options, command_args = commandparser.parse_args(args) | |
89 if len(command_args) < len(command['args']): | |
90 commandparser.error("Not enough arguments given") | |
91 if len(command_args) != len(command['args']) and not command['varargs']: | |
92 commandparser.error("Too many arguments given") | |
93 | |
94 # invoke the command | |
95 retval = getattr(_object, name)(*command_args, **command_options.__dict__) | |
96 if isinstance(retval, basestring): | |
97 print retval | |
98 elif retval is None: | |
99 pass | |
100 else: | |
101 pprint(retval) | |
102 return retval | |
103 | |
104 def command(self, function): | |
105 name = function.func_name | |
106 if function.__doc__: | |
107 doc = inspect.cleandoc(function.__doc__) | |
108 else: | |
109 doc = '' | |
110 argspec = inspect.getargspec(function) | |
111 defaults = argspec.defaults | |
112 if defaults: | |
113 args = argspec.args[1:-len(defaults)] | |
114 optional = dict(zip(argspec.args[-len(defaults):], defaults)) | |
115 else: | |
116 args = argspec.args[1:] | |
117 optional = None | |
118 command = { 'doc': doc, | |
119 'name': name, | |
120 'args': args, | |
121 'optional': optional, | |
122 'varargs': argspec.varargs | |
123 } | |
124 return command | |
125 | |
126 def commandargs2str(self, command): | |
127 if isinstance(command, basestring): | |
128 command = self.commands[command] | |
129 retval = [] | |
130 retval.extend(['<%s>' % arg for arg in command['args']]) | |
131 varargs = command['varargs'] | |
132 if varargs: | |
133 retval.append('<%s> [%s] [...]' % (varargs, varargs)) | |
134 if command['optional']: | |
135 retval.append('[options]') | |
136 return ' '.join(retval) | |
137 | |
138 def doc2arghelp(self, docstring, decoration='-', delimeter=':'): | |
139 """ | |
140 Parse a docstring and get at the section describing arguments | |
141 - decoration: decoration character | |
142 - delimeter: delimter character | |
143 | |
144 Yields a tuple of the stripped docstring and the arguments help | |
145 dictionary | |
146 """ | |
147 lines = [ i.strip() for i in docstring.split('\n') ] | |
148 argdict = {} | |
149 doc = [] | |
150 option = None | |
151 for line in lines: | |
152 if not line and option: # blank lines terminate [?] | |
153 break | |
154 if line.startswith(decoration) and delimeter in line: | |
155 name, description = line.split(delimeter, 1) | |
156 name = name.lstrip(decoration).strip() | |
157 description = description.strip() | |
158 argdict[name] = [ description ] | |
159 option = name | |
160 else: | |
161 if option: | |
162 argdict[name].append(line) | |
163 else: | |
164 doc.append(line) | |
165 argdict = dict([(key, ' '.join(value)) | |
166 for key, value in argdict.items()]) | |
167 return ('\n'.join(doc), argdict) | |
168 | |
169 def command2parser(self, command, parser=None): | |
170 if isinstance(command, basestring): | |
171 command = self.commands[command] | |
172 doc, argdict = self.doc2arghelp(command['doc']) | |
173 if parser is None: | |
174 parser = OptionParser('%%prog %s %s' % (command, self.commandargs2str(command)), | |
175 description=doc, add_help_option=False) | |
176 if command['optional']: | |
177 for key, value in command['optional'].items(): | |
178 help = argdict.get(key, '') | |
179 if value is True: | |
180 parser.add_option('--no-%s' % key, dest=key, | |
181 action='store_false', default=True, | |
182 help=help) | |
183 elif value is False: | |
184 parser.add_option('--%s' % key, action='store_true', | |
185 default=False, help=help) | |
186 else: | |
187 if value is not None: | |
188 help += ' [DEFAULT: %s]' % value | |
189 parser.add_option('--%s' % key, help=help, default=value) | |
190 | |
191 return parser |