Документ взят из кэша поисковой машины. Адрес оригинального документа : http://www.apo.nmsu.edu/Telescopes/TCC/html/cmd_parse_8py_source.html
Дата изменения: Tue Sep 15 02:25:37 2015
Дата индексирования: Sun Apr 10 01:34:03 2016
Кодировка:
lsst.tcc: python/tcc/parse/cmdParse.py Source File
lsst.tcc  1.2.2-3-g89ecb63
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
cmdParse.py
Go to the documentation of this file.
1 from __future__ import division, absolute_import
2 """A command parser for DCL commands (VMS operating system), especially those used by the APO TCC.
3 
4 @note:
5 * any parameter containing forward slashes (eg a file) must be quoted
6 * quotes are removed during parsing
7 * parameters cannot be negated
8 
9 todo:
10 sort out default behavior when default is a list...
11 error handling
12 """
13 import itertools
14 import collections
15 
16 from RO.StringUtil import strFromException
17 from RO.StringUtil import unquoteStr
18 import RO.Alg.MatchList
19 import pyparsing as pp
20 
21 
22 
23 class ParseError(Exception):
24  pass
25 
26 def getUniqueAbbrevIndex(keyword, matchList):
27  """!Return the index of keyword in list allowing unique abbreviations and independent of case.
28 
29  @param[in] keyword a possibly abbreviated Keyword
30  @param[in] matchList a list of strings to match Keyword to.
31 
32  @return integer, the index of the list holding the correct match
33  """
34  match = RO.Alg.MatchList(valueList=matchList).getUniqueMatch(keyword)
35  return matchList.index(match)
36 
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.
41  """
42  # Pyparsing Grammar
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() # double double quotes
50  escQuoted = pp.Literal('\"').suppress() + pp.CharsNotIn('"') + pp.Literal('\"').suppress()
51  enteredString = ddQuoted.setParseAction(pp.removeQuotes) | escQuoted # quotes removed!
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()
55  # try to parse with parentheses first, for cases like foo=(1,2,3), bar=1
56  datumOptParList = datumParList | datumList
57  # the command verb, may be ^Y, etc.
58  cmdVerb = pp.Word(pp.alphas + '^').setResultsName('cmdVerb')
59  # everything after a "!", is a comment
60  comment = pp.Literal("!") + pp.restOfLine
61  # qualifier specific parsing
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(")"))) # incase of empty list...
65 
66 
67  keywordValue = pp.Group(keyword + pp.Literal("=").suppress() + datum) # ::=> blah = 5
68  keywordListValues = pp.Group(keyword + pp.Literal("=").suppress() \
69  + pp.Group(datumOptParList) ) # ::=> blah = 5,4,3
70  keywordForceParListVal = pp.Group(keyword + pp.Literal("=").suppress() \
71  + datumParList)
72  keyValPairList = pp.Group(pp.delimitedList(keywordValue ^ keywordForceParListVal))#::=> blah=5, blabber=(3,4), ...
73  quotedStringParam = pp.Regex(r'"(?:\\\"|\\\\|[^"])*"') # to handle escaped quotes in broadcast/talk command
74  param = keyValPairList | keywordListValues | pp.Group(quotedStringParam) | pp.Group(datumList) #^ keywordList ^ keywordListValues
75 
76 
77  # everything together
78  genericCmd = cmdVerb + pp.ZeroOrMore(qualifier.setResultsName("qualifiers", True) ^ \
79  param.setResultsName('parameters', True))
80  genericCmd.ignore(comment)
81 
82  return genericCmd
83 
84 
85 class CmdParser(object):
86  """!A class that holds command definitions, and can parse tcc commands
87  """
88  def __init__(self, cmdDefList):
89  """!Construct a CmdParser
90 
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.
94  """
95  self.genericCmd = makeGenericCmd() # pyparsing
96  # dict of cmd verb: cmd definition
97 # self.checkDefaults(cmdDefList)
98  self.cmdDefDict = dict((cmdDef.name.lower(), cmdDef) for cmdDef in cmdDefList)
99  self.cmdMatchList = RO.Alg.MatchList(valueList=self.cmdDefDict.keys())
100 
101  def parseLine(self, inputLine):
102  """!Parse an input line, return a ParsedCmd Object
103 
104  @param[in] inputLine line to parse
105  @return parsedCmd, a ParsedCmd object
106  @throw ParseError if command cannot be parsed.
107  """
108  try:
109  # pyparsing, returns object with
110  # verb, params, quals as previously defined attributes
111  ppOut = self.genericCmd.parseString(inputLine, parseAll=True)
112  # find correct command definition
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]
120 
121  cmdDef = self.cmdDefDict[cmdName]
122  parsedCmd = ParsedCmd(cmdDef.name) # initialize
123 
124  if hasattr(cmdDef, "subCmdList"):
125  # find the secondary command in the cmdList (the first name in the first slot)
126  # alternate parsing enabled in this case
127  secCmd = ppOut.parameters[0][0][0]
128  # match with the name of the first parameter in the first slot
129  ind = getUniqueAbbrevIndex(
130  secCmd,
131  [item.paramList[0].paramElementList[0].name for item in cmdDef.subCmdList]
132  )
133  # (re) set cmdDef to this and carry on
134  cmdDef = cmdDef.subCmdList[ind]
135 
136  ################## add and validate qualifiers. #################
137  # set value = None if no value was passed, values are always a list if not None
138  for qual in ppOut.qualifiers:
139  boolValue = True
140  qualName = qual[0]
141  try:
142  # if a value was passed, it will be in index=1
143  #qualVal = qual[1][0]
144  qualVal = [q[0] for q in qual[1]]
145  except IndexError:
146  # there was no value passed
147  qualVal = None
148  try:
149  ind = getUniqueAbbrevIndex(qualName, [item.name for item in cmdDef.qualifierList])
150  except ValueError:
151  # match wasn't found, try again using negated qualifiers (where allowed)
152  nameList=[]
153  for tempQual in cmdDef.qualifierList:
154  if tempQual.negatable:
155  nameList.append('No' + tempQual.name)
156  else:
157  nameList.append(tempQual.name)
158  ind = getUniqueAbbrevIndex(qualName, nameList)
159  boolValue = False # we just encountered a negated qualifier
160  qualDef = cmdDef.qualifierList[ind]
161 
162  if qualDef.valType == None:
163  # this is a flag-like qualifier with no values
164  if qualVal != 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):
168  # this qualifier accepts a simple valueList of castable elements
169  if qualVal == None:
170  # no value was passed, set the default (which may in fact be empty)...
171  parsedCmd.addQual(
172  qualDef.name,
173  boolValue,
174  boolValueDefaulted=False,
175  valueList = qualDef.defValueList,
176  valueListDefaulted = True ,
177  )
178  else:
179  # values were passed with this qualifier...check they cast correctly
180  try:
181  valueList = [qualDef.valType(item) for item in qualVal]
182  except ValueError:
183  raise ParseError('Could not cast values %s to correct type %s' % \
184  (qualVal, qualDef.valType))
185  else:
186  # check if amount of values are allowed
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]))
191  else:
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' % \
195  (
196  qualDef.name, len(valueList),
197  qualDef.numValueRange[0],
198  qualDef.numValueRange[1])
199  )
200  # amount of values is ok
201  parsedCmd.addQual(
202  qualDef.name,
203  boolValue,
204  boolValueDefaulted=False,
205  valueList = valueList,
206  valueListDefaulted = False,
207  )
208  else:
209  # this qualifier has values that belong to a specific set of keywords
210  if qualVal == None:
211  # no value was passed, add the default value list (which may be empty)
212  parsedCmd.addQual(
213  qualDef.name,
214  boolValue,
215  boolValueDefaulted=False,
216  valueList = qualDef.defValueList,
217  valueListDefaulted = True ,
218  )
219  else:
220  # search each value for it's full (non-abbreviated) representation
221  # in the parsedCmd valType field
222  valueList = [RO.Alg.MatchList(valueList=qualDef.valType).getUniqueMatch(keyword) for keyword in qualVal]
223  # check if amount of values are allowed
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]))
228  else:
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' % \
232  (
233  qualDef.name, len(valueList),
234  qualDef.numValueRange[0],
235  qualDef.numValueRange[1])
236  )
237  parsedCmd.addQual(
238  qualDef.name,
239  boolValue,
240  boolValueDefaulted=False,
241  valueList = valueList,
242  valueListDefaulted = False,
243  )
244 
245  # now append the remaining (un commanded) qualifiers
246  for qual in cmdDef.qualifierList:
247  if qual.name.lower() not in parsedCmd.qualDict:
248  parsedCmd.addQual(
249  qual.name,
250  boolValue = qual.defBoolValue,
251  boolValueDefaulted = True,
252  valueList = qual.defValueList,
253  valueListDefaulted = True,
254  )
255 
256  ############### add and validate parameters ######################
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
263  paramList = []
264  if not paramSlotGot:
265  # if got no slot, look for a default
266  if paramSlotDef.defaultParamList: # is not None:
267  for paramDef in paramSlotDef.defaultParamList: # default list may be empty
268  validatedName = paramDef.name # may be None, if not a keyword type parameter
269  valueList = paramDef.defValueList # may be None, if not specified in definitions
270  if validatedName:
271  # keyword type parameter
272  paramList.append(ParsedKeyword(validatedName, valueList, defaulted=True))
273  parsedCmd.addParam(paramSlotName, valueList = paramList, defaulted=True, boolValue=False)
274  elif valueList is not None:
275  # param is a simple value list
276  paramList = valueList
277  parsedCmd.addParam(paramSlotName, valueList = paramList, defaulted=True, boolValue=False)
278  else:
279  # no defaults were defined, do nothing
280  raise RuntimeError("Conor believes this is a bug, investigate")
281  #continue
282  #parsedCmd.addParam(paramSlotName, valueList=None)
283  else:
284  # no defaults specified, add an empty entry in the parsed parameter dictionary
285  parsedCmd.addParam(paramSlotName, valueList=(), boolValue=False)
286  elif paramSlotDef.matchList:
287  # got a slot, and it is keywords parse it
288  if paramSlotDef.numParamRange[1] == None:
289  # no upper bound on range
290  correctParamAmt = paramSlotDef.numParamRange[0] <= len(paramSlotGot)
291  else:
292  # there is an upper bound
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
299  # are values associated with this keyword? They will be left over in paramGot
300  if paramGot:
301  # values were passed by user in association with this keyword
302  if paramDef.numValueRange[1] == None:
303  # no upper value limit
304  correctValueAmt = paramDef.numValueRange[0] <= len(paramGot)
305  else:
306  # there is an upper limit
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))
310  # zero (p[0]) index is a pyparsing definition, could figure out how to fix it...but for now it works
311  valueList = [paramDef.castVals(p[0]) for p in paramGot]
312  else:
313  # no values passed by user, but a default value list is
314  # defined for this parameter.
315  valueList = paramDef.defValueList # will be empty if no default list
316  defaulted = True if valueList else False
317  paramList.append(ParsedKeyword(validatedName, valueList, defaulted))
318  parsedCmd.addParam(paramSlotName, valueList = paramList)
319  else:
320  # this is a simple value (non-keyword) parameter
321  # treat this slot as a single list of values
322  # do this because of pyparsing behavior doesn't wrap in an outer list
323  # unless it is a list of keyword=valueList
324  paramGot = paramSlotGot
325  assert len(paramSlotDef.paramElementList) == 1
326  paramDef = paramSlotDef.paramElementList[0]
327  if paramDef.numValueRange[1] == None:
328  # no upper value limit
329  correctValueAmt = paramDef.numValueRange[0] <= len(paramGot)
330  else:
331  # there is an upper limit
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,))
335  validatedName = None
336  if paramGot[0][0]=='"'==paramGot[0][-1]:
337  # special case, this parameter is a quoted string, only used in
338  # talk and broadcast commands; dequote and send along
339  paramList = [paramDef.castVals(unquoteStr(paramGot[0]))]
340 
341  else:
342  paramList = [paramDef.castVals(par) for par, in paramGot]
343  parsedCmd.addParam(paramSlotName, valueList=paramList)
344 
345  parsedCmd.addCall(cmdDef.callFunc)
346  return parsedCmd
347  except Exception as e:
348  raise ParseError("Could not parse %r: %s" % (inputLine, strFromException(e)))
349 
350 class ParsedCmd(object):
351  def __init__(self, cmdVerb):
352  """!A class for holding a parsed command.
353 
354  @param[in] cmdVerb the command name, string.
355  """
356  self.qualDict = {} # dict of qual name (lowercase): Qualifier object
357  self.paramDict = collections.OrderedDict()
358  self.callFunc = None
359  self.cmdVerb = cmdVerb
360 
361  def addCall(self, callFunc):
362  """!Add a callable to be called after successful parsing
363 
364  @param[in] callFunc a callable
365  """
366  self.callFunc = callFunc
367 
368  def addParam(self, name, valueList, defaulted=False, boolValue=True):
369  """!Add a param to the current paramSlot.
370  Inputs:
371  name: a the param name
372  valueList: the associated list of value
373  defaulted: was the parameter defaulted?
374  """
375  # add in keword value functionality here
376  param = ParsedPar(name, valueList, defaulted, boolValue)
377  self.paramDict[name.lower()] = param
378 
379  def addQual(self,
380  name,
381  boolValue,
382  boolValueDefaulted,
383  valueList=None,
384  valueListDefaulted=False
385  ):
386  """!Add a qualifier.
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.
394  """
395  qual = ParsedQual(name, boolValue, boolValueDefaulted, valueList, valueListDefaulted)
396  self.qualDict[name.lower()] = qual
397 
398  def printData(self):
399  """!Print contents of parsed command, for diagnostic purposes
400  """
401  print "Command: %s" % (self.cmdVerb,)
402  print " params:"
403  for par in self.paramDict.itervalues():
404  print " ", par
405  print " quals:"
406  qualNameList = sorted(self.qualDict.keys())
407  for qualName in qualNameList:
408  qual = self.qualDict[qualName]
409  print " ", qual
410 
411  def __str__(self):
412  """!Bit of a hack...for now
413  """
414  self.printData()
415  return ''
416 
417 class ParsedQual(object):
418  def __init__(self, name, boolValue, boolValueDefaulted, valueList=None, valueListDefaulted=False):
419  """!A parsed qualifier object
420 
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
428  """
429  self.name = name
430  self.boolValue = bool(boolValue)
431  self.boolValueDefaulted = bool(boolValueDefaulted)
432  self.valueList = valueList or []
433  self.valueListDefaulted = bool(valueListDefaulted)
434 
435  def __repr__(self):
436  return "%s(name=%s, boolValue=%s, valueList=%s, boolValueDefaulted=%s, valueListDefaulted=%s)" % \
437  (type(self).__name__, self.name, self.boolValue, self.valueList, self.boolValueDefaulted, self.valueListDefaulted)
438 
439 
440 class ParsedKeyword(object):
441  def __init__(self, keyword, valueList, defaulted=False):
442  """!An object for holding parsed parameters that are of the form keyword=valueList
443 
444  @param[in] keyword the keyword
445  @param[in] valueList the list of value(s)
446  @param[in] defaulted was the value list defaulted?
447 
448  note: add some interactivity to this to make it useful down the line?...
449  """
450  self.keyword = keyword
451  self.valueList = valueList
452  self.defaulted = defaulted
453 
454  def __repr__(self):
455  return "%s(keyword=%r, valueList=%r, defaulted=%r)" % (type(self).__name__, self.keyword, self.valueList, self.defaulted)
456 
457 
458 class ParsedPar(object):
459  def __init__(self, name, valueList, defaulted=False, boolValue=True):
460  """!A parsed parameter object
461 
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)
466  """
467  self.name = name
468  self.valueList = valueList
469  self.defaulted = bool(defaulted)
470  self.boolValue = bool(boolValue)
471 
472  def __repr__(self):
473  return "%s(valueList=%r, defaulted=%r, boolValue=%r)" % (type(self).__name__, self.valueList, self.defaulted, self.boolValue)
def __init__
A parsed qualifier object.
Definition: cmdParse.py:418
def addCall
Add a callable to be called after successful parsing.
Definition: cmdParse.py:361
A class that holds command definitions, and can parse tcc commands.
Definition: cmdParse.py:85
def __init__
Construct a CmdParser.
Definition: cmdParse.py:88
def parseLine
Parse an input line, return a ParsedCmd Object.
Definition: cmdParse.py:101
def addQual
Add a qualifier.
Definition: cmdParse.py:385
def __init__
A parsed parameter object.
Definition: cmdParse.py:459
def printData
Print contents of parsed command, for diagnostic purposes.
Definition: cmdParse.py:398
def makeGenericCmd
Constructs a generic TCC command from pyparsing elements.
Definition: cmdParse.py:37
def __init__
An object for holding parsed parameters that are of the form keyword=valueList.
Definition: cmdParse.py:441
def getUniqueAbbrevIndex
Return the index of keyword in list allowing unique abbreviations and independent of case...
Definition: cmdParse.py:26
def __init__
A class for holding a parsed command.
Definition: cmdParse.py:351
def addParam
Add a param to the current paramSlot.
Definition: cmdParse.py:368
def __str__
Bit of a hack...for now.
Definition: cmdParse.py:411