1#!/usr/bin/env python 2# Copyright (c) 2011 The Chromium Authors. All rights reserved. 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6"""Parse a command line, retrieving a command and its arguments. 7 8Supports the concept of command line commands, each with its own set 9of arguments. Supports dependent arguments and mutually exclusive arguments. 10Basically, a better optparse. I took heed of epg's WHINE() in gvn.cmdline 11and dumped optparse in favor of something better. 12""" 13 14import os.path 15import re 16import string 17import sys 18import textwrap 19import types 20 21 22def IsString(var): 23 """Little helper function to see if a variable is a string.""" 24 return type(var) in types.StringTypes 25 26 27class ParseError(Exception): 28 """Encapsulates errors from parsing, string arg is description.""" 29 pass 30 31 32class Command(object): 33 """Implements a single command.""" 34 35 def __init__(self, names, helptext, validator=None, impl=None): 36 """Initializes Command from names and helptext, plus optional callables. 37 38 Args: 39 names: command name, or list of synonyms 40 helptext: brief string description of the command 41 validator: callable for custom argument validation 42 Should raise ParseError if it wants 43 impl: callable to be invoked when command is called 44 """ 45 self.names = names 46 self.validator = validator 47 self.helptext = helptext 48 self.impl = impl 49 self.args = [] 50 self.required_groups = [] 51 self.arg_dict = {} 52 self.positional_args = [] 53 self.cmdline = None 54 55 class Argument(object): 56 """Encapsulates an argument to a command.""" 57 VALID_TYPES = ['string', 'readfile', 'int', 'flag', 'coords'] 58 TYPES_WITH_VALUES = ['string', 'readfile', 'int', 'coords'] 59 60 def __init__(self, names, helptext, type, metaname, 61 required, default, positional): 62 """Command-line argument to a command. 63 64 Args: 65 names: argument name, or list of synonyms 66 helptext: brief description of the argument 67 type: type of the argument. Valid values include: 68 string - a string 69 readfile - a file which must exist and be available 70 for reading 71 int - an integer 72 flag - an optional flag (bool) 73 coords - (x,y) where x and y are ints 74 metaname: Name to display for value in help, inferred if not 75 specified 76 required: True if argument must be specified 77 default: Default value if not specified 78 positional: Argument specified by location, not name 79 80 Raises: 81 ValueError: the argument name is invalid for some reason 82 """ 83 if type not in Command.Argument.VALID_TYPES: 84 raise ValueError("Invalid type: %r" % type) 85 86 if required and default is not None: 87 raise ValueError("required and default are mutually exclusive") 88 89 if required and type == 'flag': 90 raise ValueError("A required flag? Give me a break.") 91 92 if metaname and type not in Command.Argument.TYPES_WITH_VALUES: 93 raise ValueError("Type %r can't have a metaname" % type) 94 95 # If no metaname is provided, infer it: use the alphabetical characters 96 # of the last provided name 97 if not metaname and type in Command.Argument.TYPES_WITH_VALUES: 98 metaname = ( 99 names[-1].lstrip(string.punctuation + string.whitespace).upper()) 100 101 self.names = names 102 self.helptext = helptext 103 self.type = type 104 self.required = required 105 self.default = default 106 self.positional = positional 107 self.metaname = metaname 108 109 self.mutex = [] # arguments that are mutually exclusive with 110 # this one 111 self.depends = [] # arguments that must be present for this 112 # one to be valid 113 self.present = False # has this argument been specified? 114 115 def AddDependency(self, arg): 116 """Makes this argument dependent on another argument. 117 118 Args: 119 arg: name of the argument this one depends on 120 """ 121 if arg not in self.depends: 122 self.depends.append(arg) 123 124 def AddMutualExclusion(self, arg): 125 """Makes this argument invalid if another is specified. 126 127 Args: 128 arg: name of the mutually exclusive argument. 129 """ 130 if arg not in self.mutex: 131 self.mutex.append(arg) 132 133 def GetUsageString(self): 134 """Returns a brief string describing the argument's usage.""" 135 if not self.positional: 136 string = self.names[0] 137 if self.type in Command.Argument.TYPES_WITH_VALUES: 138 string += "="+self.metaname 139 else: 140 string = self.metaname 141 142 if not self.required: 143 string = "["+string+"]" 144 145 return string 146 147 def GetNames(self): 148 """Returns a string containing a list of the arg's names.""" 149 if self.positional: 150 return self.metaname 151 else: 152 return ", ".join(self.names) 153 154 def GetHelpString(self, width=80, indent=5, names_width=20, gutter=2): 155 """Returns a help string including help for all the arguments.""" 156 names = [" "*indent + line +" "*(names_width-len(line)) for line in 157 textwrap.wrap(self.GetNames(), names_width)] 158 159 helpstring = textwrap.wrap(self.helptext, width-indent-names_width-gutter) 160 161 if len(names) < len(helpstring): 162 names += [" "*(indent+names_width)]*(len(helpstring)-len(names)) 163 164 if len(helpstring) < len(names): 165 helpstring += [""]*(len(names)-len(helpstring)) 166 167 return "\n".join([name_line + " "*gutter + help_line for 168 name_line, help_line in zip(names, helpstring)]) 169 170 def __repr__(self): 171 if self.present: 172 string = '= %r' % self.value 173 else: 174 string = "(absent)" 175 176 return "Argument %s '%s'%s" % (self.type, self.names[0], string) 177 178 # end of nested class Argument 179 180 def AddArgument(self, names, helptext, type="string", metaname=None, 181 required=False, default=None, positional=False): 182 """Command-line argument to a command. 183 184 Args: 185 names: argument name, or list of synonyms 186 helptext: brief description of the argument 187 type: type of the argument 188 metaname: Name to display for value in help, inferred if not 189 required: True if argument must be specified 190 default: Default value if not specified 191 positional: Argument specified by location, not name 192 193 Raises: 194 ValueError: the argument already exists or is invalid 195 196 Returns: 197 The newly-created argument 198 """ 199 if IsString(names): names = [names] 200 201 names = [name.lower() for name in names] 202 203 for name in names: 204 if name in self.arg_dict: 205 raise ValueError("%s is already an argument"%name) 206 207 if (positional and required and 208 [arg for arg in self.args if arg.positional] and 209 not [arg for arg in self.args if arg.positional][-1].required): 210 raise ValueError( 211 "A required positional argument may not follow an optional one.") 212 213 arg = Command.Argument(names, helptext, type, metaname, 214 required, default, positional) 215 216 self.args.append(arg) 217 218 for name in names: 219 self.arg_dict[name] = arg 220 221 return arg 222 223 def GetArgument(self, name): 224 """Return an argument from a name.""" 225 return self.arg_dict[name.lower()] 226 227 def AddMutualExclusion(self, args): 228 """Specifies that a list of arguments are mutually exclusive.""" 229 if len(args) < 2: 230 raise ValueError("At least two arguments must be specified.") 231 232 args = [arg.lower() for arg in args] 233 234 for index in xrange(len(args)-1): 235 for index2 in xrange(index+1, len(args)): 236 self.arg_dict[args[index]].AddMutualExclusion(self.arg_dict[args[index2]]) 237 238 def AddDependency(self, dependent, depends_on): 239 """Specifies that one argument may only be present if another is. 240 241 Args: 242 dependent: the name of the dependent argument 243 depends_on: the name of the argument on which it depends 244 """ 245 self.arg_dict[dependent.lower()].AddDependency( 246 self.arg_dict[depends_on.lower()]) 247 248 def AddMutualDependency(self, args): 249 """Specifies that a list of arguments are all mutually dependent.""" 250 if len(args) < 2: 251 raise ValueError("At least two arguments must be specified.") 252 253 args = [arg.lower() for arg in args] 254 255 for (arg1, arg2) in [(arg1, arg2) for arg1 in args for arg2 in args]: 256 if arg1 == arg2: continue 257 self.arg_dict[arg1].AddDependency(self.arg_dict[arg2]) 258 259 def AddRequiredGroup(self, args): 260 """Specifies that at least one of the named arguments must be present.""" 261 if len(args) < 2: 262 raise ValueError("At least two arguments must be in a required group.") 263 264 args = [self.arg_dict[arg.lower()] for arg in args] 265 266 self.required_groups.append(args) 267 268 def ParseArguments(self): 269 """Given a command line, parse and validate the arguments.""" 270 271 # reset all the arguments before we parse 272 for arg in self.args: 273 arg.present = False 274 arg.value = None 275 276 self.parse_errors = [] 277 278 # look for arguments remaining on the command line 279 while len(self.cmdline.rargs): 280 try: 281 self.ParseNextArgument() 282 except ParseError, e: 283 self.parse_errors.append(e.args[0]) 284 285 # after all the arguments are parsed, check for problems 286 for arg in self.args: 287 if not arg.present and arg.required: 288 self.parse_errors.append("'%s': required parameter was missing" 289 % arg.names[0]) 290 291 if not arg.present and arg.default: 292 arg.present = True 293 arg.value = arg.default 294 295 if arg.present: 296 for mutex in arg.mutex: 297 if mutex.present: 298 self.parse_errors.append( 299 "'%s', '%s': arguments are mutually exclusive" % 300 (arg.argstr, mutex.argstr)) 301 302 for depend in arg.depends: 303 if not depend.present: 304 self.parse_errors.append("'%s': '%s' must be specified as well" % 305 (arg.argstr, depend.names[0])) 306 307 # check for required groups 308 for group in self.required_groups: 309 if not [arg for arg in group if arg.present]: 310 self.parse_errors.append("%s: at least one must be present" % 311 (", ".join(["'%s'" % arg.names[-1] for arg in group]))) 312 313 # if we have any validators, invoke them 314 if not self.parse_errors and self.validator: 315 try: 316 self.validator(self) 317 except ParseError, e: 318 self.parse_errors.append(e.args[0]) 319 320 # Helper methods so you can treat the command like a dict 321 def __getitem__(self, key): 322 arg = self.arg_dict[key.lower()] 323 324 if arg.type == 'flag': 325 return arg.present 326 else: 327 return arg.value 328 329 def __iter__(self): 330 return [arg for arg in self.args if arg.present].__iter__() 331 332 def ArgumentPresent(self, key): 333 """Tests if an argument exists and has been specified.""" 334 return key.lower() in self.arg_dict and self.arg_dict[key.lower()].present 335 336 def __contains__(self, key): 337 return self.ArgumentPresent(key) 338 339 def ParseNextArgument(self): 340 """Find the next argument in the command line and parse it.""" 341 arg = None 342 value = None 343 argstr = self.cmdline.rargs.pop(0) 344 345 # First check: is this a literal argument? 346 if argstr.lower() in self.arg_dict: 347 arg = self.arg_dict[argstr.lower()] 348 if arg.type in Command.Argument.TYPES_WITH_VALUES: 349 if len(self.cmdline.rargs): 350 value = self.cmdline.rargs.pop(0) 351 352 # Second check: is this of the form "arg=val" or "arg:val"? 353 if arg is None: 354 delimiter_pos = -1 355 356 for delimiter in [':', '=']: 357 pos = argstr.find(delimiter) 358 if pos >= 0: 359 if delimiter_pos < 0 or pos < delimiter_pos: 360 delimiter_pos = pos 361 362 if delimiter_pos >= 0: 363 testarg = argstr[:delimiter_pos] 364 testval = argstr[delimiter_pos+1:] 365 366 if testarg.lower() in self.arg_dict: 367 arg = self.arg_dict[testarg.lower()] 368 argstr = testarg 369 value = testval 370 371 # Third check: does this begin an argument? 372 if arg is None: 373 for key in self.arg_dict.iterkeys(): 374 if (len(key) < len(argstr) and 375 self.arg_dict[key].type in Command.Argument.TYPES_WITH_VALUES and 376 argstr[:len(key)].lower() == key): 377 value = argstr[len(key):] 378 argstr = argstr[:len(key)] 379 arg = self.arg_dict[argstr] 380 381 # Fourth check: do we have any positional arguments available? 382 if arg is None: 383 for positional_arg in [ 384 testarg for testarg in self.args if testarg.positional]: 385 if not positional_arg.present: 386 arg = positional_arg 387 value = argstr 388 argstr = positional_arg.names[0] 389 break 390 391 # Push the retrieved argument/value onto the largs stack 392 if argstr: self.cmdline.largs.append(argstr) 393 if value: self.cmdline.largs.append(value) 394 395 # If we've made it this far and haven't found an arg, give up 396 if arg is None: 397 raise ParseError("Unknown argument: '%s'" % argstr) 398 399 # Convert the value, if necessary 400 if arg.type in Command.Argument.TYPES_WITH_VALUES and value is None: 401 raise ParseError("Argument '%s' requires a value" % argstr) 402 403 if value is not None: 404 value = self.StringToValue(value, arg.type, argstr) 405 406 arg.argstr = argstr 407 arg.value = value 408 arg.present = True 409 410 # end method ParseNextArgument 411 412 def StringToValue(self, value, type, argstr): 413 """Convert a string from the command line to a value type.""" 414 try: 415 if type == 'string': 416 pass # leave it be 417 418 elif type == 'int': 419 try: 420 value = int(value) 421 except ValueError: 422 raise ParseError 423 424 elif type == 'readfile': 425 if not os.path.isfile(value): 426 raise ParseError("'%s': '%s' does not exist" % (argstr, value)) 427 428 elif type == 'coords': 429 try: 430 value = [int(val) for val in 431 re.match("\(\s*(\d+)\s*\,\s*(\d+)\s*\)\s*\Z", value). 432 groups()] 433 except AttributeError: 434 raise ParseError 435 436 else: 437 raise ValueError("Unknown type: '%s'" % type) 438 439 except ParseError, e: 440 # The bare exception is raised in the generic case; more specific errors 441 # will arrive with arguments and should just be reraised 442 if not e.args: 443 e = ParseError("'%s': unable to convert '%s' to type '%s'" % 444 (argstr, value, type)) 445 raise e 446 447 return value 448 449 def SortArgs(self): 450 """Returns a method that can be passed to sort() to sort arguments.""" 451 452 def ArgSorter(arg1, arg2): 453 """Helper for sorting arguments in the usage string. 454 455 Positional arguments come first, then required arguments, 456 then optional arguments. Pylint demands this trivial function 457 have both Args: and Returns: sections, sigh. 458 459 Args: 460 arg1: the first argument to compare 461 arg2: the second argument to compare 462 463 Returns: 464 -1 if arg1 should be sorted first, +1 if it should be sorted second, 465 and 0 if arg1 and arg2 have the same sort level. 466 """ 467 return ((arg2.positional-arg1.positional)*2 + 468 (arg2.required-arg1.required)) 469 return ArgSorter 470 471 def GetUsageString(self, width=80, name=None): 472 """Gets a string describing how the command is used.""" 473 if name is None: name = self.names[0] 474 475 initial_indent = "Usage: %s %s " % (self.cmdline.prog, name) 476 subsequent_indent = " " * len(initial_indent) 477 478 sorted_args = self.args[:] 479 sorted_args.sort(self.SortArgs()) 480 481 return textwrap.fill( 482 " ".join([arg.GetUsageString() for arg in sorted_args]), width, 483 initial_indent=initial_indent, 484 subsequent_indent=subsequent_indent) 485 486 def GetHelpString(self, width=80): 487 """Returns a list of help strings for all this command's arguments.""" 488 sorted_args = self.args[:] 489 sorted_args.sort(self.SortArgs()) 490 491 return "\n".join([arg.GetHelpString(width) for arg in sorted_args]) 492 493 # end class Command 494 495 496class CommandLine(object): 497 """Parse a command line, extracting a command and its arguments.""" 498 499 def __init__(self): 500 self.commands = [] 501 self.cmd_dict = {} 502 503 # Add the help command to the parser 504 help_cmd = self.AddCommand(["help", "--help", "-?", "-h"], 505 "Displays help text for a command", 506 ValidateHelpCommand, 507 DoHelpCommand) 508 509 help_cmd.AddArgument( 510 "command", "Command to retrieve help for", positional=True) 511 help_cmd.AddArgument( 512 "--width", "Width of the output", type='int', default=80) 513 514 self.Exit = sys.exit # override this if you don't want the script to halt 515 # on error or on display of help 516 517 self.out = sys.stdout # override these if you want to redirect 518 self.err = sys.stderr # output or error messages 519 520 def AddCommand(self, names, helptext, validator=None, impl=None): 521 """Add a new command to the parser. 522 523 Args: 524 names: command name, or list of synonyms 525 helptext: brief string description of the command 526 validator: method to validate a command's arguments 527 impl: callable to be invoked when command is called 528 529 Raises: 530 ValueError: raised if command already added 531 532 Returns: 533 The new command 534 """ 535 if IsString(names): names = [names] 536 537 for name in names: 538 if name in self.cmd_dict: 539 raise ValueError("%s is already a command"%name) 540 541 cmd = Command(names, helptext, validator, impl) 542 cmd.cmdline = self 543 544 self.commands.append(cmd) 545 for name in names: 546 self.cmd_dict[name.lower()] = cmd 547 548 return cmd 549 550 def GetUsageString(self): 551 """Returns simple usage instructions.""" 552 return "Type '%s help' for usage." % self.prog 553 554 def ParseCommandLine(self, argv=None, prog=None, execute=True): 555 """Does the work of parsing a command line. 556 557 Args: 558 argv: list of arguments, defaults to sys.args[1:] 559 prog: name of the command, defaults to the base name of the script 560 execute: if false, just parse, don't invoke the 'impl' member 561 562 Returns: 563 The command that was executed 564 """ 565 if argv is None: argv = sys.argv[1:] 566 if prog is None: prog = os.path.basename(sys.argv[0]).split('.')[0] 567 568 # Store off our parameters, we may need them someday 569 self.argv = argv 570 self.prog = prog 571 572 # We shouldn't be invoked without arguments, that's just lame 573 if not len(argv): 574 self.out.writelines(self.GetUsageString()) 575 self.Exit() 576 return None # in case the client overrides Exit 577 578 # Is it a valid command? 579 self.command_string = argv[0].lower() 580 if not self.command_string in self.cmd_dict: 581 self.err.write("Unknown command: '%s'\n\n" % self.command_string) 582 self.out.write(self.GetUsageString()) 583 self.Exit() 584 return None # in case the client overrides Exit 585 586 self.command = self.cmd_dict[self.command_string] 587 588 # "rargs" = remaining (unparsed) arguments 589 # "largs" = already parsed, "left" of the read head 590 self.rargs = argv[1:] 591 self.largs = [] 592 593 # let the command object do the parsing 594 self.command.ParseArguments() 595 596 if self.command.parse_errors: 597 # there were errors, output the usage string and exit 598 self.err.write(self.command.GetUsageString()+"\n\n") 599 self.err.write("\n".join(self.command.parse_errors)) 600 self.err.write("\n\n") 601 602 self.Exit() 603 604 elif execute and self.command.impl: 605 self.command.impl(self.command) 606 607 return self.command 608 609 def __getitem__(self, key): 610 return self.cmd_dict[key] 611 612 def __iter__(self): 613 return self.cmd_dict.__iter__() 614 615 616def ValidateHelpCommand(command): 617 """Checks to make sure an argument to 'help' is a valid command.""" 618 if 'command' in command and command['command'] not in command.cmdline: 619 raise ParseError("'%s': unknown command" % command['command']) 620 621 622def DoHelpCommand(command): 623 """Executed when the command is 'help'.""" 624 out = command.cmdline.out 625 width = command['--width'] 626 627 if 'command' not in command: 628 out.write(command.GetUsageString()) 629 out.write("\n\n") 630 631 indent = 5 632 gutter = 2 633 634 command_width = ( 635 max([len(cmd.names[0]) for cmd in command.cmdline.commands]) + gutter) 636 637 for cmd in command.cmdline.commands: 638 cmd_name = cmd.names[0] 639 640 initial_indent = (" "*indent + cmd_name + " "* 641 (command_width+gutter-len(cmd_name))) 642 subsequent_indent = " "*(indent+command_width+gutter) 643 644 out.write(textwrap.fill(cmd.helptext, width, 645 initial_indent=initial_indent, 646 subsequent_indent=subsequent_indent)) 647 out.write("\n") 648 649 out.write("\n") 650 651 else: 652 help_cmd = command.cmdline[command['command']] 653 654 out.write(textwrap.fill(help_cmd.helptext, width)) 655 out.write("\n\n") 656 out.write(help_cmd.GetUsageString(width=width)) 657 out.write("\n\n") 658 out.write(help_cmd.GetHelpString(width=width)) 659 out.write("\n") 660 661 command.cmdline.Exit() 662 663 664def main(): 665 # If we're invoked rather than imported, run some tests 666 cmdline = CommandLine() 667 668 # Since we're testing, override Exit() 669 def TestExit(): 670 pass 671 cmdline.Exit = TestExit 672 673 # Actually, while we're at it, let's override error output too 674 cmdline.err = open(os.path.devnull, "w") 675 676 test = cmdline.AddCommand(["test", "testa", "testb"], "test command") 677 test.AddArgument(["-i", "--int", "--integer", "--optint", "--optionalint"], 678 "optional integer parameter", type='int') 679 test.AddArgument("--reqint", "required integer parameter", type='int', 680 required=True) 681 test.AddArgument("pos1", "required positional argument", positional=True, 682 required=True) 683 test.AddArgument("pos2", "optional positional argument", positional=True) 684 test.AddArgument("pos3", "another optional positional arg", 685 positional=True) 686 687 # mutually dependent arguments 688 test.AddArgument("--mutdep1", "mutually dependent parameter 1") 689 test.AddArgument("--mutdep2", "mutually dependent parameter 2") 690 test.AddArgument("--mutdep3", "mutually dependent parameter 3") 691 test.AddMutualDependency(["--mutdep1", "--mutdep2", "--mutdep3"]) 692 693 # mutually exclusive arguments 694 test.AddArgument("--mutex1", "mutually exclusive parameter 1") 695 test.AddArgument("--mutex2", "mutually exclusive parameter 2") 696 test.AddArgument("--mutex3", "mutually exclusive parameter 3") 697 test.AddMutualExclusion(["--mutex1", "--mutex2", "--mutex3"]) 698 699 # dependent argument 700 test.AddArgument("--dependent", "dependent argument") 701 test.AddDependency("--dependent", "--int") 702 703 # other argument types 704 test.AddArgument("--file", "filename argument", type='readfile') 705 test.AddArgument("--coords", "coordinate argument", type='coords') 706 test.AddArgument("--flag", "flag argument", type='flag') 707 708 test.AddArgument("--req1", "part of a required group", type='flag') 709 test.AddArgument("--req2", "part 2 of a required group", type='flag') 710 711 test.AddRequiredGroup(["--req1", "--req2"]) 712 713 # a few failure cases 714 exception_cases = """ 715 test.AddArgument("failpos", "can't have req'd pos arg after opt", 716 positional=True, required=True) 717+++ 718 test.AddArgument("--int", "this argument already exists") 719+++ 720 test.AddDependency("--int", "--doesntexist") 721+++ 722 test.AddMutualDependency(["--doesntexist", "--mutdep2"]) 723+++ 724 test.AddMutualExclusion(["--doesntexist", "--mutex2"]) 725+++ 726 test.AddArgument("--reqflag", "required flag", required=True, type='flag') 727+++ 728 test.AddRequiredGroup(["--req1", "--doesntexist"]) 729""" 730 for exception_case in exception_cases.split("+++"): 731 try: 732 exception_case = exception_case.strip() 733 exec exception_case # yes, I'm using exec, it's just for a test. 734 except ValueError: 735 # this is expected 736 pass 737 except KeyError: 738 # ...and so is this 739 pass 740 else: 741 print ("FAILURE: expected an exception for '%s'" 742 " and didn't get it" % exception_case) 743 744 # Let's do some parsing! first, the minimal success line: 745 MIN = "test --reqint 123 param1 --req1 " 746 747 # tuples of (command line, expected error count) 748 test_lines = [ 749 ("test --int 3 foo --req1", 1), # missing required named parameter 750 ("test --reqint 3 --req1", 1), # missing required positional parameter 751 (MIN, 0), # success! 752 ("test param1 --reqint 123 --req1", 0), # success, order shouldn't matter 753 ("test param1 --reqint 123 --req2", 0), # success, any of required group ok 754 (MIN+"param2", 0), # another positional parameter is okay 755 (MIN+"param2 param3", 0), # and so are three 756 (MIN+"param2 param3 param4", 1), # but four are just too many 757 (MIN+"--int", 1), # where's the value? 758 (MIN+"--int 456", 0), # this is fine 759 (MIN+"--int456", 0), # as is this 760 (MIN+"--int:456", 0), # and this 761 (MIN+"--int=456", 0), # and this 762 (MIN+"--file c:\\windows\\system32\\kernel32.dll", 0), # yup 763 (MIN+"--file c:\\thisdoesntexist", 1), # nope 764 (MIN+"--mutdep1 a", 2), # no! 765 (MIN+"--mutdep2 b", 2), # also no! 766 (MIN+"--mutdep3 c", 2), # dream on! 767 (MIN+"--mutdep1 a --mutdep2 b", 2), # almost! 768 (MIN+"--mutdep1 a --mutdep2 b --mutdep3 c", 0), # yes 769 (MIN+"--mutex1 a", 0), # yes 770 (MIN+"--mutex2 b", 0), # yes 771 (MIN+"--mutex3 c", 0), # fine 772 (MIN+"--mutex1 a --mutex2 b", 1), # not fine 773 (MIN+"--mutex1 a --mutex2 b --mutex3 c", 3), # even worse 774 (MIN+"--dependent 1", 1), # no 775 (MIN+"--dependent 1 --int 2", 0), # ok 776 (MIN+"--int abc", 1), # bad type 777 (MIN+"--coords abc", 1), # also bad 778 (MIN+"--coords (abc)", 1), # getting warmer 779 (MIN+"--coords (abc,def)", 1), # missing something 780 (MIN+"--coords (123)", 1), # ooh, so close 781 (MIN+"--coords (123,def)", 1), # just a little farther 782 (MIN+"--coords (123,456)", 0), # finally! 783 ("test --int 123 --reqint=456 foo bar --coords(42,88) baz --req1", 0) 784 ] 785 786 badtests = 0 787 788 for (test, expected_failures) in test_lines: 789 cmdline.ParseCommandLine([x.strip() for x in test.strip().split(" ")]) 790 791 if not len(cmdline.command.parse_errors) == expected_failures: 792 print "FAILED:\n issued: '%s'\n expected: %d\n received: %d\n\n" % ( 793 test, expected_failures, len(cmdline.command.parse_errors)) 794 badtests += 1 795 796 print "%d failed out of %d tests" % (badtests, len(test_lines)) 797 798 cmdline.ParseCommandLine(["help", "test"]) 799 800 801if __name__ == "__main__": 802 sys.exit(main()) 803