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