diff options
Diffstat (limited to 'tools/site_compare/command_line.py')
-rw-r--r-- | tools/site_compare/command_line.py | 823 |
1 files changed, 823 insertions, 0 deletions
diff --git a/tools/site_compare/command_line.py b/tools/site_compare/command_line.py new file mode 100644 index 0000000..b99a1c9 --- /dev/null +++ b/tools/site_compare/command_line.py @@ -0,0 +1,823 @@ +#!/usr/bin/python2.4 +# Copyright 2008, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +"""Parse a command line, retrieving a command and its arguments. + +Supports the concept of command line commands, each with its own set +of arguments. Supports dependent arguments and mutually exclusive arguments. +Basically, a better optparse. I took heed of epg's WHINE() in gvn.cmdline +and dumped optparse in favor of something better. +""" + +import os.path +import re +import string +import sys +import textwrap +import types + + +def IsString(var): + """Little helper function to see if a variable is a string.""" + return type(var) in types.StringTypes + + +class ParseError(Exception): + """Encapsulates errors from parsing, string arg is description.""" + pass + + +class Command(object): + """Implements a single command.""" + + def __init__(self, names, helptext, validator=None, impl=None): + """Initializes Command from names and helptext, plus optional callables. + + Args: + names: command name, or list of synonyms + helptext: brief string description of the command + validator: callable for custom argument validation + Should raise ParseError if it wants + impl: callable to be invoked when command is called + """ + self.names = names + self.validator = validator + self.helptext = helptext + self.impl = impl + self.args = [] + self.required_groups = [] + self.arg_dict = {} + self.positional_args = [] + self.cmdline = None + + class Argument(object): + """Encapsulates an argument to a command.""" + VALID_TYPES = ['string', 'readfile', 'int', 'flag', 'coords'] + TYPES_WITH_VALUES = ['string', 'readfile', 'int', 'coords'] + + def __init__(self, names, helptext, type, metaname, + required, default, positional): + """Command-line argument to a command. + + Args: + names: argument name, or list of synonyms + helptext: brief description of the argument + type: type of the argument. Valid values include: + string - a string + readfile - a file which must exist and be available + for reading + int - an integer + flag - an optional flag (bool) + coords - (x,y) where x and y are ints + metaname: Name to display for value in help, inferred if not + specified + required: True if argument must be specified + default: Default value if not specified + positional: Argument specified by location, not name + + Raises: + ValueError: the argument name is invalid for some reason + """ + if type not in Command.Argument.VALID_TYPES: + raise ValueError("Invalid type: %r" % type) + + if required and default is not None: + raise ValueError("required and default are mutually exclusive") + + if required and type == 'flag': + raise ValueError("A required flag? Give me a break.") + + if metaname and type not in Command.Argument.TYPES_WITH_VALUES: + raise ValueError("Type %r can't have a metaname" % type) + + # If no metaname is provided, infer it: use the alphabetical characters + # of the last provided name + if not metaname and type in Command.Argument.TYPES_WITH_VALUES: + metaname = ( + names[-1].lstrip(string.punctuation + string.whitespace).upper()) + + self.names = names + self.helptext = helptext + self.type = type + self.required = required + self.default = default + self.positional = positional + self.metaname = metaname + + self.mutex = [] # arguments that are mutually exclusive with + # this one + self.depends = [] # arguments that must be present for this + # one to be valid + self.present = False # has this argument been specified? + + def AddDependency(self, arg): + """Makes this argument dependent on another argument. + + Args: + arg: name of the argument this one depends on + """ + if arg not in self.depends: + self.depends.append(arg) + + def AddMutualExclusion(self, arg): + """Makes this argument invalid if another is specified. + + Args: + arg: name of the mutually exclusive argument. + """ + if arg not in self.mutex: + self.mutex.append(arg) + + def GetUsageString(self): + """Returns a brief string describing the argument's usage.""" + if not self.positional: + string = self.names[0] + if self.type in Command.Argument.TYPES_WITH_VALUES: + string += "="+self.metaname + else: + string = self.metaname + + if not self.required: + string = "["+string+"]" + + return string + + def GetNames(self): + """Returns a string containing a list of the arg's names.""" + if self.positional: + return self.metaname + else: + return ", ".join(self.names) + + def GetHelpString(self, width=80, indent=5, names_width=20, gutter=2): + """Returns a help string including help for all the arguments.""" + names = [" "*indent + line +" "*(names_width-len(line)) for line in + textwrap.wrap(self.GetNames(), names_width)] + + helpstring = textwrap.wrap(self.helptext, width-indent-names_width-gutter) + + if len(names) < len(helpstring): + names += [" "*(indent+names_width)]*(len(helpstring)-len(names)) + + if len(helpstring) < len(names): + helpstring += [""]*(len(names)-len(helpstring)) + + return "\n".join([name_line + " "*gutter + help_line for + name_line, help_line in zip(names, helpstring)]) + + def __repr__(self): + if self.present: + string = '= %r' % self.value + else: + string = "(absent)" + + return "Argument %s '%s'%s" % (self.type, self.names[0], string) + + # end of nested class Argument + + def AddArgument(self, names, helptext, type="string", metaname=None, + required=False, default=None, positional=False): + """Command-line argument to a command. + + Args: + names: argument name, or list of synonyms + helptext: brief description of the argument + type: type of the argument + metaname: Name to display for value in help, inferred if not + required: True if argument must be specified + default: Default value if not specified + positional: Argument specified by location, not name + + Raises: + ValueError: the argument already exists or is invalid + + Returns: + The newly-created argument + """ + if IsString(names): names = [names] + + names = [name.lower() for name in names] + + for name in names: + if name in self.arg_dict: + raise ValueError("%s is already an argument"%name) + + if (positional and required and + [arg for arg in self.args if arg.positional] and + not [arg for arg in self.args if arg.positional][-1].required): + raise ValueError( + "A required positional argument may not follow an optional one.") + + arg = Command.Argument(names, helptext, type, metaname, + required, default, positional) + + self.args.append(arg) + + for name in names: + self.arg_dict[name] = arg + + return arg + + def GetArgument(self, name): + """Return an argument from a name.""" + return self.arg_dict[name.lower()] + + def AddMutualExclusion(self, args): + """Specifies that a list of arguments are mutually exclusive.""" + if len(args) < 2: + raise ValueError("At least two arguments must be specified.") + + args = [arg.lower() for arg in args] + + for index in xrange(len(args)-1): + for index2 in xrange(index+1, len(args)): + self.arg_dict[args[index]].AddMutualExclusion(self.arg_dict[args[index2]]) + + def AddDependency(self, dependent, depends_on): + """Specifies that one argument may only be present if another is. + + Args: + dependent: the name of the dependent argument + depends_on: the name of the argument on which it depends + """ + self.arg_dict[dependent.lower()].AddDependency( + self.arg_dict[depends_on.lower()]) + + def AddMutualDependency(self, args): + """Specifies that a list of arguments are all mutually dependent.""" + if len(args) < 2: + raise ValueError("At least two arguments must be specified.") + + args = [arg.lower() for arg in args] + + for (arg1, arg2) in [(arg1, arg2) for arg1 in args for arg2 in args]: + if arg1 == arg2: continue + self.arg_dict[arg1].AddDependency(self.arg_dict[arg2]) + + def AddRequiredGroup(self, args): + """Specifies that at least one of the named arguments must be present.""" + if len(args) < 2: + raise ValueError("At least two arguments must be in a required group.") + + args = [self.arg_dict[arg.lower()] for arg in args] + + self.required_groups.append(args) + + def ParseArguments(self): + """Given a command line, parse and validate the arguments.""" + + # reset all the arguments before we parse + for arg in self.args: + arg.present = False + arg.value = None + + self.parse_errors = [] + + # look for arguments remaining on the command line + while len(self.cmdline.rargs): + try: + self.ParseNextArgument() + except ParseError, e: + self.parse_errors.append(e.args[0]) + + # after all the arguments are parsed, check for problems + for arg in self.args: + if not arg.present and arg.required: + self.parse_errors.append("'%s': required parameter was missing" + % arg.names[0]) + + if not arg.present and arg.default: + arg.present = True + arg.value = arg.default + + if arg.present: + for mutex in arg.mutex: + if mutex.present: + self.parse_errors.append( + "'%s', '%s': arguments are mutually exclusive" % + (arg.argstr, mutex.argstr)) + + for depend in arg.depends: + if not depend.present: + self.parse_errors.append("'%s': '%s' must be specified as well" % + (arg.argstr, depend.names[0])) + + # check for required groups + for group in self.required_groups: + if not [arg for arg in group if arg.present]: + self.parse_errors.append("%s: at least one must be present" % + (", ".join(["'%s'" % arg.names[-1] for arg in group]))) + + # if we have any validators, invoke them + if not self.parse_errors and self.validator: + try: + self.validator(self) + except ParseError, e: + self.parse_errors.append(e.args[0]) + + # Helper methods so you can treat the command like a dict + def __getitem__(self, key): + arg = self.arg_dict[key.lower()] + + if arg.type == 'flag': + return arg.present + else: + return arg.value + + def __iter__(self): + return [arg for arg in self.args if arg.present].__iter__() + + def ArgumentPresent(self, key): + """Tests if an argument exists and has been specified.""" + return key.lower() in self.arg_dict and self.arg_dict[key.lower()].present + + def __contains__(self, key): + return self.ArgumentPresent(key) + + def ParseNextArgument(self): + """Find the next argument in the command line and parse it.""" + arg = None + value = None + argstr = self.cmdline.rargs.pop(0) + + # First check: is this a literal argument? + if argstr.lower() in self.arg_dict: + arg = self.arg_dict[argstr.lower()] + if arg.type in Command.Argument.TYPES_WITH_VALUES: + if len(self.cmdline.rargs): + value = self.cmdline.rargs.pop(0) + + # Second check: is this of the form "arg=val" or "arg:val"? + if arg is None: + delimiter_pos = -1 + + for delimiter in [':', '=']: + pos = argstr.find(delimiter) + if pos >= 0: + if delimiter_pos < 0 or pos < delimiter_pos: + delimiter_pos = pos + + if delimiter_pos >= 0: + testarg = argstr[:delimiter_pos] + testval = argstr[delimiter_pos+1:] + + if testarg.lower() in self.arg_dict: + arg = self.arg_dict[testarg.lower()] + argstr = testarg + value = testval + + # Third check: does this begin an argument? + if arg is None: + for key in self.arg_dict.iterkeys(): + if (len(key) < len(argstr) and + self.arg_dict[key].type in Command.Argument.TYPES_WITH_VALUES and + argstr[:len(key)].lower() == key): + value = argstr[len(key):] + argstr = argstr[:len(key)] + arg = self.arg_dict[argstr] + + # Fourth check: do we have any positional arguments available? + if arg is None: + for positional_arg in [ + testarg for testarg in self.args if testarg.positional]: + if not positional_arg.present: + arg = positional_arg + value = argstr + argstr = positional_arg.names[0] + break + + # Push the retrieved argument/value onto the largs stack + if argstr: self.cmdline.largs.append(argstr) + if value: self.cmdline.largs.append(value) + + # If we've made it this far and haven't found an arg, give up + if arg is None: + raise ParseError("Unknown argument: '%s'" % argstr) + + # Convert the value, if necessary + if arg.type in Command.Argument.TYPES_WITH_VALUES and value is None: + raise ParseError("Argument '%s' requires a value" % argstr) + + if value is not None: + value = self.StringToValue(value, arg.type, argstr) + + arg.argstr = argstr + arg.value = value + arg.present = True + + # end method ParseNextArgument + + def StringToValue(self, value, type, argstr): + """Convert a string from the command line to a value type.""" + try: + if type == 'string': + pass # leave it be + + elif type == 'int': + try: + value = int(value) + except ValueError: + raise ParseError + + elif type == 'readfile': + if not os.path.isfile(value): + raise ParseError("'%s': '%s' does not exist" % (argstr, value)) + + elif type == 'coords': + try: + value = [int(val) for val in + re.match("\(\s*(\d+)\s*\,\s*(\d+)\s*\)\s*\Z", value). + groups()] + except AttributeError: + raise ParseError + + else: + raise ValueError("Unknown type: '%s'" % type) + + except ParseError, e: + # The bare exception is raised in the generic case; more specific errors + # will arrive with arguments and should just be reraised + if not e.args: + e = ParseError("'%s': unable to convert '%s' to type '%s'" % + (argstr, value, type)) + raise e + + return value + + def SortArgs(self): + """Returns a method that can be passed to sort() to sort arguments.""" + + def ArgSorter(arg1, arg2): + """Helper for sorting arguments in the usage string. + + Positional arguments come first, then required arguments, + then optional arguments. Pylint demands this trivial function + have both Args: and Returns: sections, sigh. + + Args: + arg1: the first argument to compare + arg2: the second argument to compare + + Returns: + -1 if arg1 should be sorted first, +1 if it should be sorted second, + and 0 if arg1 and arg2 have the same sort level. + """ + return ((arg2.positional-arg1.positional)*2 + + (arg2.required-arg1.required)) + return ArgSorter + + def GetUsageString(self, width=80, name=None): + """Gets a string describing how the command is used.""" + if name is None: name = self.names[0] + + initial_indent = "Usage: %s %s " % (self.cmdline.prog, name) + subsequent_indent = " " * len(initial_indent) + + sorted_args = self.args[:] + sorted_args.sort(self.SortArgs()) + + return textwrap.fill( + " ".join([arg.GetUsageString() for arg in sorted_args]), width, + initial_indent=initial_indent, + subsequent_indent=subsequent_indent) + + def GetHelpString(self, width=80): + """Returns a list of help strings for all this command's arguments.""" + sorted_args = self.args[:] + sorted_args.sort(self.SortArgs()) + + return "\n".join([arg.GetHelpString(width) for arg in sorted_args]) + + # end class Command + + +class CommandLine(object): + """Parse a command line, extracting a command and its arguments.""" + + def __init__(self): + self.commands = [] + self.cmd_dict = {} + + # Add the help command to the parser + help_cmd = self.AddCommand(["help", "--help", "-?", "-h"], + "Displays help text for a command", + ValidateHelpCommand, + DoHelpCommand) + + help_cmd.AddArgument( + "command", "Command to retrieve help for", positional=True) + help_cmd.AddArgument( + "--width", "Width of the output", type='int', default=80) + + self.Exit = sys.exit # override this if you don't want the script to halt + # on error or on display of help + + self.out = sys.stdout # override these if you want to redirect + self.err = sys.stderr # output or error messages + + def AddCommand(self, names, helptext, validator=None, impl=None): + """Add a new command to the parser. + + Args: + names: command name, or list of synonyms + helptext: brief string description of the command + validator: method to validate a command's arguments + impl: callable to be invoked when command is called + + Raises: + ValueError: raised if command already added + + Returns: + The new command + """ + if IsString(names): names = [names] + + for name in names: + if name in self.cmd_dict: + raise ValueError("%s is already a command"%name) + + cmd = Command(names, helptext, validator, impl) + cmd.cmdline = self + + self.commands.append(cmd) + for name in names: + self.cmd_dict[name.lower()] = cmd + + return cmd + + def GetUsageString(self): + """Returns simple usage instructions.""" + return "Type '%s help' for usage." % self.prog + + def ParseCommandLine(self, argv=None, prog=None, execute=True): + """Does the work of parsing a command line. + + Args: + argv: list of arguments, defaults to sys.args[1:] + prog: name of the command, defaults to the base name of the script + execute: if false, just parse, don't invoke the 'impl' member + + Returns: + The command that was executed + """ + if argv is None: argv = sys.argv[1:] + if prog is None: prog = os.path.basename(sys.argv[0]).split('.')[0] + + # Store off our parameters, we may need them someday + self.argv = argv + self.prog = prog + + # We shouldn't be invoked without arguments, that's just lame + if not len(argv): + self.out.writelines(self.GetUsageString()) + self.Exit() + return None # in case the client overrides Exit + + # Is it a valid command? + self.command_string = argv[0].lower() + if not self.command_string in self.cmd_dict: + self.err.write("Unknown command: '%s'\n\n" % self.command_string) + self.out.write(self.GetUsageString()) + self.Exit() + return None # in case the client overrides Exit + + self.command = self.cmd_dict[self.command_string] + + # "rargs" = remaining (unparsed) arguments + # "largs" = already parsed, "left" of the read head + self.rargs = argv[1:] + self.largs = [] + + # let the command object do the parsing + self.command.ParseArguments() + + if self.command.parse_errors: + # there were errors, output the usage string and exit + self.err.write(self.command.GetUsageString()+"\n\n") + self.err.write("\n".join(self.command.parse_errors)) + self.err.write("\n\n") + + self.Exit() + + elif execute and self.command.impl: + self.command.impl(self.command) + + return self.command + + def __getitem__(self, key): + return self.cmd_dict[key] + + def __iter__(self): + return self.cmd_dict.__iter__() + + +def ValidateHelpCommand(command): + """Checks to make sure an argument to 'help' is a valid command.""" + if 'command' in command and command['command'] not in command.cmdline: + raise ParseError("'%s': unknown command" % command['command']) + + +def DoHelpCommand(command): + """Executed when the command is 'help'.""" + out = command.cmdline.out + width = command['--width'] + + if 'command' not in command: + out.write(command.GetUsageString()) + out.write("\n\n") + + indent = 5 + gutter = 2 + + command_width = ( + max([len(cmd.names[0]) for cmd in command.cmdline.commands]) + gutter) + + for cmd in command.cmdline.commands: + cmd_name = cmd.names[0] + + initial_indent = (" "*indent + cmd_name + " "* + (command_width+gutter-len(cmd_name))) + subsequent_indent = " "*(indent+command_width+gutter) + + out.write(textwrap.fill(cmd.helptext, width, + initial_indent=initial_indent, + subsequent_indent=subsequent_indent)) + out.write("\n") + + out.write("\n") + + else: + help_cmd = command.cmdline[command['command']] + + out.write(textwrap.fill(help_cmd.helptext, width)) + out.write("\n\n") + out.write(help_cmd.GetUsageString(width=width)) + out.write("\n\n") + out.write(help_cmd.GetHelpString(width=width)) + out.write("\n") + + command.cmdline.Exit() + +if __name__ == "__main__": + # If we're invoked rather than imported, run some tests + cmdline = CommandLine() + + # Since we're testing, override Exit() + def TestExit(): + pass + cmdline.Exit = TestExit + + # Actually, while we're at it, let's override error output too + cmdline.err = open(os.path.devnull, "w") + + test = cmdline.AddCommand(["test", "testa", "testb"], "test command") + test.AddArgument(["-i", "--int", "--integer", "--optint", "--optionalint"], + "optional integer parameter", type='int') + test.AddArgument("--reqint", "required integer parameter", type='int', + required=True) + test.AddArgument("pos1", "required positional argument", positional=True, + required=True) + test.AddArgument("pos2", "optional positional argument", positional=True) + test.AddArgument("pos3", "another optional positional arg", + positional=True) + + # mutually dependent arguments + test.AddArgument("--mutdep1", "mutually dependent parameter 1") + test.AddArgument("--mutdep2", "mutually dependent parameter 2") + test.AddArgument("--mutdep3", "mutually dependent parameter 3") + test.AddMutualDependency(["--mutdep1", "--mutdep2", "--mutdep3"]) + + # mutually exclusive arguments + test.AddArgument("--mutex1", "mutually exclusive parameter 1") + test.AddArgument("--mutex2", "mutually exclusive parameter 2") + test.AddArgument("--mutex3", "mutually exclusive parameter 3") + test.AddMutualExclusion(["--mutex1", "--mutex2", "--mutex3"]) + + # dependent argument + test.AddArgument("--dependent", "dependent argument") + test.AddDependency("--dependent", "--int") + + # other argument types + test.AddArgument("--file", "filename argument", type='readfile') + test.AddArgument("--coords", "coordinate argument", type='coords') + test.AddArgument("--flag", "flag argument", type='flag') + + test.AddArgument("--req1", "part of a required group", type='flag') + test.AddArgument("--req2", "part 2 of a required group", type='flag') + + test.AddRequiredGroup(["--req1", "--req2"]) + + # a few failure cases + exception_cases = """ + test.AddArgument("failpos", "can't have req'd pos arg after opt", + positional=True, required=True) ++++ + test.AddArgument("--int", "this argument already exists") ++++ + test.AddDependency("--int", "--doesntexist") ++++ + test.AddMutualDependency(["--doesntexist", "--mutdep2"]) ++++ + test.AddMutualExclusion(["--doesntexist", "--mutex2"]) ++++ + test.AddArgument("--reqflag", "required flag", required=True, type='flag') ++++ + test.AddRequiredGroup(["--req1", "--doesntexist"]) +""" + for exception_case in exception_cases.split("+++"): + try: + exception_case = exception_case.strip() + exec exception_case # yes, I'm using exec, it's just for a test. + except ValueError: + # this is expected + pass + except KeyError: + # ...and so is this + pass + else: + print ("FAILURE: expected an exception for '%s'" + " and didn't get it" % exception_case) + + # Let's do some parsing! first, the minimal success line: + MIN = "test --reqint 123 param1 --req1 " + + # tuples of (command line, expected error count) + test_lines = [ + ("test --int 3 foo --req1", 1), # missing required named parameter + ("test --reqint 3 --req1", 1), # missing required positional parameter + (MIN, 0), # success! + ("test param1 --reqint 123 --req1", 0), # success, order shouldn't matter + ("test param1 --reqint 123 --req2", 0), # success, any of required group ok + (MIN+"param2", 0), # another positional parameter is okay + (MIN+"param2 param3", 0), # and so are three + (MIN+"param2 param3 param4", 1), # but four are just too many + (MIN+"--int", 1), # where's the value? + (MIN+"--int 456", 0), # this is fine + (MIN+"--int456", 0), # as is this + (MIN+"--int:456", 0), # and this + (MIN+"--int=456", 0), # and this + (MIN+"--file c:\\windows\\system32\\kernel32.dll", 0), # yup + (MIN+"--file c:\\thisdoesntexist", 1), # nope + (MIN+"--mutdep1 a", 2), # no! + (MIN+"--mutdep2 b", 2), # also no! + (MIN+"--mutdep3 c", 2), # dream on! + (MIN+"--mutdep1 a --mutdep2 b", 2), # almost! + (MIN+"--mutdep1 a --mutdep2 b --mutdep3 c", 0), # yes + (MIN+"--mutex1 a", 0), # yes + (MIN+"--mutex2 b", 0), # yes + (MIN+"--mutex3 c", 0), # fine + (MIN+"--mutex1 a --mutex2 b", 1), # not fine + (MIN+"--mutex1 a --mutex2 b --mutex3 c", 3), # even worse + (MIN+"--dependent 1", 1), # no + (MIN+"--dependent 1 --int 2", 0), # ok + (MIN+"--int abc", 1), # bad type + (MIN+"--coords abc", 1), # also bad + (MIN+"--coords (abc)", 1), # getting warmer + (MIN+"--coords (abc,def)", 1), # missing something + (MIN+"--coords (123)", 1), # ooh, so close + (MIN+"--coords (123,def)", 1), # just a little farther + (MIN+"--coords (123,456)", 0), # finally! + ("test --int 123 --reqint=456 foo bar --coords(42,88) baz --req1", 0) + ] + + badtests = 0 + + for (test, expected_failures) in test_lines: + cmdline.ParseCommandLine([x.strip() for x in test.strip().split(" ")]) + + if not len(cmdline.command.parse_errors) == expected_failures: + print "FAILED:\n issued: '%s'\n expected: %d\n received: %d\n\n" % ( + test, expected_failures, len(cmdline.command.parse_errors)) + badtests += 1 + + print "%d failed out of %d tests" % (badtests, len(test_lines)) + + cmdline.ParseCommandLine(["help", "test"]) + |