1 from __future__
import division, absolute_import
2 """A command parser for DCL commands (VMS operating system), especially those used by the APO TCC.
5 * any parameter containing forward slashes (eg a file) must be quoted
6 * quotes are removed during parsing
7 * parameters cannot be negated
10 sort out default behavior when default is a list...
16 from RO.StringUtil
import strFromException
17 from RO.StringUtil
import unquoteStr
18 import RO.Alg.MatchList
19 import pyparsing
as pp
27 """!Return the index of keyword in list allowing unique abbreviations and independent of case.
29 @param[in] keyword a possibly abbreviated Keyword
30 @param[in] matchList a list of strings to match Keyword to.
32 @return integer, the index of the list holding the correct match
34 match = RO.Alg.MatchList(valueList=matchList).getUniqueMatch(keyword)
35 return matchList.index(match)
38 """!Constructs a generic TCC command from pyparsing elements.
39 Upon parsing, will find a command verb and collections of both
40 qualifiers and parameters.
43 point = pp.Literal(
"." )
44 e = pp.CaselessLiteral(
"E" )
45 number = pp.Combine( pp.Word(
"+-"+pp.nums, pp.nums ) +
46 pp.Optional( point + pp.Optional( pp.Word( pp.nums ) ) ) +
47 pp.Optional( e + pp.Word(
"+-"+pp.nums, pp.nums ) ) )
48 keyword = pp.Word(pp.alphas + pp.alphanums, pp.alphas +
'_:.' + pp.alphanums)
49 ddQuoted = pp.Literal(
'"').suppress() + pp.dblQuotedString + pp.Literal(
'"').suppress()
50 escQuoted = pp.Literal(
'\"').suppress() + pp.CharsNotIn(
'"') + pp.Literal(
'\"').suppress()
51 enteredString = ddQuoted.setParseAction(pp.removeQuotes) | escQuoted
52 datum = pp.Group(enteredString) | pp.Group(number) | pp.Group(keyword)
53 datumList = pp.delimitedList(datum)
54 datumParList = pp.Literal(
"(").suppress() + datumList + pp.Literal(
")").suppress()
56 datumOptParList = datumParList | datumList
58 cmdVerb = pp.Word(pp.alphas +
'^').setResultsName(
'cmdVerb')
60 comment = pp.Literal(
"!") + pp.restOfLine
62 qualKey = pp.Literal(
"/").suppress() + keyword
63 qualListValues = pp.Group(qualKey + pp.Literal(
"=").suppress() + pp.Group(datumOptParList))
64 qualifier = qualListValues | (pp.Group(qualKey) + pp.Optional(pp.Literal(
"=") + pp.Literal(
"(") + pp.Literal(
")")))
67 keywordValue = pp.Group(keyword + pp.Literal(
"=").suppress() + datum)
68 keywordListValues = pp.Group(keyword + pp.Literal(
"=").suppress() \
69 + pp.Group(datumOptParList) )
70 keywordForceParListVal = pp.Group(keyword + pp.Literal(
"=").suppress() \
72 keyValPairList = pp.Group(pp.delimitedList(keywordValue ^ keywordForceParListVal))
73 quotedStringParam = pp.Regex(
r'"(?:\\\"|\\\\|[^"])*"')
74 param = keyValPairList | keywordListValues | pp.Group(quotedStringParam) | pp.Group(datumList)
78 genericCmd = cmdVerb + pp.ZeroOrMore(qualifier.setResultsName(
"qualifiers",
True) ^ \
79 param.setResultsName(
'parameters',
True))
80 genericCmd.ignore(comment)
86 """!A class that holds command definitions, and can parse tcc commands
89 """!Construct a CmdParser
91 @param[in] cmdDefList a list of command definititions
92 (Command or CommandWrapper Objects defined in parseObjects),
93 containing all commands to be recognized by this parser.
98 self.
cmdDefDict = dict((cmdDef.name.lower(), cmdDef)
for cmdDef
in cmdDefList)
99 self.
cmdMatchList = RO.Alg.MatchList(valueList=self.cmdDefDict.keys())
102 """!Parse an input line, return a ParsedCmd Object
104 @param[in] inputLine line to parse
105 @return parsedCmd, a ParsedCmd object
106 @throw ParseError if command cannot be parsed.
111 ppOut = self.genericCmd.parseString(inputLine, parseAll=
True)
113 cmdNames = self.cmdMatchList.getAllMatches(ppOut.cmdVerb)
114 if len(cmdNames) == 0:
115 raise ParseError(
"Unrecognized command %r" % (ppOut.cmdVerb,))
116 elif len(cmdNames) > 1:
117 listStr =
", ".join(cmdNames)
118 raise ParseError(
"%r not unique; could be any of %s" % (ppOut.cmdVerb, listStr))
119 cmdName = cmdNames[0]
124 if hasattr(cmdDef,
"subCmdList"):
127 secCmd = ppOut.parameters[0][0][0]
131 [item.paramList[0].paramElementList[0].name
for item
in cmdDef.subCmdList]
134 cmdDef = cmdDef.subCmdList[ind]
138 for qual
in ppOut.qualifiers:
144 qualVal = [q[0]
for q
in qual[1]]
153 for tempQual
in cmdDef.qualifierList:
154 if tempQual.negatable:
155 nameList.append(
'No' + tempQual.name)
157 nameList.append(tempQual.name)
160 qualDef = cmdDef.qualifierList[ind]
162 if qualDef.valType ==
None:
165 raise ParseError(
'Expected no value for qualifier: %s' % (qualDef.name,))
166 parsedCmd.addQual(qualDef.name, boolValue, boolValueDefaulted=
False)
167 elif callable(qualDef.valType):
174 boolValueDefaulted=
False,
175 valueList = qualDef.defValueList,
176 valueListDefaulted =
True ,
181 valueList = [qualDef.valType(item)
for item
in qualVal]
183 raise ParseError(
'Could not cast values %s to correct type %s' % \
184 (qualVal, qualDef.valType))
187 if qualDef.numValueRange[1] ==
None:
188 if len(valueList) < qualDef.numValueRange[0]:
189 raise ParseError(
'Not Enough values for qualifer %s, got %i, expected at least %i' \
190 % (qualDef.name, len(valueList), qualDef.numValueRange[0]))
192 if not (qualDef.numValueRange[0] <= len(valueList) <= qualDef.numValueRange[1]):
193 raise ParseError(
'Invalid number of values for qualifier %s.' \
194 'Got: %i, should be between %i and %i' % \
196 qualDef.name, len(valueList),
197 qualDef.numValueRange[0],
198 qualDef.numValueRange[1])
204 boolValueDefaulted=
False,
205 valueList = valueList,
206 valueListDefaulted =
False,
215 boolValueDefaulted=
False,
216 valueList = qualDef.defValueList,
217 valueListDefaulted =
True ,
222 valueList = [RO.Alg.MatchList(valueList=qualDef.valType).getUniqueMatch(keyword)
for keyword
in qualVal]
224 if qualDef.numValueRange[1] ==
None:
225 if len(valueList) < qualDef.numValueRange[0]:
226 raise ParseError(
'Not Enough values for qualifer %s, got %i, expected at least %i' \
227 % (qualDef.name, len(valueList), qualDef.numValueRange[0]))
229 if not (qualDef.numValueRange[0] <= len(valueList) <= qualDef.numValueRange[1]):
230 raise ParseError(
'Invalid number of values for qualifier %s. ' \
231 'Got: %i, should be between %i and %i' % \
233 qualDef.name, len(valueList),
234 qualDef.numValueRange[0],
235 qualDef.numValueRange[1])
240 boolValueDefaulted=
False,
241 valueList = valueList,
242 valueListDefaulted =
False,
246 for qual
in cmdDef.qualifierList:
247 if qual.name.lower()
not in parsedCmd.qualDict:
250 boolValue = qual.defBoolValue,
251 boolValueDefaulted =
True,
252 valueList = qual.defValueList,
253 valueListDefaulted =
True,
257 if len(cmdDef.paramList) < len(ppOut.parameters):
258 raise ParseError(
'Too many parameters for command %r' % (inputLine,))
259 if len(ppOut.parameters) < cmdDef.minParAmt:
260 raise ParseError(
'Too few parameters for command %r' % (inputLine,))
261 for paramSlotDef, paramSlotGot
in itertools.izip_longest(cmdDef.paramList, ppOut.parameters):
262 paramSlotName = paramSlotDef.name
266 if paramSlotDef.defaultParamList:
267 for paramDef
in paramSlotDef.defaultParamList:
268 validatedName = paramDef.name
269 valueList = paramDef.defValueList
272 paramList.append(
ParsedKeyword(validatedName, valueList, defaulted=
True))
273 parsedCmd.addParam(paramSlotName, valueList = paramList, defaulted=
True, boolValue=
False)
274 elif valueList
is not None:
276 paramList = valueList
277 parsedCmd.addParam(paramSlotName, valueList = paramList, defaulted=
True, boolValue=
False)
280 raise RuntimeError(
"Conor believes this is a bug, investigate")
285 parsedCmd.addParam(paramSlotName, valueList=(), boolValue=
False)
286 elif paramSlotDef.matchList:
288 if paramSlotDef.numParamRange[1] ==
None:
290 correctParamAmt = paramSlotDef.numParamRange[0] <= len(paramSlotGot)
293 correctParamAmt = (paramSlotDef.numParamRange[0] <= len(paramSlotGot) <= paramSlotDef.numParamRange[1])
294 if not correctParamAmt:
295 raise ParseError(
'Too many elements in parameter slot for command %r' % (inputLine,))
296 for paramGot
in paramSlotGot:
297 paramDef = paramSlotDef.paramElementList[
getUniqueAbbrevIndex(paramGot.pop(0), paramSlotDef.matchList)]
298 validatedName = paramDef.name
302 if paramDef.numValueRange[1] ==
None:
304 correctValueAmt = paramDef.numValueRange[0] <= len(paramGot)
307 correctValueAmt = paramDef.numValueRange[0] <= len(paramGot) <= paramDef.numValueRange[1]
308 if not correctValueAmt:
309 raise ParseError(
'Incorrect amt of values passed for %s param. Commanded: %r ' % (paramDef.name, inputLine))
311 valueList = [paramDef.castVals(p[0])
for p
in paramGot]
315 valueList = paramDef.defValueList
316 defaulted =
True if valueList
else False
317 paramList.append(
ParsedKeyword(validatedName, valueList, defaulted))
318 parsedCmd.addParam(paramSlotName, valueList = paramList)
324 paramGot = paramSlotGot
325 assert len(paramSlotDef.paramElementList) == 1
326 paramDef = paramSlotDef.paramElementList[0]
327 if paramDef.numValueRange[1] ==
None:
329 correctValueAmt = paramDef.numValueRange[0] <= len(paramGot)
332 correctValueAmt = paramDef.numValueRange[0] <= len(paramGot) <= paramDef.numValueRange[1]
333 if not correctValueAmt:
334 raise ParseError(
'Too many elements in parameter slot for command %r' % (inputLine,))
336 if paramGot[0][0]==
'"'==paramGot[0][-1]:
339 paramList = [paramDef.castVals(unquoteStr(paramGot[0]))]
342 paramList = [paramDef.castVals(par)
for par,
in paramGot]
343 parsedCmd.addParam(paramSlotName, valueList=paramList)
345 parsedCmd.addCall(cmdDef.callFunc)
347 except Exception
as e:
348 raise ParseError(
"Could not parse %r: %s" % (inputLine, strFromException(e)))
352 """!A class for holding a parsed command.
354 @param[in] cmdVerb the command name, string.
362 """!Add a callable to be called after successful parsing
364 @param[in] callFunc a callable
368 def addParam(self, name, valueList, defaulted=False, boolValue=True):
369 """!Add a param to the current paramSlot.
371 name: a the param name
372 valueList: the associated list of value
373 defaulted: was the parameter defaulted?
376 param =
ParsedPar(name, valueList, defaulted, boolValue)
384 valueListDefaulted=
False
387 @param[in] name the qualifier name
388 @param[in] boolValue bool, was it commanded?
389 @param[in] boolValueDefaulted bool, was this passed explicitly by the user
390 or added by default by the parser
391 @param[in] valueList any value list associated with this qualifier
392 @param[in] valueListDefaulted was the list explicitly passed by the user or did the parser
393 read and apply this list as a default as indicated in a command definition.
395 qual =
ParsedQual(name, boolValue, boolValueDefaulted, valueList, valueListDefaulted)
399 """!Print contents of parsed command, for diagnostic purposes
401 print "Command: %s" % (self.
cmdVerb,)
403 for par
in self.paramDict.itervalues():
406 qualNameList = sorted(self.qualDict.keys())
407 for qualName
in qualNameList:
412 """!Bit of a hack...for now
418 def __init__(self, name, boolValue, boolValueDefaulted, valueList=None, valueListDefaulted=False):
419 """!A parsed qualifier object
421 @param[in] name the qualifier name
422 @param[in] boolValue bool, was it commanded?
423 @param[in] boolValueDefaulted if True the user did not specify this qualifier,
424 so the boolValue of this qualifier is the default
425 @param[in] valueList any value list associated with this qualifier
426 @param[in] valueListDefaulted if True, the user did not specify values for this qualifier,
427 so the valueList is the default
436 return "%s(name=%s, boolValue=%s, valueList=%s, boolValueDefaulted=%s, valueListDefaulted=%s)" % \
441 def __init__(self, keyword, valueList, defaulted=False):
442 """!An object for holding parsed parameters that are of the form keyword=valueList
444 @param[in] keyword the keyword
445 @param[in] valueList the list of value(s)
446 @param[in] defaulted was the value list defaulted?
448 note: add some interactivity to this to make it useful down the line?...
455 return "%s(keyword=%r, valueList=%r, defaulted=%r)" % (type(self).__name__, self.
keyword, self.
valueList, self.
defaulted)
459 def __init__(self, name, valueList, defaulted=False, boolValue=True):
460 """!A parsed parameter object
462 @param[in] name paramName
463 @param[in] valueList any list of value(s) associated with this parameter
464 @param[in] defaulted boolean. Indicate whether this parameter was passed by default
465 @param[in] boolValue boolean. True if it was commanded (analogous to a qualifier)
def __init__
A parsed qualifier object.
def addCall
Add a callable to be called after successful parsing.
A class that holds command definitions, and can parse tcc commands.
def __init__
Construct a CmdParser.
def parseLine
Parse an input line, return a ParsedCmd Object.
def addQual
Add a qualifier.
def __init__
A parsed parameter object.
def printData
Print contents of parsed command, for diagnostic purposes.
def makeGenericCmd
Constructs a generic TCC command from pyparsing elements.
def __init__
An object for holding parsed parameters that are of the form keyword=valueList.
def getUniqueAbbrevIndex
Return the index of keyword in list allowing unique abbreviations and independent of case...
def __init__
A class for holding a parsed command.
def addParam
Add a param to the current paramSlot.
def __str__
Bit of a hack...for now.