| #!/usr/bin/python3 |
| # -*-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 |
| |
| # 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 |
| |
| 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 |
| |