Package qm :: Module cmdline
[hide private]
[frames] | no frames]

Source Code for Module qm.cmdline

  1  ######################################################################## 
  2  # 
  3  # File:   cmdline.py 
  4  # Author: Benjamin Chelf 
  5  # Date:   2001-01-09 
  6  # 
  7  # Contents: 
  8  #   Code for command line interface. 
  9  # 
 10  # Copyright (c) 2001, 2002 by CodeSourcery, LLC.  All rights reserved.  
 11  # 
 12  # For license terms see the file COPYING. 
 13  # 
 14  ######################################################################## 
 15   
 16  ######################################################################## 
 17  #  Using the Command Parser 
 18  # 
 19  #  The command parser can be used by giving a list of options and 
 20  #  commands to be parsed. See the constructor below for the exact 
 21  #  structure of those things. You can then use the parser to 1) generate 
 22  #  help strings for the general program, 2) generate help strings for 
 23  #  specific commands, and 3) parse command lines to split up which 
 24  #  options were passed, which command was given, and the arguments and 
 25  #  options to that command that were specified. 
 26  # 
 27  ######################################################################## 
 28   
 29  ######################################################################## 
 30  # imports 
 31  ######################################################################## 
 32   
 33  import copy 
 34  import getopt 
 35  import qm 
 36  import string 
 37  import structured_text 
 38  import sys 
 39   
 40  ######################################################################## 
 41  # classes 
 42  ######################################################################## 
 43   
44 -class CommandError(qm.UserError):
45 46 pass
47 48 49
50 -class CommandParser:
51 """Class for the functionality that parses the command line. 52 53 The command parser is used to easily specify a list of command line 54 options and commands to be parsed from an argument list.""" 55
56 - def __init__(self, name, options, commands, conflicting_options=()):
57 """Create a new command parser. 58 59 'name' -- The name of the executable that we are currently 60 using. This will normally be argv[0]. 61 62 'options' -- A list of 4-tuples specifying options that you wish 63 this parser to accept. The 4-tuple has the following form: 64 (short_form, long_form, options, description). 'short_form' 65 must be exactly one character. 'long_form' must be specified 66 for every option in the list. 'arg_name' is a string 67 representing the name of the argument that is passed to this 68 option. If it is 'None,' then this option doesn't take an 69 argument. 'description' is a string describing the option. 70 71 'commands' -- A list of 5-tuples specifying commands to be 72 accepted after the command line options. The 5-tuple has the 73 form '(name, short_description, args_string, long_description, 74 options)'. 75 76 'name' -- The string for the command. 77 78 'short_description' -- A short description of the command to 79 be printed out in general help. 80 81 'args_string' -- The string that will be printed after the 82 command in the command specific help. 83 84 'long_description' -- The long description to be printed out 85 in the command specfic help. 86 87 'options' -- A list of 4-tuples of the same form as the 88 'options' described above. 89 90 'conflicting_options' -- A sequence of sets of conflicting 91 options. Each element is a sequence of option specifiers in the 92 same form as 'options', above.""" 93 94 self.__name = name 95 96 # Check that the options are ok. 97 self.CheckOptions(options) 98 self.__options = copy.deepcopy(options) 99 100 self.__option_to_long = {} 101 for option in self.__options: 102 if option[0]: 103 # Check for duplicate short options. 104 assert not self.__option_to_long.has_key('-' + option[0]) 105 self.__option_to_long['-' + option[0]] = option[1] 106 # Check for duplicate long options. 107 assert not self.__option_to_long.has_key('--' + option[1]) 108 self.__option_to_long['--' + option[1]] = option[1] 109 110 # Check that the options for each command are ok. 111 for command in commands: 112 self.CheckOptions(command[4]) 113 114 self.__commands = copy.deepcopy(commands) 115 for i in range(0, len(self.__commands)): 116 command = self.__commands[i] 117 map = {} 118 for option in command[4]: 119 if option[0] is not None: 120 # Check for duplicate short options. 121 if map.has_key('-' + option[0]): 122 raise ValueError, \ 123 "duplicate short command option -%s" \ 124 % option[0] 125 map['-' + option[0]] = option[1] 126 # Check for duplicate long options. 127 if map.has_key('--' + option[1]): 128 raise ValueError, \ 129 "duplicate long command option --%s" % option[1] 130 map['--' + option[1]] = option[1] 131 command = command + (map,) 132 self.__commands[i] = command 133 134 # Build the options string for getopt. 135 self.__getopt_options = self.BuildGetoptString(self.__options) 136 137 # Check that all options in the conflicting options set are 138 # included somewhere. 139 for conflict_set in conflicting_options: 140 # Check each option in each set. 141 for option_spec in conflict_set: 142 found = 0 143 # Check in the global options. 144 if option_spec in options: 145 found = 1 146 break 147 if not found: 148 # Check in the command options for each command. 149 for command in commands: 150 if option in command[4]: 151 found = 1 152 break 153 if not found: 154 # This option spec wasn't found anywhere. 155 raise ValueError, \ 156 "unknown option --%s in conflict set", option[1] 157 # Store for later. 158 self.__conflicting_options = conflicting_options
159 160
161 - def CheckOptions(self, options):
162 """Check that a list of options 4-tuples is correct. 163 164 'options' -- A list of 4-tuples as described above. 165 166 returns -- 1 if the options are all valid, 0 otherwise.""" 167 168 for short_option, long_option, options, descripton in options: 169 # The short form of the option must have exactly 1 character. 170 if short_option != None and len(short_option) != 1: 171 raise ValueError, "short option must have exactly 1 character" 172 # The long form of the option must be specified. 173 if long_option == None or len(long_option) == 0: 174 raise ValueError, \ 175 "long option must be specified for -%s" % short_option 176 177 return 1
178 179
180 - def BuildGetoptList(self, options):
181 """Build a getopt list for the long options. 182 183 'options' -- A list of 4-tuples as described above. 184 185 returns -- A list to be passed to getopt to parse long options.""" 186 187 # Build the options string for getopt. 188 getopt_list = [] 189 190 for option in options: 191 # Tell getopt that this option takes an argument. 192 if option[2] != None: 193 getopt_list.append(option[1] + '=') 194 else: 195 getopt_list.append(option[1]) 196 197 return getopt_list
198 199
200 - def BuildGetoptString(self, options):
201 """Build a getopt string for the options passed in. 202 203 'options' -- A list of 4-tuples as described above. 204 205 returns -- A string to be passed to getopt to parse the 206 options.""" 207 208 # Build the options string for getopt. 209 getopt_string = '' 210 211 for option in options: 212 if option[0] is not None: 213 getopt_string = getopt_string + option[0] 214 # Tell getopt that this option takes an argument. 215 if option[2] != None: 216 getopt_string = getopt_string + ':' 217 218 return getopt_string
219 220
221 - def GetOptionsHelp(self, options):
222 """Return a string that is the basic help for options. 223 224 options -- A list of options to get the help string for. 225 226 returns -- A string to be printed for the options.""" 227 228 help_string = "" 229 230 # Print out the short form, long form, and then the description. 231 for option in options: 232 # Format the short form, if there is one. 233 if option[0] is None: 234 short_form = " " 235 else: 236 short_form = "-%s," % option[0] 237 # Format the long form. Include the option arugment, if 238 # there is one. 239 if option[2] is None: 240 long_form = "--%-24s" % option[1] 241 else: 242 long_form = "--%-24s" % (option[1] + " " + option[2]) 243 # Generate a line for this option. 244 help_string = help_string \ 245 + " %s %s: %s\n" \ 246 % (short_form, long_form, option[3]) 247 248 return help_string
249 250
251 - def GetBasicHelp(self):
252 """Return a string that is the basic help for the commands. 253 254 returns -- A string to be printed with basic functionality of 255 arguments and commands.""" 256 257 help_string = "Usage: %s " % self.__name 258 help_string = help_string + "[ OPTION... ] COMMAND " \ 259 "[ COMMAND-OPTION... ] [ ARGUMENT... ]\n\n" 260 help_string = help_string + "Options:\n" 261 help_string = help_string + self.GetOptionsHelp(self.__options) 262 help_string = help_string + "\nCommands:\n" 263 # Print out the commands and their short descriptions. 264 for command in self.__commands: 265 help_add = "%-30s: %s"%(command[0], command[1]) 266 help_string = help_string + " %s\n"%(help_add) 267 help_string = help_string \ 268 + "\nInvoke\n %s COMMAND --help\n" \ 269 "for information about " \ 270 "COMMAND-OPTIONS and ARGUMENTS.\n\n" % self.__name 271 272 return help_string
273 274
275 - def GetCommandHelp(self, command):
276 """Return a string that is the help for a specific command. 277 278 command -- A string of the command that you want help for. 279 280 returns -- A string of help for a given command.""" 281 282 help_string = "Usage: %s %s [ OPTIONS ] "%(self.__name, command) 283 for command_item in self.__commands: 284 if command_item[0] == command: 285 help_string = help_string + command_item[2] + "\n\n" 286 help_string = help_string + "Options:\n" 287 help_string = help_string \ 288 + self.GetOptionsHelp(command_item[4]) 289 help_string = help_string + "\n" 290 help_string = help_string \ 291 + structured_text.to_text(command_item[3]) 292 return help_string 293 294 return "Command not found"
295 296
297 - def ParseCommandLine(self, argv):
298 """Parse a command line. 299 300 'argv' -- A string containing the command line starting with 301 argv[1]. It should not contain the name of the executed program. 302 303 returns -- A 4-tuple of the options given, the command given, 304 the command options, and the command arguments. Its form is 305 this: (options, command, command_options, command_args). 306 'options' is a list of 2-tuples indicating each option specified 307 and the argument given to that option (if applicable). 308 'command' is the command given. 'command_options' is a list of 309 2-tuples indicating each option given to the command and its 310 possible argument. 'command_args' is a list of arguments as 311 given to the command. If no command is given, then the function 312 will return '' for the command, [] for the arguments, and [] for 313 the command options. 314 315 raises -- 'CommandError' if the command is invalid.""" 316 317 # Get the options off of the front of the command line. 318 getopt_list = self.BuildGetoptList(self.__options) 319 320 try: 321 options, args = getopt.getopt(argv, self.__getopt_options, 322 getopt_list) 323 except getopt.error, msg: 324 raise CommandError, msg 325 326 for i in range(0, len(options)): 327 option = options[i] 328 new_option = (self.__option_to_long[option[0]], option[1]) 329 options[i] = new_option 330 331 # Did not specify anything on the command line except options. 332 if args == []: 333 return (options, '', [], []) 334 335 # Get the command. 336 command = args[0] 337 338 # This checks to make sure the command they specified is actually 339 # a command that we know. Checking this now saves trouble 340 # in having to do it later. 341 found = 0 342 for command_item in self.__commands: 343 if command == command_item[0]: 344 found = 1 345 346 if found == 0: 347 # The command they specified does not exist; print out the 348 # help and raise an exception. 349 raise CommandError, \ 350 qm.error("unrecognized command", command=command) 351 352 # Get the arguments to the command. 353 command_options = [] 354 355 for command_item in self.__commands: 356 if command_item[0] == command: 357 command_options = command_item[4] 358 break 359 getopt_string = self.BuildGetoptString(command_options) 360 getopt_list = self.BuildGetoptList(command_options) 361 try: 362 command_options, command_args = getopt.getopt(args[1:], 363 getopt_string, 364 getopt_list) 365 except getopt.error, msg: 366 raise CommandError, "%s: %s" % (command, msg) 367 368 for i in range(0, len(command_options)): 369 option = command_options[i] 370 new_option = (command_item[5][option[0]], option[1]) 371 command_options[i] = new_option 372 373 # Check for mutually exclusive options. First generate a set of 374 # all the options that were specified, both global options and 375 # command options. 376 all_options = map(lambda option: option[0], 377 options + command_options) 378 # Loop over sets of conflicting options. 379 for conflict_set in self.__conflicting_options: 380 # Generate sequence of names of the conflicting options. 381 conflict_names = map(lambda opt_spec: opt_spec[1], conflict_set) 382 # Filter out options that were specified that aren't in the 383 # set of conflicting options. 384 conflict_filter = lambda option, conflict_names=conflict_names: \ 385 option in conflict_names and option 386 matches = filter(conflict_filter, all_options) 387 # Was more than one option from the conflicting set specified? 388 if len(matches) > 1: 389 # Yes; that's a user error. 390 raise qm.cmdline.CommandError, \ 391 qm.error("conflicting options", 392 option1=matches[0], 393 option2=matches[1]) 394 395 return (options, command, command_options, command_args)
396