text: #!/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.
  """
  
  from __future__ import print_function
  
  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())
  