- add sources.
[platform/framework/web/crosswalk.git] / src / tools / site_compare / command_line.py
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
8 Supports the concept of command line commands, each with its own set
9 of arguments. Supports dependent arguments and mutually exclusive arguments.
10 Basically, a better optparse. I took heed of epg's WHINE() in gvn.cmdline
11 and dumped optparse in favor of something better.
12 """
13
14 import os.path
15 import re
16 import string
17 import sys
18 import textwrap
19 import types
20
21
22 def IsString(var):
23   """Little helper function to see if a variable is a string."""
24   return type(var) in types.StringTypes
25
26
27 class ParseError(Exception):
28   """Encapsulates errors from parsing, string arg is description."""
29   pass
30
31
32 class 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
496 class 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
616 def 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
622 def 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
664 def 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
801 if __name__ == "__main__":
802   sys.exit(main())