blob: 7f8a5b9808641ecfd69600fb15b7b47f0425f7d3 [file] [log] [blame]
#!/usr/bin/python2
# -*-coding:utf-8 -*
# Copyright (c) 2011-2014, Intel Corporation
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation and/or
# other materials provided with the distribution.
#
# 3. Neither the name of the copyright holder nor the names of its contributors
# may be used to endorse or promote products derived from this software without
# specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import re
import sys
import copy
# For Python 2.x/3.x compatibility
try:
from itertools import izip as zip
from itertools import imap as map
except:
pass
# =====================================================================
""" Context classes, used during propagation and the "to PFW script" step """
# =====================================================================
class PropagationContextItem(list) :
"""Handle an item during the propagation step"""
def __copy__(self):
"""C.__copy__() -> a shallow copy of C"""
return self.__class__(self)
class PropagationContextElement(PropagationContextItem) :
"""Handle an Element during the propagation step"""
def getElementsFromName(self, name):
matchingElements = []
for element in self :
if element.getName() == name :
matchingElements.append(element)
return matchingElements
class PropagationContextOption(PropagationContextItem) :
"""Handle an Option during the propagation step"""
def getOptionItems (self, itemName):
items = []
for options in self :
items.append(options.getOption(itemName))
return items
class PropagationContext() :
"""Handle the context during the propagation step"""
def __init__(self, propagationContext=None) :
if propagationContext == None :
self._context = {
"DomainOptions" : PropagationContextOption() ,
"Configurations" : PropagationContextElement() ,
"ConfigurationOptions" : PropagationContextOption() ,
"Rules" : PropagationContextElement() ,
"PathOptions" : PropagationContextOption() ,
}
else :
self._context = propagationContext
def copy(self):
"""return a copy of the context"""
contextCopy = self._context.copy()
for key in iter(self._context) :
contextCopy[key] = contextCopy[key].__copy__()
return self.__class__(contextCopy)
def getDomainOptions (self):
return self._context["DomainOptions"]
def getConfigurations (self):
return self._context["Configurations"]
def getConfigurationOptions (self):
return self._context["ConfigurationOptions"]
def getRules (self):
return self._context["Rules"]
def getPathOptions (self):
return self._context["PathOptions"]
# =====================================================
"""Element option container"""
# =====================================================
class Options () :
"""handle element options"""
def __init__(self, options=[], optionNames=[]) :
self.options = dict(zip(optionNames, options))
# print(options,optionNames,self.options)
def __str__(self) :
ops2str = []
for name, argument in list(self.options.items()) :
ops2str.append(str(name) + "=\"" + str(argument) + "\"")
return " ".join(ops2str)
def getOption(self, name):
"""get option by its name, if it does not exist return empty string"""
return self.options.get(name, "")
def setOption(self, name, newOption):
"""set option by its name"""
self.options[name] = newOption
def copy (self):
"""D.copy() -> a shallow copy of D"""
copy = Options()
copy.options = self.options.copy()
return copy
# ====================================================
"""Definition of all element class"""
# ====================================================
class Element(object):
""" implement a basic element
It is the class base for all other elements as Domain, Configuration..."""
tag = "unknown"
optionNames = ["Name"]
childWhiteList = []
optionDelimiter = " "
def __init__(self, line=None) :
if line == None :
self.option = Options([], self.optionNames)
else :
self.option = self.optionFromLine(line)
self.children = []
def optionFromLine(self, line) :
# get ride of spaces
line = line.strip()
options = self.extractOptions(line)
return Options(options, self.optionNames)
def extractOptions(self, line) :
"""return the line splited by the optionDelimiter atribute
Option list length is less or equal to the optionNames list length
"""
options = line.split(self.optionDelimiter, len(self.optionNames) - 1)
# get ride of leftover spaces
optionsStrip = list(map(str.strip, options))
return optionsStrip
def addChild(self, child, append=True) :
""" A.addChid(B) -> add B to A child list if B class name is in A white List"""
try:
# Will raise an exception if this child is not in the white list
self.childWhiteList.index(child.__class__.__name__)
# If no exception was raised, add child to child list
if append :
self.children.append(child)
else :
self.children.insert(0, child)
except ValueError:
# the child class is not in the white list
raise ChildNotPermitedError("", self, child)
def addChildren(self, children, append=True) :
"""Add a list of child"""
if append:
# Add children at the end of the child list
self.children.extend(children)
else:
# Add children at the begining of the child list
self.children = children + self.children
def childrenToString(self, prefix=""):
"""return raw printed children """
body = ""
for child in self.children :
body = body + child.__str__(prefix)
return body
def __str__(self, prefix="") :
"""return raw printed element"""
selfToString = prefix + " " + self.tag + " " + str(self.option)
return selfToString + "\n" + self.childrenToString(prefix + "\t")
def extractChildrenByClass(self, classTypeList) :
"""return all children whose class is in the list argument
return a list of all children whose class in the list "classTypeList" (second arguments)"""
selectedChildren = []
for child in self.children :
for classtype in classTypeList :
if child.__class__ == classtype :
selectedChildren.append(child)
break
return selectedChildren
def propagate (self, context=PropagationContext()):
"""call the propagate method of all children"""
for child in self.children :
child.propagate(context)
def getName(self):
"""return name option value. If none return "" """
return self.option.getOption("Name")
def setName(self, name):
self.option.setOption("Name", name)
def translate(self, translator):
for child in self.children:
child.translate(translator)
# ----------------------------------------------------------
class ElementWithTag (Element):
"""Element of this class are declared with a tag => line == "tag: .*" """
def extractOptions(self, line) :
lineWithoutTag = line.split(":", 1)[-1].strip()
options = super(ElementWithTag, self).extractOptions(lineWithoutTag)
return options
# ----------------------------------------------------------
class ElementWithInheritance(Element):
def propagate (self, context=PropagationContext) :
"""propagate some proprieties to children"""
# copy the context so that everything that hapend next will only affect
# children
contextCopy = context.copy()
# check for inheritance
self.Inheritance(contextCopy)
# call the propagate method of all children
super(ElementWithInheritance, self).propagate(contextCopy)
class ElementWithRuleInheritance(ElementWithInheritance):
"""class that will give to its children its rules"""
def ruleInheritance(self, context):
"""Add its rules to the context and get context rules"""
# extract all children rule and operator
childRules = self.extractChildrenByClass([Operator, Rule])
# get context rules
contextRules = context.getRules()
# adopt rules of the beginning of the context
self.addChildren(contextRules, append=False)
# add previously extract rules to the context
contextRules += childRules
# ----------------------------------------------------------
class EmptyLine (Element) :
"""This class represents an empty line.
Will raise "EmptyLineWarning" exception at instanciation."""
tag = "emptyLine"
match = re.compile(r"[ \t]*\n?$").match
def __init__ (self, line):
raise EmptyLineWarning(line)
# ----------------------------------------------------------
class Commentary(Element):
"""This class represents a commentary.
Will raise "CommentWarning" exception at instanciation."""
tag = "commentary"
optionNames = ["comment"]
match = re.compile(r"#").match
def __init__ (self, line):
raise CommentWarning(line)
# ----------------------------------------------------------
class Path (ElementWithInheritance) :
"""class implementing the "path = value" concept"""
tag = "path"
optionNames = ["Name", "value"]
match = re.compile(r".+=").match
optionDelimiter = "="
def translate(self, translator):
translator.setParameter(self.getName(), self.option.getOption("value"))
def Inheritance (self, context) :
"""check for path name inheritance"""
self.OptionsInheritance(context)
def OptionsInheritance (self, context) :
"""make configuration name inheritance """
context.getPathOptions().append(self.option.copy())
self.setName("/".join(context.getPathOptions().getOptionItems("Name")))
class GroupPath (Path, ElementWithTag) :
tag = "component"
match = re.compile(tag + r" *:").match
optionNames = ["Name"]
childWhiteList = ["Path", "GroupPath"]
def getPathNames (self) :
"""Return the list of all path child name"""
pathNames = []
paths = self.extractChildrenByClass([Path])
for path in paths :
pathNames.append(path.getName())
groupPaths = self.extractChildrenByClass([GroupPath])
for groupPath in groupPaths :
pathNames += groupPath.getPathNames()
return pathNames
def translate(self, translator):
for child in self.extractChildrenByClass([Path, GroupPath]):
child.translate(translator)
# ----------------------------------------------------------
class Rule (Element) :
"""class implementing the rule concept
A rule is composed of a criterion, a rule type and an criterion state.
It should not have any child and is propagated to all configuration in parent descendants.
"""
tag = "rule"
optionNames = ["criterion", "type", "element"]
match = re.compile(r"[a-zA-Z0-9_.]+ +(Is|IsNot|Includes|Excludes) +[a-zA-Z0-9_.]+").match
childWhiteList = []
def PFWSyntax (self, prefix=""):
script = prefix + \
self.option.getOption("criterion") + " " + \
self.option.getOption("type") + " " + \
self.option.getOption("element")
return script
class Operator (Rule) :
"""class implementing the operator concept
An operator contains rules and other operators
It is as rules propagated to all configuration children in parent descendants.
It should only have the name ANY or ALL to be understood by PFW.
"""
tag = "operator"
optionNames = ["Name"]
match = re.compile(r"ANY|ALL").match
childWhiteList = ["Rule", "Operator"]
syntax = { "ANY" : "Any" , "ALL" : "All"}
def PFWSyntax (self, prefix=""):
""" return a pfw rule (ex : "Any{criterion1 is state1}") generated from "self" and its children options"""
script = ""
script += prefix + \
self.syntax[self.getName()] + "{ "
rules = self.extractChildrenByClass([Rule, Operator])
PFWRules = []
for rule in rules :
PFWRules.append(rule.PFWSyntax(prefix + " "))
script += (" , ").join(PFWRules)
script += prefix + " }"
return script
# ----------------------------------------------------------
class Configuration (ElementWithRuleInheritance, ElementWithTag) :
tag = "configuration"
optionNames = ["Name"]
match = re.compile(r"conf *:").match
childWhiteList = ["Rule", "Operator", "Path", "GroupPath"]
def composition (self, context):
"""make all needed composition
Composition is the fact that group configuration with the same name defined
in a parent will give their rule children to this configuration
"""
name = self.getName()
sameNameConf = context.getConfigurations().getElementsFromName(name)
sameNameConf.reverse()
for configuration in sameNameConf :
# add same name configuration rule children to self child list
self.addChildren(configuration.extractChildrenByClass([Operator, Rule]), append=False)
def propagate (self, context=PropagationContext) :
"""propagate proprieties to children
make needed compositions, join ancestor name to its name,
and add rules previously defined rules"""
# make all needed composition
self.composition(context)
super(Configuration, self).propagate(context)
def Inheritance (self, context) :
"""make configuration name and rule inheritance"""
# check for configuration name inheritance
self.OptionsInheritance(context)
# check for rule inheritance
self.ruleInheritance(context)
def OptionsInheritance (self, context) :
"""make configuration name inheritance """
context.getConfigurationOptions().append(self.option.copy())
self.setName(".".join(context.getConfigurationOptions().getOptionItems("Name")))
def getRootPath (self) :
paths = self.extractChildrenByClass([Path, GroupPath])
rootPath = GroupPath()
rootPath.addChildren(paths)
return rootPath
def getConfigurableElements (self) :
"""return all path name defined in this configuration"""
return self.getRootPath().getPathNames()
def getRuleString(self):
"""Output this configuration's rule as a string"""
# Create a rootRule
ruleChildren = self.extractChildrenByClass([Rule, Operator])
# Do not create a root rule if there is only one fist level Operator rule
if len(ruleChildren) == 1 and ruleChildren[0].__class__ == Operator :
ruleroot = ruleChildren[0]
else :
ruleroot = Operator()
ruleroot.setName("ALL")
ruleroot.addChildren(ruleChildren)
return ruleroot.PFWSyntax()
def translate(self, translator):
translator.createConfiguration(self.getName())
translator.setRule(self.getRuleString())
paths = self.extractChildrenByClass([Path, GroupPath])
translator.setElementSequence(self.getConfigurableElements())
for path in paths:
path.translate(translator)
def copy (self) :
"""return a shallow copy of the configuration"""
# create configuration or subclass copy
confCopy = self.__class__()
# add children
confCopy.children = list(self.children)
# add option
confCopy.option = self.option.copy()
return confCopy
class GroupConfiguration (Configuration) :
tag = "GroupConfiguration"
optionNames = ["Name"]
match = re.compile(r"(supConf|confGroup|confType) *:").match
childWhiteList = ["Rule", "Operator", "GroupConfiguration", "Configuration", "GroupPath"]
def composition (self, context) :
"""add itself in context for configuration composition
Composition is the fact that group configuration with the same name defined
in a parent will give their rule children to this configuration
"""
# copyItself
selfCopy = self.copy()
# make all needed composition
super(GroupConfiguration, self).composition(context)
# add the copy in context for futur configuration composition
context.getConfigurations().append(selfCopy)
def getConfigurableElements (self) :
"""return a list. Each elements consist of a list of configurable element of a configuration
return a list consisting of all configurable elements for each configuration.
These configurable elements are organized in a list"""
configurableElements = []
configurations = self.extractChildrenByClass([Configuration])
for configuration in configurations :
configurableElements.append(configuration.getConfigurableElements())
groudeConfigurations = self.extractChildrenByClass([GroupConfiguration])
for groudeConfiguration in groudeConfigurations :
configurableElements += groudeConfiguration.getConfigurableElements()
return configurableElements
def translate(self, translator):
for child in self.extractChildrenByClass([Configuration, GroupConfiguration]):
child.translate(translator)
# ----------------------------------------------------------
class Domain (ElementWithRuleInheritance, ElementWithTag) :
tag = "domain"
sequenceAwareKeyword = "sequenceAware"
match = re.compile(r"domain *:").match
optionNames = ["Name", sequenceAwareKeyword]
childWhiteList = ["Configuration", "GroupConfiguration", "Rule", "Operator"]
def propagate (self, context=PropagationContext) :
""" propagate name, sequenceAwareness and rule to children"""
# call the propagate method of all children
super(Domain, self).propagate(context)
self.checkConfigurableElementUnicity()
def Inheritance (self, context) :
"""check for domain name, sequence awarness and rules inheritance"""
# check for domain name and sequence awarness inheritance
self.OptionsInheritance(context)
# check for rule inheritance
self.ruleInheritance(context)
def OptionsInheritance(self, context) :
""" make domain name and sequence awareness inheritance
join to the domain name all domain names defined in context and
if any domain in context is sequence aware, set sequenceAwareness to True"""
# add domain options to context
context.getDomainOptions().append(self.option.copy())
# set name to the junction of all domain name in context
self.setName(".".join(context.getDomainOptions().getOptionItems("Name")))
# get sequenceAwareness of all domains in context
sequenceAwareList = context.getDomainOptions().getOptionItems(self.sequenceAwareKeyword)
# or operation on all booleans in sequenceAwareList
sequenceAwareness = False
for sequenceAware in sequenceAwareList :
sequenceAwareness = sequenceAwareness or sequenceAware
# current domain sequenceAwareness = sequenceAwareness
self.option.setOption(self.sequenceAwareKeyword, sequenceAwareness)
def extractOptions(self, line) :
"""Extract options from the definition line"""
options = super(Domain, self).extractOptions(line)
sequenceAwareIndex = self.optionNames.index(self.sequenceAwareKeyword)
# translate the keyword self.sequenceAwareKeyword if specified to boolean True,
# to False otherwise
try :
if options[sequenceAwareIndex] == self.sequenceAwareKeyword :
options[sequenceAwareIndex] = True
else:
options[sequenceAwareIndex] = False
except IndexError :
options = options + [None] * (sequenceAwareIndex - len(options)) + [False]
return options
def getRootConfiguration (self) :
"""return the root configuration group"""
configurations = self.extractChildrenByClass([Configuration, GroupConfiguration])
configurationRoot = GroupConfiguration()
configurationRoot.addChildren(configurations)
return configurationRoot
# TODO: don't do that in the parser, let the PFW tell you that
def checkConfigurableElementUnicity (self):
""" check that all configurable elements defined in child configuration are the sames"""
# get a list. Each elements of is the configurable element list of a configuration
configurableElementsList = self.getRootConfiguration().getConfigurableElements()
# if at least two configurations in the domain
if len(configurableElementsList) > 1 :
# get first configuration configurable element list sort
configurableElementsList0 = list(configurableElementsList[0])
configurableElementsList0.sort()
for configurableElements in configurableElementsList :
# sort current configurable element list
auxConfigurableElements = list(configurableElements)
auxConfigurableElements.sort()
if auxConfigurableElements != configurableElementsList0 :
# if different, 2 configurations those not have the same configurable element list
# => one or more configurable element is missing in one of the 2 configuration
raise UndefinedParameter(self.getName())
def translate(self, translator):
sequence_aware = self.option.getOption(self.sequenceAwareKeyword)
translator.createDomain(self.getName(), sequence_aware)
configurations = self.getRootConfiguration()
configurableElementsList = configurations.getConfigurableElements()
# add configurable elements
if len(configurableElementsList) != 0 :
for configurableElement in configurableElementsList[0] :
translator.addElement(configurableElement)
configurations.translate(translator)
class GroupDomain (Domain) :
tag = "groupDomain"
match = re.compile(r"(supDomain|domainGroup) *:").match
childWhiteList = ["GroupDomain", "Domain", "GroupConfiguration", "Rule", "Operator"]
def translate(self, translator):
for child in self.extractChildrenByClass([Domain, GroupDomain]):
child.translate(translator)
# ----------------------------------------------------------
class Root(Element):
tag = "root"
childWhiteList = ["Domain", "GroupDomain"]
# ===========================================
""" Syntax error Exceptions"""
# ===========================================
class MySyntaxProblems(SyntaxError) :
comment = "syntax error in %(line)s "
def __init__(self, line=None, num=None):
self.setLine(line, num)
def __str__(self):
if self.line :
self.comment = self.comment % {"line" : repr(self.line)}
if self.num :
self.comment = "Line " + str(self.num) + ", " + self.comment
return self.comment
def setLine (self, line, num):
self.line = str(line)
self.num = num
# ---------------------------------------------------------
class MyPropagationError(MySyntaxProblems) :
""" Syntax error Exceptions used in the propagation step"""
pass
class UndefinedParameter(MyPropagationError) :
comment = "Configurations in domain '%(domainName)s' do not all set the same parameters "
def __init__ (self, domainName):
self.domainName = domainName
def __str__ (self):
return self.comment % { "domainName" : self.domainName }
# -----------------------------------------------------
""" Syntax error Exceptions used by parser"""
class MySyntaxError(MySyntaxProblems) :
""" Syntax error Exceptions used by parser"""
pass
class MySyntaxWarning(MySyntaxProblems) :
""" Syntax warning Exceptions used by parser"""
pass
class IndentationSyntaxError(MySyntaxError) :
comment = """syntax error in %(line)s has no father element.
You can only increment indentation by one tabutation per line")"""
class EmptyLineWarning(MySyntaxWarning):
comment = "warning : %(line)s is an empty line and has been ommited"
class CommentWarning(MySyntaxWarning):
comment = "warning : %(line)s is a commentary and has been ommited"
class ChildNotPermitedError(MySyntaxError):
def __init__(self, line, fatherElement, childElement):
self.comment = "syntax error in %(line)s, " + fatherElement.tag + " should not have a " + childElement.tag + " child."
super(ChildNotPermitedError, self).__init__(line)
class UnknownElementTypeError(MySyntaxError):
comment = " error in line %(line)s , not known element type were matched "
class SpaceInIndentationError(MySyntaxError):
comment = " error in ,%(line)s space is not permited in indentation"
# ============================================
"""Class creating the DOM elements from a stream"""
# ============================================
class ElementsFactory(object) :
"""Element factory, return an instance of the first matching element
Test each element list in elementClass and instanciate it if it's methode match returns True
The method match is called with input line as argument
"""
def __init__ (self):
self.elementClass = [
EmptyLine ,
Commentary,
GroupDomain,
Domain,
Path,
GroupConfiguration,
Configuration,
Operator,
Rule,
GroupPath
]
def createElementFromLine (self, line) :
"""return an instance of the first matching element
Test each element list in elementClass and instanciate it if it's methode match returns True
The method match is called with the argument line.
Raise UnknownElementTypeError if no element matched.
"""
for element in self.elementClass :
if element.match(line) :
# print (line + element.__class__.__name__)
return element(line)
# if we have not find any
raise UnknownElementTypeError(line)
#------------------------------------------------------
class Parser(object) :
"""Class implementing the parser"""
def __init__(self):
self.rankPattern = re.compile(r"^([\t ]*)(.*)")
self.elementFactory = ElementsFactory()
self.previousRank = 0
def __parseLine__(self, line):
rank, rest = self.__getRank__(line)
# instanciate the coresponding element
element = self.elementFactory.createElementFromLine(rest)
self.__checkIndentation__(rank)
return rank, element
def __getRank__(self, line):
"""return the rank, the name and the option of the input line
the rank is the number of tabulation (\t) at the line beginning.
the rest is the rest of the line."""
# split line in rank and rest
rank = self.rankPattern.match(line)
if rank :
rank, rest = rank.group(1, 2)
else :
raise MySyntaxError(line)
# check for empty line
if rest == "" :
raise EmptyLineWarning(line)
# check for space in indentation
if rank.find(" ") > -1 :
raise SpaceInIndentationError(line)
rank = len (rank) + 1 # rank starts at 1
return rank, rest
def __checkIndentation__(self, rank):
"""check if indentation > previous indentation + 1. If so, raise IndentationSyntaxError"""
if (rank > self.previousRank + 1) :
raise IndentationSyntaxError()
self.previousRank = rank
def parse(self, stream, verbose=False):
"""parse a stream, usually a opened file"""
myroot = Root("root")
context = [myroot] # root is element of rank 0
warnings = ""
for num, line in enumerate(stream):
try:
rank, myelement = self.__parseLine__(line)
while len(context) > rank :
context.pop()
context.append(myelement)
context[-2].addChild(myelement)
except MySyntaxWarning as ex:
ex.setLine(line, num + 1)
if verbose :
sys.stderr.write("{}\n".format(ex))
except MySyntaxError as ex :
ex.setLine(line, num + 1)
raise
return myroot