DPyGetOpt.py
671 lines
| 20.6 KiB
| text/x-python
|
PythonLexer
/ IPython / DPyGetOpt.py
fperez
|
r0 | # -*- coding: utf-8 -*- | ||
"""DPyGetOpt -- Demiurge Python GetOptions Module | ||||
$Id: DPyGetOpt.py 389 2004-10-09 07:59:30Z fperez $ | ||||
This module is modeled after perl's Getopt::Long module-- which | ||||
is, in turn, modeled after GNU's extended getopt() function. | ||||
Upon instantiation, the option specification should be a sequence | ||||
(list) of option definitions. | ||||
Options that take no arguments should simply contain the name of | ||||
the option. If a ! is post-pended, the option can be negated by | ||||
prepending 'no'; ie 'debug!' specifies that -debug and -nodebug | ||||
should be accepted. | ||||
Mandatory arguments to options are specified using a postpended | ||||
'=' + a type specifier. '=s' specifies a mandatory string | ||||
argument, '=i' specifies a mandatory integer argument, and '=f' | ||||
specifies a mandatory real number. In all cases, the '=' can be | ||||
substituted with ':' to specify that the argument is optional. | ||||
Dashes '-' in option names are allowed. | ||||
If an option has the character '@' postpended (after the | ||||
argumentation specification), it can appear multiple times within | ||||
each argument list that is processed. The results will be stored | ||||
in a list. | ||||
The option name can actually be a list of names separated by '|' | ||||
characters; ie-- 'foo|bar|baz=f@' specifies that all -foo, -bar, | ||||
and -baz options that appear on within the parsed argument list | ||||
must have a real number argument and that the accumulated list | ||||
of values will be available under the name 'foo' | ||||
$Id: DPyGetOpt.py 389 2004-10-09 07:59:30Z fperez $""" | ||||
#***************************************************************************** | ||||
# | ||||
# Copyright (c) 2001 Bill Bumgarner <bbum@friday.com> | ||||
# | ||||
# | ||||
# Published under the terms of the MIT license, hereby reproduced: | ||||
# | ||||
# Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
# of this software and associated documentation files (the "Software"), to | ||||
# deal in the Software without restriction, including without limitation the | ||||
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or | ||||
# sell copies of the Software, and to permit persons to whom the Software is | ||||
# furnished to do so, subject to the following conditions: | ||||
# | ||||
# The above copyright notice and this permission notice shall be included in | ||||
# all copies or substantial portions of the Software. | ||||
# | ||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | ||||
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS | ||||
# IN THE SOFTWARE. | ||||
# | ||||
#***************************************************************************** | ||||
__author__ = 'Bill Bumgarner <bbum@friday.com>' | ||||
__license__ = 'MIT' | ||||
__version__ = '1.2' | ||||
# Modified to use re instead of regex and regsub modules. | ||||
# 2001/5/7, Jonathan Hogg <jonathan@onegoodidea.com> | ||||
import string | ||||
import re | ||||
import sys | ||||
import types | ||||
arg_error = 'DPyGetOpt Argument Error' | ||||
spec_error = 'DPyGetOpt Specification Error' | ||||
term_error = 'DPyGetOpt Termination Error' | ||||
specificationExpr = re.compile('(?P<required>.)(?P<type>.)(?P<multi>@?)') | ||||
ArgRequired = 'Requires an Argument' | ||||
ArgOptional = 'Argument Optional' | ||||
# The types modules is not used for these identifiers because there | ||||
# is no identifier for 'boolean' or 'generic' | ||||
StringArgType = 'String Argument Type' | ||||
IntegerArgType = 'Integer Argument Type' | ||||
RealArgType = 'Real Argument Type' | ||||
BooleanArgType = 'Boolean Argument Type' | ||||
GenericArgType = 'Generic Argument Type' | ||||
# dictionary of conversion functions-- boolean and generic options | ||||
# do not accept arguments and do not need conversion functions; | ||||
# the identity function is used purely for convenience. | ||||
ConversionFunctions = { | ||||
StringArgType : lambda x: x, | ||||
IntegerArgType : string.atoi, | ||||
RealArgType : string.atof, | ||||
BooleanArgType : lambda x: x, | ||||
GenericArgType : lambda x: x, | ||||
} | ||||
class DPyGetOpt: | ||||
def __init__(self, spec = None, terminators = ['--']): | ||||
""" | ||||
Declare and intialize instance variables | ||||
Yes, declaration is not necessary... but one of the things | ||||
I sorely miss from C/Obj-C is the concept of having an | ||||
interface definition that clearly declares all instance | ||||
variables and methods without providing any implementation | ||||
details. it is a useful reference! | ||||
all instance variables are initialized to 0/Null/None of | ||||
the appropriate type-- not even the default value... | ||||
""" | ||||
# sys.stderr.write(string.join(spec) + "\n") | ||||
self.allowAbbreviations = 1 # boolean, 1 if abbreviations will | ||||
# be expanded | ||||
self.freeValues = [] # list, contains free values | ||||
self.ignoreCase = 0 # boolean, YES if ignoring case | ||||
self.needsParse = 0 # boolean, YES if need to reparse parameter spec | ||||
self.optionNames = {} # dict, all option names-- value is index of tuple | ||||
self.optionStartExpr = None # regexp defining the start of an option (ie; '-', '--') | ||||
self.optionTuples = [] # list o' tuples containing defn of options AND aliases | ||||
self.optionValues = {} # dict, option names (after alias expansion) -> option value(s) | ||||
self.orderMixed = 0 # boolean, YES if options can be mixed with args | ||||
self.posixCompliance = 0 # boolean, YES indicates posix like behaviour | ||||
self.spec = [] # list, raw specs (in case it must be reparsed) | ||||
self.terminators = terminators # list, strings that terminate argument processing | ||||
self.termValues = [] # list, values after terminator | ||||
self.terminator = None # full name of terminator that ended | ||||
# option processing | ||||
# set up defaults | ||||
self.setPosixCompliance() | ||||
self.setIgnoreCase() | ||||
self.setAllowAbbreviations() | ||||
# parse spec-- if present | ||||
if spec: | ||||
self.parseConfiguration(spec) | ||||
def setPosixCompliance(self, aFlag = 0): | ||||
""" | ||||
Enables and disables posix compliance. | ||||
When enabled, '+' can be used as an option prefix and free | ||||
values can be mixed with options. | ||||
""" | ||||
self.posixCompliance = aFlag | ||||
self.needsParse = 1 | ||||
if self.posixCompliance: | ||||
self.optionStartExpr = re.compile('(--|-)(?P<option>[A-Za-z0-9_-]+)(?P<arg>=.*)?') | ||||
self.orderMixed = 0 | ||||
else: | ||||
self.optionStartExpr = re.compile('(--|-|\+)(?P<option>[A-Za-z0-9_-]+)(?P<arg>=.*)?') | ||||
self.orderMixed = 1 | ||||
def isPosixCompliant(self): | ||||
""" | ||||
Returns the value of the posix compliance flag. | ||||
""" | ||||
return self.posixCompliance | ||||
def setIgnoreCase(self, aFlag = 1): | ||||
""" | ||||
Enables and disables ignoring case during option processing. | ||||
""" | ||||
self.needsParse = 1 | ||||
self.ignoreCase = aFlag | ||||
def ignoreCase(self): | ||||
""" | ||||
Returns 1 if the option processor will ignore case when | ||||
processing options. | ||||
""" | ||||
return self.ignoreCase | ||||
def setAllowAbbreviations(self, aFlag = 1): | ||||
""" | ||||
Enables and disables the expansion of abbreviations during | ||||
option processing. | ||||
""" | ||||
self.allowAbbreviations = aFlag | ||||
def willAllowAbbreviations(self): | ||||
""" | ||||
Returns 1 if abbreviated options will be automatically | ||||
expanded to the non-abbreviated form (instead of causing an | ||||
unrecognized option error). | ||||
""" | ||||
return self.allowAbbreviations | ||||
def addTerminator(self, newTerm): | ||||
""" | ||||
Adds newTerm as terminator of option processing. | ||||
Whenever the option processor encounters one of the terminators | ||||
during option processing, the processing of options terminates | ||||
immediately, all remaining options are stored in the termValues | ||||
instance variable and the full name of the terminator is stored | ||||
in the terminator instance variable. | ||||
""" | ||||
self.terminators = self.terminators + [newTerm] | ||||
def _addOption(self, oTuple): | ||||
""" | ||||
Adds the option described by oTuple (name, (type, mode, | ||||
default), alias) to optionTuples. Adds index keyed under name | ||||
to optionNames. Raises spec_error if name already in | ||||
optionNames | ||||
""" | ||||
(name, (type, mode, default, multi), realName) = oTuple | ||||
# verify name and add to option names dictionary | ||||
if self.optionNames.has_key(name): | ||||
if realName: | ||||
raise spec_error, 'Alias \'' + name + '\' for \'' + realName + \ | ||||
'\' already used for another option or alias.' | ||||
else: | ||||
raise spec_error, 'Option named \'' + name + \ | ||||
'\' specified more than once. Specification: ' + option | ||||
# validated. add to optionNames | ||||
self.optionNames[name] = self.tupleIndex | ||||
self.tupleIndex = self.tupleIndex + 1 | ||||
# add to optionTuples | ||||
self.optionTuples = self.optionTuples + [oTuple] | ||||
# if type is boolean, add negation | ||||
if type == BooleanArgType: | ||||
alias = 'no' + name | ||||
specTuple = (type, mode, 0, multi) | ||||
oTuple = (alias, specTuple, name) | ||||
# verify name and add to option names dictionary | ||||
if self.optionNames.has_key(alias): | ||||
if realName: | ||||
raise spec_error, 'Negated alias \'' + name + '\' for \'' + realName + \ | ||||
'\' already used for another option or alias.' | ||||
else: | ||||
raise spec_error, 'Negated option named \'' + name + \ | ||||
'\' specified more than once. Specification: ' + option | ||||
# validated. add to optionNames | ||||
self.optionNames[alias] = self.tupleIndex | ||||
self.tupleIndex = self.tupleIndex + 1 | ||||
# add to optionTuples | ||||
self.optionTuples = self.optionTuples + [oTuple] | ||||
def addOptionConfigurationTuple(self, oTuple): | ||||
(name, argSpec, realName) = oTuple | ||||
if self.ignoreCase: | ||||
name = string.lower(name) | ||||
if realName: | ||||
realName = string.lower(realName) | ||||
else: | ||||
realName = name | ||||
oTuple = (name, argSpec, realName) | ||||
# add option | ||||
self._addOption(oTuple) | ||||
def addOptionConfigurationTuples(self, oTuple): | ||||
if type(oTuple) is ListType: | ||||
for t in oTuple: | ||||
self.addOptionConfigurationTuple(t) | ||||
else: | ||||
self.addOptionConfigurationTuple(oTuple) | ||||
def parseConfiguration(self, spec): | ||||
# destroy previous stored information + store raw spec | ||||
self.spec = spec | ||||
self.optionTuples = [] | ||||
self.optionNames = {} | ||||
self.tupleIndex = 0 | ||||
tupleIndex = 0 | ||||
# create some regex's for parsing each spec | ||||
splitExpr = \ | ||||
re.compile('(?P<names>\w+[-A-Za-z0-9|]*)?(?P<spec>!|[=:][infs]@?)?') | ||||
for option in spec: | ||||
# push to lower case (does not negatively affect | ||||
# specification) | ||||
if self.ignoreCase: | ||||
option = string.lower(option) | ||||
# break into names, specification | ||||
match = splitExpr.match(option) | ||||
if match is None: | ||||
raise spec_error, 'Invalid specification {' + option + '}' | ||||
names = match.group('names') | ||||
specification = match.group('spec') | ||||
# break name into name, aliases | ||||
nlist = string.split(names, '|') | ||||
# get name | ||||
name = nlist[0] | ||||
aliases = nlist[1:] | ||||
# specificationExpr = regex.symcomp('\(<required>.\)\(<type>.\)\(<multi>@?\)') | ||||
if not specification: | ||||
#spec tuple is ('type', 'arg mode', 'default value', 'multiple') | ||||
argType = GenericArgType | ||||
argMode = None | ||||
argDefault = 1 | ||||
argMultiple = 0 | ||||
elif specification == '!': | ||||
argType = BooleanArgType | ||||
argMode = None | ||||
argDefault = 1 | ||||
argMultiple = 0 | ||||
else: | ||||
# parse | ||||
match = specificationExpr.match(specification) | ||||
if match is None: | ||||
# failed to parse, die | ||||
raise spec_error, 'Invalid configuration for option \'' + option + '\'' | ||||
# determine mode | ||||
required = match.group('required') | ||||
if required == '=': | ||||
argMode = ArgRequired | ||||
elif required == ':': | ||||
argMode = ArgOptional | ||||
else: | ||||
raise spec_error, 'Unknown requirement configuration \'' + required + '\'' | ||||
# determine type | ||||
type = match.group('type') | ||||
if type == 's': | ||||
argType = StringArgType | ||||
argDefault = '' | ||||
elif type == 'i': | ||||
argType = IntegerArgType | ||||
argDefault = 1 | ||||
elif type == 'f' or type == 'n': | ||||
argType = RealArgType | ||||
argDefault = 1 | ||||
else: | ||||
raise spec_error, 'Unknown type specifier \'' + type + '\'' | ||||
# determine quantity | ||||
if match.group('multi') == '@': | ||||
argMultiple = 1 | ||||
else: | ||||
argMultiple = 0 | ||||
## end else (of not specification) | ||||
# construct specification tuple | ||||
specTuple = (argType, argMode, argDefault, argMultiple) | ||||
# add the option-- option tuple is (name, specTuple, real name) | ||||
oTuple = (name, specTuple, name) | ||||
self._addOption(oTuple) | ||||
for alias in aliases: | ||||
# drop to all lower (if configured to do so) | ||||
if self.ignoreCase: | ||||
alias = string.lower(alias) | ||||
# create configuration tuple | ||||
oTuple = (alias, specTuple, name) | ||||
# add | ||||
self._addOption(oTuple) | ||||
# successfully parsed.... | ||||
self.needsParse = 0 | ||||
def _getArgTuple(self, argName): | ||||
""" | ||||
Returns a list containing all the specification tuples that | ||||
match argName. If none match, None is returned. If one | ||||
matches, a list with one tuple is returned. If more than one | ||||
match, a list containing all the tuples that matched is | ||||
returned. | ||||
In other words, this function does not pass judgement upon the | ||||
validity of multiple matches. | ||||
""" | ||||
# is it in the optionNames dict? | ||||
try: | ||||
# sys.stderr.write(argName + string.join(self.optionNames.keys()) + "\n") | ||||
# yes, get index | ||||
tupleIndex = self.optionNames[argName] | ||||
# and return tuple as element of list | ||||
return [self.optionTuples[tupleIndex]] | ||||
except KeyError: | ||||
# are abbreviations allowed? | ||||
if not self.allowAbbreviations: | ||||
# No! terefore, this cannot be valid argument-- nothing found | ||||
return None | ||||
# argName might be an abbreviation (and, abbreviations must | ||||
# be allowed... or this would not have been reached!) | ||||
# create regex for argName | ||||
argExpr = re.compile('^' + argName) | ||||
tuples = filter(lambda x, argExpr=argExpr: argExpr.search(x[0]) is not None, | ||||
self.optionTuples) | ||||
if not len(tuples): | ||||
return None | ||||
else: | ||||
return tuples | ||||
def _isTerminator(self, optionName): | ||||
""" | ||||
Returns the full name of the terminator if optionName is a valid | ||||
terminator. If it is, sets self.terminator to the full name of | ||||
the terminator. | ||||
If more than one terminator matched, raises a term_error with a | ||||
string describing the ambiguity. | ||||
""" | ||||
# sys.stderr.write(optionName + "\n") | ||||
# sys.stderr.write(repr(self.terminators)) | ||||
if optionName in self.terminators: | ||||
self.terminator = optionName | ||||
elif not self.allowAbbreviations: | ||||
return None | ||||
# regex thing in bogus | ||||
# termExpr = regex.compile('^' + optionName) | ||||
terms = filter(lambda x, on=optionName: string.find(x,on) == 0, self.terminators) | ||||
if not len(terms): | ||||
return None | ||||
elif len(terms) > 1: | ||||
raise term_error, 'Ambiguous terminator \'' + optionName + \ | ||||
'\' matches ' + repr(terms) | ||||
self.terminator = terms[0] | ||||
return self.terminator | ||||
def processArguments(self, args = None): | ||||
""" | ||||
Processes args, a list of arguments (including options). | ||||
If args is the same as sys.argv, automatically trims the first | ||||
argument (the executable name/path). | ||||
If an exception is not raised, the argument list was parsed | ||||
correctly. | ||||
Upon successful completion, the freeValues instance variable | ||||
will contain all the arguments that were not associated with an | ||||
option in the order they were encountered. optionValues is a | ||||
dictionary containing the value of each option-- the method | ||||
valueForOption() can be used to query this dictionary. | ||||
terminator will contain the argument encountered that terminated | ||||
option processing (or None, if a terminator was never | ||||
encountered) and termValues will contain all of the options that | ||||
appeared after the Terminator (or an empty list). | ||||
""" | ||||
if hasattr(sys, "argv") and args == sys.argv: | ||||
args = sys.argv[1:] | ||||
max = len(args) # maximum index + 1 | ||||
self.freeValues = [] # array to hold return values | ||||
self.optionValues= {} | ||||
index = 0 # initial index | ||||
self.terminator = None | ||||
self.termValues = [] | ||||
while index < max: | ||||
# obtain argument | ||||
arg = args[index] | ||||
# increment index -- REMEMBER; it is NOW incremented | ||||
index = index + 1 | ||||
# terminate immediately if option terminator encountered | ||||
if self._isTerminator(arg): | ||||
self.freeValues = self.freeValues + args[index:] | ||||
self.termValues = args[index:] | ||||
return | ||||
# is this possibly an option? | ||||
match = self.optionStartExpr.match(arg) | ||||
if match is None: | ||||
# not an option-- add to freeValues | ||||
self.freeValues = self.freeValues + [arg] | ||||
if not self.orderMixed: | ||||
# mixing not allowed; add rest of args as freeValues | ||||
self.freeValues = self.freeValues + args[index:] | ||||
# return to caller | ||||
return | ||||
else: | ||||
continue | ||||
# grab name | ||||
optName = match.group('option') | ||||
# obtain next argument-- index has already been incremented | ||||
nextArg = match.group('arg') | ||||
if nextArg: | ||||
nextArg = nextArg[1:] | ||||
index = index - 1 # put it back | ||||
else: | ||||
try: | ||||
nextArg = args[index] | ||||
except: | ||||
nextArg = None | ||||
# transpose to lower case, if necessary | ||||
if self.ignoreCase: | ||||
optName = string.lower(optName) | ||||
# obtain defining tuple | ||||
tuples = self._getArgTuple(optName) | ||||
if tuples == None: | ||||
raise arg_error, 'Illegal option \'' + arg + '\'' | ||||
elif len(tuples) > 1: | ||||
raise arg_error, 'Ambiguous option \'' + arg + '\'; matches ' + \ | ||||
repr(map(lambda x: x[0], tuples)) | ||||
else: | ||||
config = tuples[0] | ||||
# config is now set to the configuration tuple for the | ||||
# argument | ||||
(fullName, spec, realName) = config | ||||
(optType, optMode, optDefault, optMultiple) = spec | ||||
# if opt mode required, but nextArg is none, raise an error | ||||
if (optMode == ArgRequired): | ||||
if (not nextArg) or self._isTerminator(nextArg): | ||||
# print nextArg | ||||
raise arg_error, 'Option \'' + arg + \ | ||||
'\' requires an argument of type ' + optType | ||||
if (not optMode == None) and nextArg and (not self._isTerminator(nextArg)): | ||||
# nextArg defined, option configured to possibly consume arg | ||||
try: | ||||
# grab conversion function-- the try is more for internal diagnostics | ||||
func = ConversionFunctions[optType] | ||||
try: | ||||
optionValue = func(nextArg) | ||||
index = index + 1 | ||||
except: | ||||
# only raise conversion error if REQUIRED to consume argument | ||||
if optMode == ArgRequired: | ||||
raise arg_error, 'Invalid argument to option \'' + arg + \ | ||||
'\'; should be \'' + optType + '\'' | ||||
else: | ||||
optionValue = optDefault | ||||
except arg_error: | ||||
raise arg_error, sys.exc_value | ||||
except: | ||||
raise arg_error, '(' + arg + \ | ||||
') Conversion function for \'' + optType + '\' not found.' | ||||
else: | ||||
optionValue = optDefault | ||||
# add value to options dictionary | ||||
if optMultiple: | ||||
# can be multiple values | ||||
try: | ||||
# try to append element | ||||
self.optionValues[realName] = self.optionValues[realName] + [optionValue] | ||||
except: | ||||
# failed-- must not exist; add it | ||||
self.optionValues[realName] = [optionValue] | ||||
else: | ||||
# only one value per | ||||
if self.isPosixCompliant and self.optionValues.has_key(realName): | ||||
raise arg_error, 'Argument \'' + arg + '\' occurs multiple times.' | ||||
self.optionValues[realName] = optionValue | ||||
def valueForOption(self, optionName, defaultValue = None): | ||||
""" | ||||
Return the value associated with optionName. If optionName was | ||||
not encountered during parsing of the arguments, returns the | ||||
defaultValue (which defaults to None). | ||||
""" | ||||
try: | ||||
optionValue = self.optionValues[optionName] | ||||
except: | ||||
optionValue = defaultValue | ||||
return optionValue | ||||
## | ||||
## test/example section | ||||
## | ||||
test_error = 'Test Run Amok!' | ||||
def _test(): | ||||
""" | ||||
A relatively complete test suite. | ||||
""" | ||||
try: | ||||
DPyGetOpt(['foo', 'bar=s', 'foo']) | ||||
except: | ||||
print 'EXCEPTION (should be \'foo\' already used..): ' + sys.exc_value | ||||
try: | ||||
DPyGetOpt(['foo|bar|apple=s@', 'baz|apple!']) | ||||
except: | ||||
print 'EXCEPTION (should be duplicate alias/name error): ' + sys.exc_value | ||||
x = DPyGetOpt(['apple|atlas=i@', 'application|executable=f@']) | ||||
try: | ||||
x.processArguments(['-app', '29.3']) | ||||
except: | ||||
print 'EXCEPTION (should be ambiguous argument): ' + sys.exc_value | ||||
x = DPyGetOpt(['foo'], ['antigravity', 'antithesis']) | ||||
try: | ||||
x.processArguments(['-foo', 'anti']) | ||||
except: | ||||
print 'EXCEPTION (should be ambiguous terminator): ' + sys.exc_value | ||||
profile = ['plain-option', | ||||
'boolean-option!', | ||||
'list-of-integers=i@', | ||||
'list-real-option|list-real-alias|list-real-pseudonym=f@', | ||||
'optional-string-option:s', | ||||
'abbreviated-string-list=s@'] | ||||
terminators = ['terminator'] | ||||
args = ['-plain-option', | ||||
'+noboolean-option', | ||||
'--list-of-integers', '1', | ||||
'+list-of-integers', '2', | ||||
'-list-of-integers', '3', | ||||
'freeargone', | ||||
'-list-real-option', '1.1', | ||||
'+list-real-alias', '1.2', | ||||
'--list-real-pseudonym', '1.3', | ||||
'freeargtwo', | ||||
'-abbreviated-string-list', 'String1', | ||||
'--abbreviated-s', 'String2', | ||||
'-abbrev', 'String3', | ||||
'-a', 'String4', | ||||
'-optional-string-option', | ||||
'term', | ||||
'next option should look like an invalid arg', | ||||
'-a'] | ||||
print 'Using profile: ' + repr(profile) | ||||
print 'With terminator: ' + repr(terminators) | ||||
print 'Processing arguments: ' + repr(args) | ||||
go = DPyGetOpt(profile, terminators) | ||||
go.processArguments(args) | ||||
print 'Options (and values): ' + repr(go.optionValues) | ||||
print 'free args: ' + repr(go.freeValues) | ||||
print 'term args: ' + repr(go.termValues) | ||||