Trees | Indices | Help |
---|
|
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 1315 """ 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 _aliases = None 21 26 31 32 ### override parent method 3372 7335 # textwrap doesn't allow for a way to preserve double newlines 36 # to separate paragraphs, so we do it here. 37 paragraphs = description.split('\n\n') 38 rets = [] 39 40 for paragraph in paragraphs: 41 # newlines starting with a space/dash are treated as a table, ie as 42 # is 43 lines = paragraph.split('\n -') 44 formatted = [] 45 for line in lines: 46 formatted.append( 47 optparse.IndentedHelpFormatter.format_description( 48 self, line)) 49 rets.append(" -".join(formatted)) 50 51 ret = "\n".join(rets) 52 53 # add aliases 54 if self._aliases: 55 ret += "\nAliases: " + ", ".join(self._aliases) + "\n" 56 57 # add subcommands 58 if self._commands: 59 commandDesc = [] 60 commandDesc.append("Commands:") 61 keys = self._commands.keys() 62 keys.sort() 63 length = 0 64 for key in keys: 65 if len(key) > length: 66 length = len(key) 67 for name in keys: 68 formatString = " %-" + "%d" % length + "s %s" 69 commandDesc.append(formatString % (name, self._commands[name])) 70 ret += "\n" + "\n".join(commandDesc) + "\n" 71 return ret75 """ 76 I parse options as usual, but I explicitly allow setting stdout 77 so that our print_help() method (invoked by default with -h/--help) 78 defaults to writing there. 79 80 I also override exit() so that I can be used in interactive shells. 81 82 @ivar help_printed: whether help was printed during parsing 83 @ivar usage_printed: whether usage was printed during parsing 84 """ 85 help_printed = False 86 usage_printed = False 87 88 _stdout = sys.stdout 89118 11991 self._stdout = stdout9294 self.help_printed = False 95 self.usage_printed = False 96 return optparse.OptionParser.parse_args(self, args, values)97 # we're overriding the built-in file, but we need to since this is 98 # the signature from the base class 99 __pychecker__ = 'no-shadowbuiltin' 100102 # we are overriding a parent method so we can't do anything about file 103 __pychecker__ = 'no-shadowbuiltin' 104 if file is None: 105 file = self._stdout 106 file.write(self.format_help()) 107 self.help_printed = True108 112121 """ 122 I am a class that handles a command for a program. 123 Commands can be nested underneath a command for further processing. 124 125 @cvar name: name of the command, lowercase; 126 defaults to the lowercase version of the class name 127 @cvar aliases: list of alternative lowercase names recognized 128 @type aliases: list of str 129 @cvar usage: short one-line usage string; 130 %command gets expanded to a sub-command or [commands] 131 as appropriate. Don't specify the command name itself, 132 it will be added automatically. If not set, defaults 133 to name. 134 @cvar summary: short one-line summary of the command 135 @cvar description: longer paragraph explaining the command 136 @cvar subCommands: dict of name -> commands below this command 137 @type subCommands: dict of str -> L{Command} 138 @cvar parser: the option parser used for parsing 139 @type parser: L{optparse.OptionParser} 140 """ 141 name = None 142 aliases = None 143 usage = None 144 summary = None 145 description = None 146 parentCommand = None 147 subCommands = None 148 subCommandClasses = None 149 aliasedSubCommands = None 150 parser = None 151389 390 397 398 403 404 409 410154 """ 155 Create a new command instance, with the given parent. 156 Allows for redirecting stdout and stderr if needed. 157 This redirection will be passed on to child commands. 158 """ 159 if not self.name: 160 self.name = str(self.__class__).split('.')[-1].lower() 161 self.stdout = stdout 162 self.stderr = stderr 163 self.parentCommand = parentCommand 164 165 # create subcommands if we have them 166 self.subCommands = {} 167 self.aliasedSubCommands = {} 168 if self.subCommandClasses: 169 for C in self.subCommandClasses: 170 c = C(self, stdout=stdout, stderr=stderr, width=width) 171 self.subCommands[c.name] = c 172 if c.aliases: 173 for alias in c.aliases: 174 self.aliasedSubCommands[alias] = c 175 176 # create our formatter and add subcommands if we have them 177 formatter = CommandHelpFormatter(width=width) 178 if self.subCommands: 179 for name, command in self.subCommands.items(): 180 formatter.addCommand(name, command.summary or 181 command.description) 182 183 if self.aliases: 184 for alias in self.aliases: 185 formatter.addAlias(alias) 186 187 # expand %command for the bottom usage 188 usage = self.usage or '' 189 if not usage: 190 # if no usage, but subcommands, then default to showing that 191 if self.subCommands: 192 usage = "%command" 193 194 # the main program name shouldn't get prepended, because %prog 195 # already expands to the name 196 if not usage.startswith('%prog'): 197 usage = self.name + ' ' + usage 198 199 if usage.find("%command") > -1: 200 usage = usage.split("%command")[0] + '[command]' 201 usages = [usage, ] 202 203 # FIXME: abstract this into getUsage that takes an optional 204 # parentCommand on where to stop recursing up 205 # useful for implementing subshells 206 207 # walk the tree up for our usage 208 c = self.parentCommand 209 while c: 210 usage = c.usage or c.name 211 if usage.find(" %command") > -1: 212 usage = usage.split(" %command")[0] 213 usages.append(usage) 214 c = c.parentCommand 215 usages.reverse() 216 usage = " ".join(usages) 217 218 # create our parser 219 description = self.description or self.summary 220 if description: 221 description = description.strip() 222 self.parser = CommandOptionParser( 223 usage=usage, description=description, 224 formatter=formatter) 225 self.parser.set_stdout(self.stdout) 226 self.parser.disable_interspersed_args() 227 228 # allow subclasses to add options 229 self.addOptions()230 236238 """ 239 Override me to implement the functionality of the command. 240 """ 241 raise NotImplementedError('Implement %s.do()' % self.__class__) 242 # by default, return 1 and hopefully show help 243 return 1244246 """ 247 Parse the given arguments and act on them. 248 249 @param argv: list of arguments to parse 250 @type argv: list of str 251 252 @rtype: int 253 @returns: an exit code, or None if no actual action was taken. 254 """ 255 # note: no arguments should be passed as an empty list, not a list 256 # with an empty str as ''.split(' ') returns 257 self.debug('calling parse_args') 258 self.options, args = self.parser.parse_args(argv) 259 self.debug('called parse_args') 260 261 # if we were asked to print help or usage, we are done 262 if self.parser.usage_printed or self.parser.help_printed: 263 return None 264 265 # FIXME: make handleOptions not take options, since we store it 266 # in self.options now 267 ret = self.handleOptions(self.options) 268 if ret: 269 return ret 270 271 # handle pleas for help 272 if args and args[0] == 'help': 273 self.debug('Asked for help, args %r' % args) 274 275 # give help on current command if only 'help' is passed 276 if len(args) == 1: 277 self.outputHelp() 278 return 0 279 280 # complain if we were asked for help on a subcommand, but we don't 281 # have any 282 if not self.subCommands: 283 self.stderr.write('No subcommands defined.') 284 self.parser.print_usage(file=self.stderr) 285 self.stderr.write( 286 "Use --help to get more information about this command.\n") 287 return 1 288 289 # rewrite the args the other way around; 290 # help doap becomes doap help so it gets deferred to the doap 291 # command 292 args = [args[1], args[0]] 293 294 # if we don't have args or don't have subcommands, 295 # defer to our do() method 296 # allows implementing a do() for commands that also have subcommands 297 if not args or not self.subCommands: 298 self.debug('no args or no subcommands, doing') 299 try: 300 ret = self.do(args) 301 except CommandOk, e: 302 ret = e.status 303 self.stdout.write(e.output + '\n') 304 except CommandExited, e: 305 ret = e.status 306 self.stderr.write(e.output + '\n') 307 except NotImplementedError: 308 self.parser.print_usage(file=self.stderr) 309 self.stderr.write( 310 "Use --help to get a list of commands.\n") 311 return 1 312 313 # if everything's fine, we return 0 314 if not ret: 315 ret = 0 316 317 return ret 318 319 # if we do have subcommands, defer to them 320 try: 321 command = args[0] 322 except IndexError: 323 self.parser.print_usage(file=self.stderr) 324 self.stderr.write( 325 "Use --help to get a list of commands.\n") 326 return 1 327 328 if command in self.subCommands.keys(): 329 return self.subCommands[command].parse(args[1:]) 330 331 if self.aliasedSubCommands: 332 if command in self.aliasedSubCommands.keys(): 333 return self.aliasedSubCommands[command].parse(args[1:]) 334 335 self.stderr.write("Unknown command '%s'.\n" % command) 336 self.parser.print_usage(file=self.stderr) 337 return 1338 344346 """ 347 Output help information. 348 """ 349 __pychecker__ = 'no-shadowbuiltin' 350 self.debug('outputHelp') 351 if not file: 352 file = self.stderr 353 self.parser.print_help(file=file)354356 """ 357 Output usage information. 358 Used when the options or arguments were missing or wrong. 359 """ 360 __pychecker__ = 'no-shadowbuiltin' 361 self.debug('outputUsage') 362 if not file: 363 file = self.stderr 364 self.parser.print_usage(file=file)365367 """ 368 Return the top-level command, which is typically the program. 369 """ 370 c = self 371 while c.parentCommand: 372 c = c.parentCommand 373 return c374 380412 """ 413 Take a Command instance and create a L{cmd.Cmd} class from it that 414 implements a command line interpreter. 415 416 Example use in a command: 417 418 >>> def do(self, args): 419 ... cmd = command.commandToCmd(self) 420 ... cmd.prompt = 'prompt> ' 421 ... while not cmd.exited: 422 ... cmd.cmdloop() 423 """ 424 import cmd 425 426 # internal class to subclass cmd.Cmd with a Ctrl-D handler 427 428 class CommandCmd(cmd.Cmd): 429 prompt = '(command) ' 430 exited = False 431 432 def do_EOF(self, args): 433 self.stdout.write('\n') 434 self.exited = True 435 sys.exit(0)436 437 def do_exit(self, args): 438 self.exited = True 439 sys.exit(0) 440 441 def help_EOF(self): 442 print 'Exit.' 443 444 def help_exit(self): 445 print 'Exit.' 446 447 # populate the Cmd interpreter from our command class 448 cmdClass = CommandCmd 449 450 for name, command in command.subCommands.items() \ 451 + command.aliasedSubCommands.items(): 452 if name == 'shell': 453 continue 454 command.debug('Adding shell command %s for %r' % (name, command)) 455 456 # add do command 457 methodName = 'do_' + name 458 459 def generateDo(command): 460 461 def do_(s, line): 462 # the do_ method is passed a single argument consisting of 463 # the remainder of the line 464 args = line.split(' ') 465 command.debug('Asking %r to parse %r' % (command, args)) 466 command.parse(args) 467 return do_ 468 469 method = generateDo(command) 470 setattr(cmdClass, methodName, method) 471 472 473 # add help command 474 methodName = 'help_' + name 475 476 def generateHelp(command): 477 478 def help_(s): 479 command.parser.print_help(file=command.stdout) 480 return help_ 481 482 method = generateHelp(command) 483 setattr(cmdClass, methodName, method) 484 485 return cmdClass() 486
Trees | Indices | Help |
---|
Generated by Epydoc 3.0.1 on Thu Dec 8 09:51:22 2011 | http://epydoc.sourceforge.net |