Fix build break
[chromium-blink-merge.git] / tools / site_compare / command_line.py
blobde93d18ce0ab36525ecb1ae3af6e417ef5cf00d6
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.
6 """Parse a command line, retrieving a command and its arguments.
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 """
14 import os.path
15 import re
16 import string
17 import sys
18 import textwrap
19 import types
22 def IsString(var):
23 """Little helper function to see if a variable is a string."""
24 return type(var) in types.StringTypes
27 class ParseError(Exception):
28 """Encapsulates errors from parsing, string arg is description."""
29 pass
32 class Command(object):
33 """Implements a single command."""
35 def __init__(self, names, helptext, validator=None, impl=None):
36 """Initializes Command from names and helptext, plus optional callables.
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
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']
60 def __init__(self, names, helptext, type, metaname,
61 required, default, positional):
62 """Command-line argument to a command.
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
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)
86 if required and default is not None:
87 raise ValueError("required and default are mutually exclusive")
89 if required and type == 'flag':
90 raise ValueError("A required flag? Give me a break.")
92 if metaname and type not in Command.Argument.TYPES_WITH_VALUES:
93 raise ValueError("Type %r can't have a metaname" % type)
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())
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
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?
115 def AddDependency(self, arg):
116 """Makes this argument dependent on another argument.
118 Args:
119 arg: name of the argument this one depends on
121 if arg not in self.depends:
122 self.depends.append(arg)
124 def AddMutualExclusion(self, arg):
125 """Makes this argument invalid if another is specified.
127 Args:
128 arg: name of the mutually exclusive argument.
130 if arg not in self.mutex:
131 self.mutex.append(arg)
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
142 if not self.required:
143 string = "["+string+"]"
145 return string
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)
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)]
159 helpstring = textwrap.wrap(self.helptext, width-indent-names_width-gutter)
161 if len(names) < len(helpstring):
162 names += [" "*(indent+names_width)]*(len(helpstring)-len(names))
164 if len(helpstring) < len(names):
165 helpstring += [""]*(len(names)-len(helpstring))
167 return "\n".join([name_line + " "*gutter + help_line for
168 name_line, help_line in zip(names, helpstring)])
170 def __repr__(self):
171 if self.present:
172 string = '= %r' % self.value
173 else:
174 string = "(absent)"
176 return "Argument %s '%s'%s" % (self.type, self.names[0], string)
178 # end of nested class Argument
180 def AddArgument(self, names, helptext, type="string", metaname=None,
181 required=False, default=None, positional=False):
182 """Command-line argument to a command.
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
193 Raises:
194 ValueError: the argument already exists or is invalid
196 Returns:
197 The newly-created argument
199 if IsString(names): names = [names]
201 names = [name.lower() for name in names]
203 for name in names:
204 if name in self.arg_dict:
205 raise ValueError("%s is already an argument"%name)
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.")
213 arg = Command.Argument(names, helptext, type, metaname,
214 required, default, positional)
216 self.args.append(arg)
218 for name in names:
219 self.arg_dict[name] = arg
221 return arg
223 def GetArgument(self, name):
224 """Return an argument from a name."""
225 return self.arg_dict[name.lower()]
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.")
232 args = [arg.lower() for arg in args]
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]])
238 def AddDependency(self, dependent, depends_on):
239 """Specifies that one argument may only be present if another is.
241 Args:
242 dependent: the name of the dependent argument
243 depends_on: the name of the argument on which it depends
245 self.arg_dict[dependent.lower()].AddDependency(
246 self.arg_dict[depends_on.lower()])
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.")
253 args = [arg.lower() for arg in args]
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])
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.")
264 args = [self.arg_dict[arg.lower()] for arg in args]
266 self.required_groups.append(args)
268 def ParseArguments(self):
269 """Given a command line, parse and validate the arguments."""
271 # reset all the arguments before we parse
272 for arg in self.args:
273 arg.present = False
274 arg.value = None
276 self.parse_errors = []
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])
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])
291 if not arg.present and arg.default:
292 arg.present = True
293 arg.value = arg.default
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))
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]))
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])))
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])
320 # Helper methods so you can treat the command like a dict
321 def __getitem__(self, key):
322 arg = self.arg_dict[key.lower()]
324 if arg.type == 'flag':
325 return arg.present
326 else:
327 return arg.value
329 def __iter__(self):
330 return [arg for arg in self.args if arg.present].__iter__()
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
336 def __contains__(self, key):
337 return self.ArgumentPresent(key)
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)
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)
352 # Second check: is this of the form "arg=val" or "arg:val"?
353 if arg is None:
354 delimiter_pos = -1
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
362 if delimiter_pos >= 0:
363 testarg = argstr[:delimiter_pos]
364 testval = argstr[delimiter_pos+1:]
366 if testarg.lower() in self.arg_dict:
367 arg = self.arg_dict[testarg.lower()]
368 argstr = testarg
369 value = testval
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]
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
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)
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)
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)
403 if value is not None:
404 value = self.StringToValue(value, arg.type, argstr)
406 arg.argstr = argstr
407 arg.value = value
408 arg.present = True
410 # end method ParseNextArgument
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
418 elif type == 'int':
419 try:
420 value = int(value)
421 except ValueError:
422 raise ParseError
424 elif type == 'readfile':
425 if not os.path.isfile(value):
426 raise ParseError("'%s': '%s' does not exist" % (argstr, value))
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
436 else:
437 raise ValueError("Unknown type: '%s'" % type)
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
447 return value
449 def SortArgs(self):
450 """Returns a method that can be passed to sort() to sort arguments."""
452 def ArgSorter(arg1, arg2):
453 """Helper for sorting arguments in the usage string.
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.
459 Args:
460 arg1: the first argument to compare
461 arg2: the second argument to compare
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.
467 return ((arg2.positional-arg1.positional)*2 +
468 (arg2.required-arg1.required))
469 return ArgSorter
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]
475 initial_indent = "Usage: %s %s " % (self.cmdline.prog, name)
476 subsequent_indent = " " * len(initial_indent)
478 sorted_args = self.args[:]
479 sorted_args.sort(self.SortArgs())
481 return textwrap.fill(
482 " ".join([arg.GetUsageString() for arg in sorted_args]), width,
483 initial_indent=initial_indent,
484 subsequent_indent=subsequent_indent)
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())
491 return "\n".join([arg.GetHelpString(width) for arg in sorted_args])
493 # end class Command
496 class CommandLine(object):
497 """Parse a command line, extracting a command and its arguments."""
499 def __init__(self):
500 self.commands = []
501 self.cmd_dict = {}
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)
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)
514 self.Exit = sys.exit # override this if you don't want the script to halt
515 # on error or on display of help
517 self.out = sys.stdout # override these if you want to redirect
518 self.err = sys.stderr # output or error messages
520 def AddCommand(self, names, helptext, validator=None, impl=None):
521 """Add a new command to the parser.
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
529 Raises:
530 ValueError: raised if command already added
532 Returns:
533 The new command
535 if IsString(names): names = [names]
537 for name in names:
538 if name in self.cmd_dict:
539 raise ValueError("%s is already a command"%name)
541 cmd = Command(names, helptext, validator, impl)
542 cmd.cmdline = self
544 self.commands.append(cmd)
545 for name in names:
546 self.cmd_dict[name.lower()] = cmd
548 return cmd
550 def GetUsageString(self):
551 """Returns simple usage instructions."""
552 return "Type '%s help' for usage." % self.prog
554 def ParseCommandLine(self, argv=None, prog=None, execute=True):
555 """Does the work of parsing a command line.
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
562 Returns:
563 The command that was executed
565 if argv is None: argv = sys.argv[1:]
566 if prog is None: prog = os.path.basename(sys.argv[0]).split('.')[0]
568 # Store off our parameters, we may need them someday
569 self.argv = argv
570 self.prog = prog
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
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
586 self.command = self.cmd_dict[self.command_string]
588 # "rargs" = remaining (unparsed) arguments
589 # "largs" = already parsed, "left" of the read head
590 self.rargs = argv[1:]
591 self.largs = []
593 # let the command object do the parsing
594 self.command.ParseArguments()
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")
602 self.Exit()
604 elif execute and self.command.impl:
605 self.command.impl(self.command)
607 return self.command
609 def __getitem__(self, key):
610 return self.cmd_dict[key]
612 def __iter__(self):
613 return self.cmd_dict.__iter__()
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'])
622 def DoHelpCommand(command):
623 """Executed when the command is 'help'."""
624 out = command.cmdline.out
625 width = command['--width']
627 if 'command' not in command:
628 out.write(command.GetUsageString())
629 out.write("\n\n")
631 indent = 5
632 gutter = 2
634 command_width = (
635 max([len(cmd.names[0]) for cmd in command.cmdline.commands]) + gutter)
637 for cmd in command.cmdline.commands:
638 cmd_name = cmd.names[0]
640 initial_indent = (" "*indent + cmd_name + " "*
641 (command_width+gutter-len(cmd_name)))
642 subsequent_indent = " "*(indent+command_width+gutter)
644 out.write(textwrap.fill(cmd.helptext, width,
645 initial_indent=initial_indent,
646 subsequent_indent=subsequent_indent))
647 out.write("\n")
649 out.write("\n")
651 else:
652 help_cmd = command.cmdline[command['command']]
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")
661 command.cmdline.Exit()
664 def main():
665 # If we're invoked rather than imported, run some tests
666 cmdline = CommandLine()
668 # Since we're testing, override Exit()
669 def TestExit():
670 pass
671 cmdline.Exit = TestExit
673 # Actually, while we're at it, let's override error output too
674 cmdline.err = open(os.path.devnull, "w")
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)
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"])
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"])
699 # dependent argument
700 test.AddArgument("--dependent", "dependent argument")
701 test.AddDependency("--dependent", "--int")
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')
708 test.AddArgument("--req1", "part of a required group", type='flag')
709 test.AddArgument("--req2", "part 2 of a required group", type='flag')
711 test.AddRequiredGroup(["--req1", "--req2"])
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)
718 test.AddArgument("--int", "this argument already exists")
720 test.AddDependency("--int", "--doesntexist")
722 test.AddMutualDependency(["--doesntexist", "--mutdep2"])
724 test.AddMutualExclusion(["--doesntexist", "--mutex2"])
726 test.AddArgument("--reqflag", "required flag", required=True, type='flag')
728 test.AddRequiredGroup(["--req1", "--doesntexist"])
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)
744 # Let's do some parsing! first, the minimal success line:
745 MIN = "test --reqint 123 param1 --req1 "
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)
786 badtests = 0
788 for (test, expected_failures) in test_lines:
789 cmdline.ParseCommandLine([x.strip() for x in test.strip().split(" ")])
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
796 print "%d failed out of %d tests" % (badtests, len(test_lines))
798 cmdline.ParseCommandLine(["help", "test"])
801 if __name__ == "__main__":
802 sys.exit(main())