| #!/usr/bin/env python |
| # Copyright (c) 2011 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """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() |
| |
| |
| def 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"]) |
| |
| |
| if __name__ == "__main__": |
| sys.exit(main()) |