ProfileManager
view profilemanager/command.py @ 79:145e111903d2
add MPL license
| author | Jeff Hammel <jhammel@mozilla.com> |
|---|---|
| date | Mon May 10 13:11:38 2010 -0700 (2 years ago) |
| parents | 18f16bd1ba6b |
| children |
line source
1 # ***** BEGIN LICENSE BLOCK *****
2 # Version: MPL 1.1/GPL 2.0/LGPL 2.1
3 #
4 # The contents of this file are subject to the Mozilla Public License Version
5 # 1.1 (the "License"); you may not use this file except in compliance with
6 # the License. You may obtain a copy of the License at
7 # http://www.mozilla.org/MPL/
8 #
9 # Software distributed under the License is distributed on an "AS IS" basis,
10 # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
11 # for the specific language governing rights and limitations under the
12 # License.
13 #
14 # The Original Code is mozilla.org code.
15 #
16 # The Initial Developer of the Original Code is
17 # Mozilla.org.
18 # Portions created by the Initial Developer are Copyright (C) 2010
19 # the Initial Developer. All Rights Reserved.
20 #
21 # Contributor(s):
22 # Jeff Hammel <jhammel@mozilla.com> (Original author)
23 #
24 # Alternatively, the contents of this file may be used under the terms of
25 # either of the GNU General Public License Version 2 or later (the "GPL"),
26 # or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
27 # in which case the provisions of the GPL or the LGPL are applicable instead
28 # of those above. If you wish to allow use of your version of this file only
29 # under the terms of either the GPL or the LGPL, and not to allow others to
30 # use your version of this file under the terms of the MPL, indicate your
31 # decision by deleting the provisions above and replace them with the notice
32 # and other provisions required by the GPL or the LGPL. If you do not delete
33 # the provisions above, a recipient may use your version of this file under
34 # the terms of any one of the MPL, the GPL or the LGPL.
35 #
36 # ***** END LICENSE BLOCK *****
37 """
38 a command-line interface to the command line, a la pythonpaste
39 """
41 import inspect
42 import sys
43 from optparse import OptionParser
44 from pprint import pprint
46 if 'commands' not in globals():
47 commands = {}
49 def command(function):
50 # XXX should get bound/unbound state from function (how?)
51 global commands
52 name = function.func_name
53 doc = inspect.cleandoc(function.__doc__)
54 argspec = inspect.getargspec(function)
55 defaults = argspec.defaults
56 if defaults:
57 args = argspec.args[1:-len(defaults)]
58 optional = dict(zip(argspec.args[-len(defaults):], defaults))
59 else:
60 args = argspec.args[1:]
61 optional = None
62 commands[name] = { 'doc': doc,
63 'args': args,
64 'optional': optional,
65 'varargs': argspec.varargs
66 }
67 return function
69 def commandargs2str(command):
70 if isinstance(command, basestring):
71 command = commands[command]
72 retval = []
73 retval.extend(['<%s>' % arg for arg in command['args']])
74 varargs = command['varargs']
75 if varargs:
76 retval.append('<%s> [%s] [...]' % (varargs, varargs))
77 if command['optional']:
78 retval.append('[options]')
79 return ' '.join(retval)
82 def doc2arghelp(docstring, decoration='-', delimeter=':'):
83 """
84 Parse a docstring and get at the section describing arguments
85 - decoration: decoration character
86 - delimeter: delimter character
88 Yields a tuple of the stripped docstring and the arguments help
89 dictionary
90 """
91 lines = [ i.strip() for i in docstring.split('\n') ]
92 argdict = {}
93 doc = []
94 option = None
95 for line in lines:
96 if not line and option: # blank lines terminate
97 break
98 if line.startswith(decoration) and delimeter in line:
99 name, description = line.split(delimeter, 1)
100 name = name.lstrip(decoration).strip()
101 description = description.strip()
102 argdict[name] = [ description ]
103 option = name
104 else:
105 if option:
106 argdict[name].append(line)
107 else:
108 doc.append(line)
109 argdict = dict([(key, ' '.join(value))
110 for key, value in argdict.items()])
111 return ('\n'.join(doc), argdict)
113 def command2parser(command):
114 doc, argdict = doc2arghelp(commands[command]['doc'])
115 parser = OptionParser('%%prog %s %s' % (command, commandargs2str(command)),
116 description=doc, add_help_option=False)
117 if commands[command]['optional']:
118 for key, value in commands[command]['optional'].items():
119 help = argdict.get(key)
120 if value is True:
121 parser.add_option('--no-%s' % key, dest=key,
122 action='store_false', default=True,
123 help=help)
124 elif value is False:
125 parser.add_option('--%s' % key, action='store_true',
126 default=False, help=help)
127 else:
128 parser.add_option('--%s' % key, help=help)
130 return parser
132 class CommandParser(OptionParser):
133 def __init__(self, commands, description=None, setup=None):
134 usage = '%prog [options] command [command-options]'
135 OptionParser.__init__(self, usage=usage, description=description)
136 for _command in commands:
137 command(_command)
138 self.disable_interspersed_args()
139 self.setup = setup
141 def print_help(self):
142 OptionParser.print_help(self)
143 # short descriptions for commands
144 command_descriptions = [dict(name=i,
145 description=commands[i]['doc'].strip().split('\n',1)[0])
146 for i in sorted(commands.keys())]
147 max_len = max([len(i['name']) for i in command_descriptions])
148 description = "Commands: \n%s" % ('\n'.join([' %s%s %s' % (description['name'], ' ' * (max_len - len(description['name'])), description['description'])
149 for description in command_descriptions]))
151 print
152 print description
154 def parse(self, args=sys.argv[1:]):
155 """global parse step"""
157 self.options, args = self.parse_args(args)
159 # help/sanity check -- should probably be separated
160 if not len(args):
161 self.print_help()
162 sys.exit(0)
163 if args[0] == 'help':
164 if len(args) == 2:
165 if args[1] in commands:
166 name = args[1]
167 commandparser = command2parser(name)
168 commandparser.print_help()
169 else:
170 self.error("No command '%s'" % args[1])
171 else:
172 self.print_help()
173 sys.exit(0)
174 command = args[0]
175 if command not in commands:
176 self.error("No command '%s'" % command)
177 return command, args[1:]
179 def invoke(self, args=sys.argv[1:]):
180 """
181 invoke
182 """
184 # parse
185 name, args = self.parse(args)
187 # setup
188 _object = self.setup(self, self.options)
190 # command specific args
191 command = commands[name]
192 commandparser = command2parser(name)
193 command_options, command_args = commandparser.parse_args(args)
194 if len(command_args) < len(command['args']):
195 commandparser.error("Not enough arguments given")
196 if len(command_args) != len(command['args']) and not command['varargs']:
197 commandparser.error("Too many arguments given")
199 # invoke the command
200 retval = getattr(_object, name)(*command_args, **command_options.__dict__)
202 # print the output
203 if retval is None:
204 pass
205 elif isinstance(retval, basestring):
206 print retval
207 elif isinstance(retval, dict):
208 for key in sorted(retval.keys()):
209 print '%s: %s' % (key, retval[key])
210 elif hasattr(retval, '__iter__'):
212 # hack since python doesn't have ordered dicts
213 if not [ val for val in retval
214 if not(isinstance(val, tuple) and len(val) == 2) ]:
215 for val in retval:
216 print '%s: %s' % (val[0], val[1])
218 else:
219 for val in retval:
220 print val
221 else:
222 pprint(retval)
224 # return the value
225 return retval
