15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#!/usr/bin/env python
25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Copyright (c) 2011 The Chromium Authors. All rights reserved.
35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Use of this source code is governed by a BSD-style license that can be
45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# found in the LICENSE file.
55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)"""Parse a command line, retrieving a command and its arguments.
75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)Supports the concept of command line commands, each with its own set
95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)of arguments. Supports dependent arguments and mutually exclusive arguments.
105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)Basically, a better optparse. I took heed of epg's WHINE() in gvn.cmdline
115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)and dumped optparse in favor of something better.
125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)"""
135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import os.path
155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import re
165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import string
175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import sys
185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import textwrap
195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import types
205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def IsString(var):
235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """Little helper function to see if a variable is a string."""
245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return type(var) in types.StringTypes
255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class ParseError(Exception):
285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """Encapsulates errors from parsing, string arg is description."""
295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  pass
305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class Command(object):
335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """Implements a single command."""
345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def __init__(self, names, helptext, validator=None, impl=None):
365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Initializes Command from names and helptext, plus optional callables.
375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      names:       command name, or list of synonyms
405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      helptext:    brief string description of the command
415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      validator:   callable for custom argument validation
425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                   Should raise ParseError if it wants
435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      impl:        callable to be invoked when command is called
445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.names = names
465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.validator = validator
475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.helptext = helptext
485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.impl = impl
495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.args = []
505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.required_groups = []
515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.arg_dict = {}
525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.positional_args = []
535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.cmdline = None
545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  class Argument(object):
565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Encapsulates an argument to a command."""
575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    VALID_TYPES = ['string', 'readfile', 'int', 'flag', 'coords']
585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    TYPES_WITH_VALUES = ['string', 'readfile', 'int', 'coords']
595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def __init__(self, names, helptext, type, metaname,
615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                 required, default, positional):
625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      """Command-line argument to a command.
635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      Args:
655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        names:       argument name, or list of synonyms
665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        helptext:    brief description of the argument
675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        type:        type of the argument. Valid values include:
685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                          string - a string
695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                          readfile - a file which must exist and be available
705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                            for reading
715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                          int - an integer
725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                          flag - an optional flag (bool)
735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                          coords - (x,y) where x and y are ints
745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        metaname:    Name to display for value in help, inferred if not
755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                     specified
765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        required:    True if argument must be specified
775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        default:     Default value if not specified
785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        positional:  Argument specified by location, not name
795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      Raises:
815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        ValueError: the argument name is invalid for some reason
825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      """
835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if type not in Command.Argument.VALID_TYPES:
845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        raise ValueError("Invalid type: %r" % type)
855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if required and default is not None:
875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        raise ValueError("required and default are mutually exclusive")
885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if required and type == 'flag':
905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        raise ValueError("A required flag? Give me a break.")
915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if metaname and type not in Command.Argument.TYPES_WITH_VALUES:
935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        raise ValueError("Type %r can't have a metaname" % type)
945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # If no metaname is provided, infer it: use the alphabetical characters
965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # of the last provided name
975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if not metaname and type in Command.Argument.TYPES_WITH_VALUES:
985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        metaname = (
995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          names[-1].lstrip(string.punctuation + string.whitespace).upper())
1005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self.names = names
1025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self.helptext = helptext
1035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self.type = type
1045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self.required = required
1055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self.default = default
1065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self.positional = positional
1075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self.metaname = metaname
1085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self.mutex = []          # arguments that are mutually exclusive with
1105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                               # this one
1115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self.depends = []        # arguments that must be present for this
1125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                               # one to be valid
1135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self.present = False     # has this argument been specified?
1145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def AddDependency(self, arg):
1165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      """Makes this argument dependent on another argument.
1175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      Args:
1195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        arg: name of the argument this one depends on
1205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      """
1215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if arg not in self.depends:
1225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self.depends.append(arg)
1235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def AddMutualExclusion(self, arg):
1255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      """Makes this argument invalid if another is specified.
1265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      Args:
1285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        arg: name of the mutually exclusive argument.
1295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      """
1305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if arg not in self.mutex:
1315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self.mutex.append(arg)
1325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def GetUsageString(self):
1345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      """Returns a brief string describing the argument's usage."""
1355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if not self.positional:
1365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        string = self.names[0]
1375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if self.type in Command.Argument.TYPES_WITH_VALUES:
1385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          string += "="+self.metaname
1395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      else:
1405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        string = self.metaname
1415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if not self.required:
1435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        string = "["+string+"]"
1445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return string
1465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def GetNames(self):
1485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      """Returns a string containing a list of the arg's names."""
1495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if self.positional:
1505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return self.metaname
1515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      else:
1525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return ", ".join(self.names)
1535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def GetHelpString(self, width=80, indent=5, names_width=20, gutter=2):
1555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      """Returns a help string including help for all the arguments."""
1565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      names = [" "*indent + line +" "*(names_width-len(line)) for line in
1575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)               textwrap.wrap(self.GetNames(), names_width)]
1585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      helpstring = textwrap.wrap(self.helptext, width-indent-names_width-gutter)
1605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if len(names) < len(helpstring):
1625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        names += [" "*(indent+names_width)]*(len(helpstring)-len(names))
1635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if len(helpstring) < len(names):
1655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        helpstring += [""]*(len(names)-len(helpstring))
1665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return "\n".join([name_line + " "*gutter + help_line for
1685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                        name_line, help_line in zip(names, helpstring)])
1695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def __repr__(self):
1715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if self.present:
1725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        string = '= %r' % self.value
1735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      else:
1745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        string = "(absent)"
1755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return "Argument %s '%s'%s" % (self.type, self.names[0], string)
1775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # end of nested class Argument
1795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def AddArgument(self, names, helptext, type="string", metaname=None,
1815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                  required=False, default=None, positional=False):
1825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Command-line argument to a command.
1835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
1855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      names:      argument name, or list of synonyms
1865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      helptext:   brief description of the argument
1875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      type:       type of the argument
1885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      metaname:   Name to display for value in help, inferred if not
1895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      required:   True if argument must be specified
1905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      default:    Default value if not specified
1915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      positional: Argument specified by location, not name
1925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Raises:
1945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      ValueError: the argument already exists or is invalid
1955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Returns:
1975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      The newly-created argument
1985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
1995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if IsString(names): names = [names]
2005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    names = [name.lower() for name in names]
2025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for name in names:
2045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if name in self.arg_dict:
2055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        raise ValueError("%s is already an argument"%name)
2065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (positional and required and
2085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        [arg for arg in self.args if arg.positional] and
2095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        not [arg for arg in self.args if arg.positional][-1].required):
2105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      raise ValueError(
2115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        "A required positional argument may not follow an optional one.")
2125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    arg = Command.Argument(names, helptext, type, metaname,
2145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                           required, default, positional)
2155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.args.append(arg)
2175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for name in names:
2195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self.arg_dict[name] = arg
2205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return arg
2225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def GetArgument(self, name):
2245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Return an argument from a name."""
2255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return self.arg_dict[name.lower()]
2265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def AddMutualExclusion(self, args):
2285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Specifies that a list of arguments are mutually exclusive."""
2295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if len(args) < 2:
2305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      raise ValueError("At least two arguments must be specified.")
2315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    args = [arg.lower() for arg in args]
2335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for index in xrange(len(args)-1):
2355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      for index2 in xrange(index+1, len(args)):
2365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self.arg_dict[args[index]].AddMutualExclusion(self.arg_dict[args[index2]])
2375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def AddDependency(self, dependent, depends_on):
2395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Specifies that one argument may only be present if another is.
2405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
2425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      dependent:  the name of the dependent argument
2435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      depends_on: the name of the argument on which it depends
2445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
2455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.arg_dict[dependent.lower()].AddDependency(
2465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self.arg_dict[depends_on.lower()])
2475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def AddMutualDependency(self, args):
2495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Specifies that a list of arguments are all mutually dependent."""
2505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if len(args) < 2:
2515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      raise ValueError("At least two arguments must be specified.")
2525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    args = [arg.lower() for arg in args]
2545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for (arg1, arg2) in [(arg1, arg2) for arg1 in args for arg2 in args]:
2565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if arg1 == arg2: continue
2575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self.arg_dict[arg1].AddDependency(self.arg_dict[arg2])
2585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def AddRequiredGroup(self, args):
2605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Specifies that at least one of the named arguments must be present."""
2615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if len(args) < 2:
2625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      raise ValueError("At least two arguments must be in a required group.")
2635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    args = [self.arg_dict[arg.lower()] for arg in args]
2655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.required_groups.append(args)
2675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def ParseArguments(self):
2695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Given a command line, parse and validate the arguments."""
2705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # reset all the arguments before we parse
2725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for arg in self.args:
2735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      arg.present = False
2745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      arg.value = None
2755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.parse_errors = []
2775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # look for arguments remaining on the command line
2795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    while len(self.cmdline.rargs):
2805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      try:
2815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self.ParseNextArgument()
2825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      except ParseError, e:
2835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self.parse_errors.append(e.args[0])
2845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # after all the arguments are parsed, check for problems
2865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for arg in self.args:
2875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if not arg.present and arg.required:
2885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self.parse_errors.append("'%s': required parameter was missing"
2895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                 % arg.names[0])
2905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if not arg.present and arg.default:
2925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        arg.present = True
2935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        arg.value = arg.default
2945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if arg.present:
2965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        for mutex in arg.mutex:
2975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          if mutex.present:
2985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            self.parse_errors.append(
2995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)              "'%s', '%s': arguments are mutually exclusive" %
3005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)              (arg.argstr, mutex.argstr))
3015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        for depend in arg.depends:
3035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          if not depend.present:
3045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            self.parse_errors.append("'%s': '%s' must be specified as well" %
3055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                     (arg.argstr, depend.names[0]))
3065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # check for required groups
3085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for group in self.required_groups:
3095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if not [arg for arg in group if arg.present]:
3105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self.parse_errors.append("%s: at least one must be present" %
3115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                         (", ".join(["'%s'" % arg.names[-1] for arg in group])))
3125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # if we have any validators, invoke them
3145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if not self.parse_errors and self.validator:
3155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      try:
3165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self.validator(self)
3175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      except ParseError, e:
3185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self.parse_errors.append(e.args[0])
3195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Helper methods so you can treat the command like a dict
3215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def __getitem__(self, key):
3225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    arg = self.arg_dict[key.lower()]
3235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if arg.type == 'flag':
3255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return arg.present
3265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    else:
3275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return arg.value
3285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def __iter__(self):
3305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return [arg for arg in self.args if arg.present].__iter__()
3315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def ArgumentPresent(self, key):
3335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Tests if an argument exists and has been specified."""
3345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return key.lower() in self.arg_dict and self.arg_dict[key.lower()].present
3355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def __contains__(self, key):
3375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return self.ArgumentPresent(key)
3385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def ParseNextArgument(self):
3405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Find the next argument in the command line and parse it."""
3415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    arg = None
3425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    value = None
3435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    argstr = self.cmdline.rargs.pop(0)
3445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # First check: is this a literal argument?
3465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if argstr.lower() in self.arg_dict:
3475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      arg = self.arg_dict[argstr.lower()]
3485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if arg.type in Command.Argument.TYPES_WITH_VALUES:
3495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if len(self.cmdline.rargs):
3505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          value = self.cmdline.rargs.pop(0)
3515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Second check: is this of the form "arg=val" or "arg:val"?
3535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if arg is None:
3545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      delimiter_pos = -1
3555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      for delimiter in [':', '=']:
3575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        pos = argstr.find(delimiter)
3585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if pos >= 0:
3595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          if delimiter_pos < 0 or pos < delimiter_pos:
3605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            delimiter_pos = pos
3615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if delimiter_pos >= 0:
3635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        testarg = argstr[:delimiter_pos]
3645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        testval = argstr[delimiter_pos+1:]
3655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if testarg.lower() in self.arg_dict:
3675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          arg = self.arg_dict[testarg.lower()]
3685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          argstr = testarg
3695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          value = testval
3705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Third check: does this begin an argument?
3725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if arg is None:
3735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      for key in self.arg_dict.iterkeys():
3745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if (len(key) < len(argstr) and
3755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            self.arg_dict[key].type in Command.Argument.TYPES_WITH_VALUES and
3765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            argstr[:len(key)].lower() == key):
3775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          value = argstr[len(key):]
3785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          argstr = argstr[:len(key)]
3795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          arg = self.arg_dict[argstr]
3805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Fourth check: do we have any positional arguments available?
3825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if arg is None:
3835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      for positional_arg in [
3845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          testarg for testarg in self.args if testarg.positional]:
3855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if not positional_arg.present:
3865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          arg = positional_arg
3875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          value = argstr
3885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          argstr = positional_arg.names[0]
3895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          break
3905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Push the retrieved argument/value onto the largs stack
3925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if argstr: self.cmdline.largs.append(argstr)
3935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if value:  self.cmdline.largs.append(value)
3945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # If we've made it this far and haven't found an arg, give up
3965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if arg is None:
3975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      raise ParseError("Unknown argument: '%s'" % argstr)
3985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Convert the value, if necessary
4005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if arg.type in Command.Argument.TYPES_WITH_VALUES and value is None:
4015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      raise ParseError("Argument '%s' requires a value" % argstr)
4025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if value is not None:
4045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      value = self.StringToValue(value, arg.type, argstr)
4055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    arg.argstr = argstr
4075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    arg.value = value
4085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    arg.present = True
4095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # end method ParseNextArgument
4115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def StringToValue(self, value, type, argstr):
4135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Convert a string from the command line to a value type."""
4145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    try:
4155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if type == 'string':
4165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        pass  # leave it be
4175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      elif type == 'int':
4195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        try:
4205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          value = int(value)
4215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        except ValueError:
4225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          raise ParseError
4235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      elif type == 'readfile':
4255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if not os.path.isfile(value):
4265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          raise ParseError("'%s': '%s' does not exist" % (argstr, value))
4275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      elif type == 'coords':
4295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        try:
4305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          value = [int(val) for val in
4315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                   re.match("\(\s*(\d+)\s*\,\s*(\d+)\s*\)\s*\Z", value).
4325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                   groups()]
4335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        except AttributeError:
4345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          raise ParseError
4355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      else:
4375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        raise ValueError("Unknown type: '%s'" % type)
4385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    except ParseError, e:
4405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # The bare exception is raised in the generic case; more specific errors
4415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # will arrive with arguments and should just be reraised
4425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if not e.args:
4435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        e = ParseError("'%s': unable to convert '%s' to type '%s'" %
4445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                       (argstr, value, type))
4455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      raise e
4465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return value
4485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def SortArgs(self):
4505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Returns a method that can be passed to sort() to sort arguments."""
4515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def ArgSorter(arg1, arg2):
4535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      """Helper for sorting arguments in the usage string.
4545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      Positional arguments come first, then required arguments,
4565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      then optional arguments. Pylint demands this trivial function
4575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      have both Args: and Returns: sections, sigh.
4585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      Args:
4605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        arg1: the first argument to compare
4615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        arg2: the second argument to compare
4625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      Returns:
4645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        -1 if arg1 should be sorted first, +1 if it should be sorted second,
4655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        and 0 if arg1 and arg2 have the same sort level.
4665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      """
4675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return ((arg2.positional-arg1.positional)*2 +
4685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)              (arg2.required-arg1.required))
4695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return ArgSorter
4705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def GetUsageString(self, width=80, name=None):
4725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Gets a string describing how the command is used."""
4735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if name is None: name = self.names[0]
4745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    initial_indent = "Usage: %s %s " % (self.cmdline.prog, name)
4765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    subsequent_indent = " " * len(initial_indent)
4775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    sorted_args = self.args[:]
4795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    sorted_args.sort(self.SortArgs())
4805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return textwrap.fill(
4825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      " ".join([arg.GetUsageString() for arg in sorted_args]), width,
4835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      initial_indent=initial_indent,
4845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      subsequent_indent=subsequent_indent)
4855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def GetHelpString(self, width=80):
4875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Returns a list of help strings for all this command's arguments."""
4885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    sorted_args = self.args[:]
4895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    sorted_args.sort(self.SortArgs())
4905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return "\n".join([arg.GetHelpString(width) for arg in sorted_args])
4925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # end class Command
4945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class CommandLine(object):
4975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """Parse a command line, extracting a command and its arguments."""
4985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def __init__(self):
5005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.commands = []
5015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.cmd_dict = {}
5025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Add the help command to the parser
5045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    help_cmd = self.AddCommand(["help", "--help", "-?", "-h"],
5055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                               "Displays help text for a command",
5065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                               ValidateHelpCommand,
5075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                               DoHelpCommand)
5085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    help_cmd.AddArgument(
5105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      "command", "Command to retrieve help for", positional=True)
5115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    help_cmd.AddArgument(
5125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      "--width", "Width of the output", type='int', default=80)
5135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.Exit = sys.exit   # override this if you don't want the script to halt
5155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                           # on error or on display of help
5165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.out = sys.stdout  # override these if you want to redirect
5185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.err = sys.stderr  # output or error messages
5195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def AddCommand(self, names, helptext, validator=None, impl=None):
5215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Add a new command to the parser.
5225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
5245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      names:       command name, or list of synonyms
5255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      helptext:    brief string description of the command
5265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      validator:   method to validate a command's arguments
5275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      impl:        callable to be invoked when command is called
5285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Raises:
5305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      ValueError: raised if command already added
5315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Returns:
5335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      The new command
5345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
5355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if IsString(names): names = [names]
5365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for name in names:
5385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if name in self.cmd_dict:
5395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        raise ValueError("%s is already a command"%name)
5405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    cmd = Command(names, helptext, validator, impl)
5425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    cmd.cmdline = self
5435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.commands.append(cmd)
5455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for name in names:
5465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self.cmd_dict[name.lower()] = cmd
5475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return cmd
5495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def GetUsageString(self):
5515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Returns simple usage instructions."""
5525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return "Type '%s help' for usage." % self.prog
5535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def ParseCommandLine(self, argv=None, prog=None, execute=True):
5555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Does the work of parsing a command line.
5565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Args:
5585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      argv:     list of arguments, defaults to sys.args[1:]
5595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      prog:     name of the command, defaults to the base name of the script
5605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      execute:  if false, just parse, don't invoke the 'impl' member
5615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Returns:
5635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      The command that was executed
5645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
5655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if argv is None: argv = sys.argv[1:]
5665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if prog is None: prog = os.path.basename(sys.argv[0]).split('.')[0]
5675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Store off our parameters, we may need them someday
5695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.argv = argv
5705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.prog = prog
5715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # We shouldn't be invoked without arguments, that's just lame
5735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if not len(argv):
5745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self.out.writelines(self.GetUsageString())
5755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self.Exit()
5765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return None   # in case the client overrides Exit
5775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Is it a valid command?
5795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.command_string = argv[0].lower()
5805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if not self.command_string in self.cmd_dict:
5815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self.err.write("Unknown command: '%s'\n\n" % self.command_string)
5825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self.out.write(self.GetUsageString())
5835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self.Exit()
5845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return None   # in case the client overrides Exit
5855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.command = self.cmd_dict[self.command_string]
5875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # "rargs" = remaining (unparsed) arguments
5895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # "largs" = already parsed, "left" of the read head
5905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.rargs = argv[1:]
5915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.largs = []
5925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # let the command object do the parsing
5945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.command.ParseArguments()
5955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if self.command.parse_errors:
5975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # there were errors, output the usage string and exit
5985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self.err.write(self.command.GetUsageString()+"\n\n")
5995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self.err.write("\n".join(self.command.parse_errors))
6005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self.err.write("\n\n")
6015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self.Exit()
6035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    elif execute and self.command.impl:
6055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self.command.impl(self.command)
6065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return self.command
6085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def __getitem__(self, key):
6105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return self.cmd_dict[key]
6115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def __iter__(self):
6135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return self.cmd_dict.__iter__()
6145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def ValidateHelpCommand(command):
6175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """Checks to make sure an argument to 'help' is a valid command."""
6185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if 'command' in command and command['command'] not in command.cmdline:
6195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    raise ParseError("'%s': unknown command" % command['command'])
6205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def DoHelpCommand(command):
6235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """Executed when the command is 'help'."""
6245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  out = command.cmdline.out
6255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  width = command['--width']
6265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if 'command' not in command:
6285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    out.write(command.GetUsageString())
6295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    out.write("\n\n")
6305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    indent = 5
6325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    gutter = 2
6335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    command_width = (
6355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      max([len(cmd.names[0]) for cmd in command.cmdline.commands]) + gutter)
6365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for cmd in command.cmdline.commands:
6385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      cmd_name = cmd.names[0]
6395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      initial_indent = (" "*indent + cmd_name + " "*
6415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                        (command_width+gutter-len(cmd_name)))
6425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      subsequent_indent = " "*(indent+command_width+gutter)
6435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      out.write(textwrap.fill(cmd.helptext, width,
6455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                              initial_indent=initial_indent,
6465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                              subsequent_indent=subsequent_indent))
6475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      out.write("\n")
6485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    out.write("\n")
6505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  else:
6525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    help_cmd = command.cmdline[command['command']]
6535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    out.write(textwrap.fill(help_cmd.helptext, width))
6555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    out.write("\n\n")
6565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    out.write(help_cmd.GetUsageString(width=width))
6575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    out.write("\n\n")
6585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    out.write(help_cmd.GetHelpString(width=width))
6595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    out.write("\n")
6605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    command.cmdline.Exit()
6625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def main():
6655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # If we're invoked rather than imported, run some tests
6665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  cmdline = CommandLine()
6675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Since we're testing, override Exit()
6695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def TestExit():
6705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    pass
6715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  cmdline.Exit = TestExit
6725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Actually, while we're at it, let's override error output too
6745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  cmdline.err = open(os.path.devnull, "w")
6755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  test = cmdline.AddCommand(["test", "testa", "testb"], "test command")
6775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  test.AddArgument(["-i", "--int", "--integer", "--optint", "--optionalint"],
6785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                   "optional integer parameter", type='int')
6795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  test.AddArgument("--reqint", "required integer parameter", type='int',
6805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                   required=True)
6815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  test.AddArgument("pos1", "required positional argument", positional=True,
6825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                   required=True)
6835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  test.AddArgument("pos2", "optional positional argument", positional=True)
6845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  test.AddArgument("pos3", "another optional positional arg",
6855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                   positional=True)
6865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # mutually dependent arguments
6885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  test.AddArgument("--mutdep1", "mutually dependent parameter 1")
6895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  test.AddArgument("--mutdep2", "mutually dependent parameter 2")
6905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  test.AddArgument("--mutdep3", "mutually dependent parameter 3")
6915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  test.AddMutualDependency(["--mutdep1", "--mutdep2", "--mutdep3"])
6925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # mutually exclusive arguments
6945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  test.AddArgument("--mutex1", "mutually exclusive parameter 1")
6955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  test.AddArgument("--mutex2", "mutually exclusive parameter 2")
6965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  test.AddArgument("--mutex3", "mutually exclusive parameter 3")
6975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  test.AddMutualExclusion(["--mutex1", "--mutex2", "--mutex3"])
6985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # dependent argument
7005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  test.AddArgument("--dependent", "dependent argument")
7015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  test.AddDependency("--dependent", "--int")
7025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # other argument types
7045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  test.AddArgument("--file", "filename argument", type='readfile')
7055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  test.AddArgument("--coords", "coordinate argument", type='coords')
7065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  test.AddArgument("--flag", "flag argument", type='flag')
7075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  test.AddArgument("--req1", "part of a required group", type='flag')
7095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  test.AddArgument("--req2", "part 2 of a required group", type='flag')
7105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  test.AddRequiredGroup(["--req1", "--req2"])
7125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # a few failure cases
7145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  exception_cases = """
7155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    test.AddArgument("failpos", "can't have req'd pos arg after opt",
7165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)       positional=True, required=True)
7175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)+++
7185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    test.AddArgument("--int", "this argument already exists")
7195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)+++
7205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    test.AddDependency("--int", "--doesntexist")
7215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)+++
7225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    test.AddMutualDependency(["--doesntexist", "--mutdep2"])
7235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)+++
7245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    test.AddMutualExclusion(["--doesntexist", "--mutex2"])
7255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)+++
7265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    test.AddArgument("--reqflag", "required flag", required=True, type='flag')
7275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)+++
7285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    test.AddRequiredGroup(["--req1", "--doesntexist"])
7295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)"""
7305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  for exception_case in exception_cases.split("+++"):
7315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    try:
7325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      exception_case = exception_case.strip()
7335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      exec exception_case     # yes, I'm using exec, it's just for a test.
7345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    except ValueError:
7355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # this is expected
7365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      pass
7375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    except KeyError:
7385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # ...and so is this
7395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      pass
7405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    else:
7415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      print ("FAILURE: expected an exception for '%s'"
7425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)             " and didn't get it" % exception_case)
7435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Let's do some parsing! first, the minimal success line:
7455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  MIN = "test --reqint 123 param1 --req1 "
7465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # tuples of (command line, expected error count)
7485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  test_lines = [
7495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    ("test --int 3 foo --req1", 1),   # missing required named parameter
7505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    ("test --reqint 3 --req1", 1),    # missing required positional parameter
7515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    (MIN, 0),                         # success!
7525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    ("test param1 --reqint 123 --req1", 0),  # success, order shouldn't matter
7535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    ("test param1 --reqint 123 --req2", 0),  # success, any of required group ok
7545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    (MIN+"param2", 0),                # another positional parameter is okay
7555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    (MIN+"param2 param3", 0),         # and so are three
7565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    (MIN+"param2 param3 param4", 1),  # but four are just too many
7575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    (MIN+"--int", 1),                 # where's the value?
7585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    (MIN+"--int 456", 0),             # this is fine
7595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    (MIN+"--int456", 0),              # as is this
7605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    (MIN+"--int:456", 0),             # and this
7615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    (MIN+"--int=456", 0),             # and this
7625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    (MIN+"--file c:\\windows\\system32\\kernel32.dll", 0),  # yup
7635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    (MIN+"--file c:\\thisdoesntexist", 1),           # nope
7645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    (MIN+"--mutdep1 a", 2),                          # no!
7655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    (MIN+"--mutdep2 b", 2),                          # also no!
7665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    (MIN+"--mutdep3 c", 2),                          # dream on!
7675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    (MIN+"--mutdep1 a --mutdep2 b", 2),              # almost!
7685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    (MIN+"--mutdep1 a --mutdep2 b --mutdep3 c", 0),  # yes
7695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    (MIN+"--mutex1 a", 0),                           # yes
7705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    (MIN+"--mutex2 b", 0),                           # yes
7715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    (MIN+"--mutex3 c", 0),                           # fine
7725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    (MIN+"--mutex1 a --mutex2 b", 1),                # not fine
7735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    (MIN+"--mutex1 a --mutex2 b --mutex3 c", 3),     # even worse
7745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    (MIN+"--dependent 1", 1),                        # no
7755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    (MIN+"--dependent 1 --int 2", 0),                # ok
7765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    (MIN+"--int abc", 1),                            # bad type
7775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    (MIN+"--coords abc", 1),                         # also bad
7785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    (MIN+"--coords (abc)", 1),                       # getting warmer
7795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    (MIN+"--coords (abc,def)", 1),                   # missing something
7805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    (MIN+"--coords (123)", 1),                       # ooh, so close
7815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    (MIN+"--coords (123,def)", 1),                   # just a little farther
7825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    (MIN+"--coords (123,456)", 0),                   # finally!
7835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    ("test --int 123 --reqint=456 foo bar --coords(42,88) baz --req1", 0)
7845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    ]
7855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  badtests = 0
7875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  for (test, expected_failures) in test_lines:
7895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    cmdline.ParseCommandLine([x.strip() for x in test.strip().split(" ")])
7905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if not len(cmdline.command.parse_errors) == expected_failures:
7925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      print "FAILED:\n  issued: '%s'\n  expected: %d\n  received: %d\n\n" % (
7935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        test, expected_failures, len(cmdline.command.parse_errors))
7945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      badtests += 1
7955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  print "%d failed out of %d tests" % (badtests, len(test_lines))
7975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  cmdline.ParseCommandLine(["help", "test"])
7995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
8005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
8015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)if __name__ == "__main__":
8025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  sys.exit(main())
803