Package moap :: Package extern :: Package command :: Module command
[hide private]
[frames] | no frames]

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