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