Package flumotion :: Package extern :: Package command :: Module command
[hide private]

Source Code for Module flumotion.extern.command.command

  1  # -*- Mode: Python; test-case-name: test_command -*- 
  2  # vi:si:et:sw=4:sts=4:ts=4 
  3   
  4  # This file is released under the standard PSF license. 
  5   
  6  """ 
  7  Command class. 
  8  """ 
  9   
 10  import optparse 
 11  import sys 
 12   
 13   
14 -class CommandHelpFormatter(optparse.IndentedHelpFormatter):
15 """ 16 I format the description as usual, but add an overview of commands 17 after it if there are any, formatted like the options. 18 """ 19 _commands = None 20
21 - def addCommand(self, name, description):
22 if self._commands is None: 23 self._commands = {} 24 self._commands[name] = description
25 26 ### override parent method 27
28 - def format_description(self, description):
29 30 # textwrap doesn't allow for a way to preserve double newlines 31 # to separate paragraphs, so we do it here. 32 blocks = description.split('\n\n') 33 rets = [] 34 35 for block in blocks: 36 rets.append(optparse.IndentedHelpFormatter.format_description(self, 37 block)) 38 ret = "\n".join(rets) 39 if self._commands: 40 commandDesc = [] 41 commandDesc.append("commands:") 42 keys = self._commands.keys() 43 keys.sort() 44 length = 0 45 for key in keys: 46 if len(key) > length: 47 length = len(key) 48 for name in keys: 49 formatString = " %-" + "%d" % length + "s %s" 50 commandDesc.append(formatString % (name, self._commands[name])) 51 ret += "\n" + "\n".join(commandDesc) + "\n" 52 return ret
53 54
55 -class CommandOptionParser(optparse.OptionParser):
56 """ 57 I parse options as usual, but I explicitly allow setting stdout 58 so that our print_help() method (invoked by default with -h/--help) 59 defaults to writing there. 60 61 I also override exit() so that I can be used in interactive shells. 62 63 @ivar help_printed: whether help was printed during parsing 64 @ivar usage_printed: whether usage was printed during parsing 65 """ 66 help_printed = False 67 usage_printed = False 68 69 _stdout = sys.stdout 70
71 - def set_stdout(self, stdout):
72 self._stdout = stdout
73
74 - def parse_args(self, args=None, values=None):
75 self.help_printed = False 76 self.usage_printed = False 77 return optparse.OptionParser.parse_args(self, args, values)
78 # we're overriding the built-in file, but we need to since this is 79 # the signature from the base class 80 __pychecker__ = 'no-shadowbuiltin' 81
82 - def print_help(self, file=None):
83 # we are overriding a parent method so we can't do anything about file 84 __pychecker__ = 'no-shadowbuiltin' 85 if file is None: 86 file = self._stdout 87 file.write(self.format_help()) 88 self.help_printed = True
89
90 - def print_usage(self, file=None):
91 optparse.OptionParser.print_usage(self, file) 92 self.usage_printed = True
93
94 - def exit(self, status=0, msg=None):
95 if msg: 96 sys.stderr.write(msg) 97 98 return status
99 100
101 -class Command:
102 """ 103 I am a class that handles a command for a program. 104 Commands can be nested underneath a command for further processing. 105 106 @cvar name: name of the command, lowercase; 107 defaults to the lowercase version of the class name 108 @cvar aliases: list of alternative lowercase names recognized 109 @type aliases: list of str 110 @cvar usage: short one-line usage string; 111 %command gets expanded to a sub-command or [commands] 112 as appropriate. Don't specify the command name itself, 113 it will be added automatically. If not set, defaults 114 to name. 115 @cvar summary: short one-line summary of the command 116 @cvar description: longer paragraph explaining the command 117 @cvar subCommands: dict of name -> commands below this command 118 @type subCommands: dict of str -> L{Command} 119 @cvar parser: the option parser used for parsing 120 @type parser: L{optparse.OptionParser} 121 """ 122 name = None 123 aliases = None 124 usage = None 125 summary = None 126 description = None 127 parentCommand = None 128 subCommands = None 129 subCommandClasses = None 130 aliasedSubCommands = None 131 parser = None 132
133 - def __init__(self, parentCommand=None, stdout=sys.stdout, 134 stderr=sys.stderr):
135 """ 136 Create a new command instance, with the given parent. 137 Allows for redirecting stdout and stderr if needed. 138 This redirection will be passed on to child commands. 139 """ 140 if not self.name: 141 self.name = str(self.__class__).split('.')[-1].lower() 142 self.stdout = stdout 143 self.stderr = stderr 144 self.parentCommand = parentCommand 145 146 # create subcommands if we have them 147 self.subCommands = {} 148 self.aliasedSubCommands = {} 149 if self.subCommandClasses: 150 for C in self.subCommandClasses: 151 c = C(self, stdout=stdout, stderr=stderr) 152 self.subCommands[c.name] = c 153 if c.aliases: 154 for alias in c.aliases: 155 self.aliasedSubCommands[alias] = c 156 157 # create our formatter and add subcommands if we have them 158 formatter = CommandHelpFormatter() 159 if self.subCommands: 160 for name, command in self.subCommands.items(): 161 formatter.addCommand(name, command.summary or 162 command.description) 163 164 # expand %command for the bottom usage 165 usage = self.usage or '' 166 if not usage: 167 # if no usage, but subcommands, then default to showing that 168 if self.subCommands: 169 usage = "%command" 170 171 # the main program name shouldn't get prepended, because %prog 172 # already expands to the name 173 if not usage.startswith('%prog'): 174 usage = self.name + ' ' + usage 175 176 if usage.find("%command") > -1: 177 usage = usage.split("%command")[0] + '[command]' 178 usages = [usage, ] 179 180 # FIXME: abstract this into getUsage that takes an optional 181 # parentCommand on where to stop recursing up 182 # useful for implementing subshells 183 184 # walk the tree up for our usage 185 c = self.parentCommand 186 while c: 187 usage = c.usage or c.name 188 if usage.find(" %command") > -1: 189 usage = usage.split(" %command")[0] 190 usages.append(usage) 191 c = c.parentCommand 192 usages.reverse() 193 usage = " ".join(usages) 194 195 # create our parser 196 description = self.description or self.summary 197 if description: 198 description = description.strip() 199 self.parser = CommandOptionParser( 200 usage=usage, description=description, 201 formatter=formatter) 202 self.parser.set_stdout(self.stdout) 203 self.parser.disable_interspersed_args() 204 205 # allow subclasses to add options 206 self.addOptions()
207
208 - def addOptions(self):
209 """ 210 Override me to add options to the parser. 211 """ 212 pass
213
214 - def do(self, args):
215 """ 216 Override me to implement the functionality of the command. 217 """ 218 pass
219
220 - def parse(self, argv):
221 """ 222 Parse the given arguments and act on them. 223 224 @param argv: list of arguments to parse 225 @type argv: list of str 226 227 @rtype: int 228 @returns: an exit code, or None if no actual action was taken. 229 """ 230 # note: no arguments should be passed as an empty list, not a list 231 # with an empty str as ''.split(' ') returns 232 self.options, args = self.parser.parse_args(argv) 233 self.debug('parse_args called') 234 235 # if we were asked to print help or usage, we are done 236 if self.parser.usage_printed or self.parser.help_printed: 237 return None 238 239 # FIXME: make handleOptions not take options, since we store it 240 # in self.options now 241 ret = self.handleOptions(self.options) 242 if ret: 243 return ret 244 245 # handle pleas for help 246 if args and args[0] == 'help': 247 self.debug('Asked for help, args %r' % args) 248 249 # give help on current command if only 'help' is passed 250 if len(args) == 1: 251 self.outputHelp() 252 return 0 253 254 # complain if we were asked for help on a subcommand, but we don't 255 # have any 256 if not self.subCommands: 257 self.stderr.write('No subcommands defined.') 258 self.parser.print_usage(file=self.stderr) 259 self.stderr.write( 260 "Use --help to get more information about this command.\n") 261 return 1 262 263 # rewrite the args the other way around; 264 # help doap becomes doap help so it gets deferred to the doap 265 # command 266 args = [args[1], args[0]] 267 268 # if we don't have subcommands, defer to our do() method 269 if not self.subCommands: 270 try: 271 ret = self.do(args) 272 except CommandOk, e: 273 ret = e.status 274 self.stdout.write(e.output + '\n') 275 except CommandExited, e: 276 ret = e.status 277 self.stderr.write(e.output + '\n') 278 279 # if everything's fine, we return 0 280 if not ret: 281 ret = 0 282 283 return ret 284 285 # if we do have subcommands, defer to them 286 try: 287 command = args[0] 288 except IndexError: 289 self.parser.print_usage(file=self.stderr) 290 self.stderr.write( 291 "Use --help to get a list of commands.\n") 292 return 1 293 294 if command in self.subCommands.keys(): 295 return self.subCommands[command].parse(args[1:]) 296 297 if self.aliasedSubCommands: 298 if command in self.aliasedSubCommands.keys(): 299 return self.aliasedSubCommands[command].parse(args[1:]) 300 301 self.stderr.write("Unknown command '%s'.\n" % command) 302 self.parser.print_usage(file=self.stderr) 303 return 1
304
305 - def handleOptions(self, options):
306 """ 307 Handle the parsed options. 308 """ 309 pass
310
311 - def outputHelp(self):
312 """ 313 Output help information. 314 """ 315 self.debug('outputHelp') 316 self.parser.print_help(file=self.stderr)
317
318 - def outputUsage(self):
319 """ 320 Output usage information. 321 Used when the options or arguments were missing or wrong. 322 """ 323 self.debug('outputUsage') 324 self.parser.print_usage(file=self.stderr)
325
326 - def getRootCommand(self):
327 """ 328 Return the top-level command, which is typically the program. 329 """ 330 c = self 331 while c.parentCommand: 332 c = c.parentCommand 333 return c
334
335 - def debug(self, format, *args):
336 """ 337 Override me to handle debug output from this class. 338 """ 339 pass
340 341
342 -class CommandExited(Exception):
343
344 - def __init__(self, status, output):
345 self.args = (status, output) 346 self.status = status 347 self.output = output
348 349
350 -class CommandOk(CommandExited):
351
352 - def __init__(self, output):
353 CommandExited.__init__(self, 0, output)
354 355
356 -class CommandError(CommandExited):
357
358 - def __init__(self, output):
359 CommandExited.__init__(self, 3, output)
360