• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/python2
2# -*-coding:utf-8 -*
3
4# Copyright (c) 2011-2014, Intel Corporation
5# All rights reserved.
6#
7# Redistribution and use in source and binary forms, with or without modification,
8# are permitted provided that the following conditions are met:
9#
10# 1. Redistributions of source code must retain the above copyright notice, this
11# list of conditions and the following disclaimer.
12#
13# 2. Redistributions in binary form must reproduce the above copyright notice,
14# this list of conditions and the following disclaimer in the documentation and/or
15# other materials provided with the distribution.
16#
17# 3. Neither the name of the copyright holder nor the names of its contributors
18# may be used to endorse or promote products derived from this software without
19# specific prior written permission.
20#
21# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
22# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
23# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
25# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
26# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
27# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
28# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31
32
33
34import re
35import sys
36import copy
37# For Python 2.x/3.x compatibility
38try:
39    from itertools import izip as zip
40    from itertools import imap as map
41except:
42    pass
43
44# =====================================================================
45""" Context classes, used during propagation and the "to PFW script" step """
46# =====================================================================
47
48class PropagationContextItem(list) :
49    """Handle an item during the propagation step"""
50    def __copy__(self):
51        """C.__copy__() -> a shallow copy of C"""
52        return self.__class__(self)
53
54class PropagationContextElement(PropagationContextItem) :
55    """Handle an Element during the propagation step"""
56    def getElementsFromName(self, name):
57        matchingElements = []
58        for element in self :
59            if element.getName() == name :
60                matchingElements.append(element)
61        return matchingElements
62
63
64class PropagationContextOption(PropagationContextItem) :
65    """Handle an Option during the propagation step"""
66    def getOptionItems (self, itemName):
67        items = []
68        for options in self :
69            items.append(options.getOption(itemName))
70        return items
71
72
73class PropagationContext() :
74    """Handle the context during the propagation step"""
75    def __init__(self, propagationContext=None) :
76
77        if propagationContext == None :
78            self._context = {
79                "DomainOptions" : PropagationContextOption() ,
80                "Configurations" : PropagationContextElement() ,
81                "ConfigurationOptions" : PropagationContextOption() ,
82                "Rules" : PropagationContextElement() ,
83                "PathOptions" : PropagationContextOption() ,
84        }
85        else :
86            self._context = propagationContext
87
88    def copy(self):
89        """return a copy of the context"""
90        contextCopy = self._context.copy()
91
92        for key in iter(self._context) :
93            contextCopy[key] = contextCopy[key].__copy__()
94
95        return self.__class__(contextCopy)
96
97    def getDomainOptions (self):
98        return self._context["DomainOptions"]
99
100    def getConfigurations (self):
101        return self._context["Configurations"]
102
103    def getConfigurationOptions (self):
104        return self._context["ConfigurationOptions"]
105
106    def getRules (self):
107        return self._context["Rules"]
108
109    def getPathOptions (self):
110        return self._context["PathOptions"]
111
112
113# =====================================================
114"""Element option container"""
115# =====================================================
116
117class Options () :
118    """handle element options"""
119    def __init__(self, options=[], optionNames=[]) :
120        self.options = dict(zip(optionNames, options))
121        # print(options,optionNames,self.options)
122
123
124    def __str__(self) :
125        ops2str = []
126        for name, argument in list(self.options.items()) :
127            ops2str.append(str(name) + "=\"" + str(argument) + "\"")
128
129        return " ".join(ops2str)
130
131    def getOption(self, name):
132        """get option by its name, if it does not exist return empty string"""
133        return self.options.get(name, "")
134
135    def setOption(self, name, newOption):
136        """set option by its name"""
137        self.options[name] = newOption
138
139    def copy (self):
140        """D.copy() -> a shallow copy of D"""
141        copy = Options()
142        copy.options = self.options.copy()
143        return copy
144
145# ====================================================
146"""Definition of all element class"""
147# ====================================================
148
149class Element(object):
150    """ implement a basic element
151
152    It is the class base for all other elements as Domain, Configuration..."""
153    tag = "unknown"
154    optionNames = ["Name"]
155    childWhiteList = []
156    optionDelimiter = " "
157
158    def __init__(self, line=None) :
159
160        if line == None :
161            self.option = Options([], self.optionNames)
162        else :
163            self.option = self.optionFromLine(line)
164
165        self.children = []
166
167    def optionFromLine(self, line) :
168        # get ride of spaces
169        line = line.strip()
170
171        options = self.extractOptions(line)
172
173        return Options(options, self.optionNames)
174
175    def extractOptions(self, line) :
176        """return the line splited by the optionDelimiter atribute
177
178        Option list length is less or equal to the optionNames list length
179        """
180        options = line.split(self.optionDelimiter, len(self.optionNames) - 1)
181
182        # get ride of leftover spaces
183        optionsStrip = list(map(str.strip, options))
184
185        return optionsStrip
186
187    def addChild(self, child, append=True) :
188        """ A.addChid(B) -> add B to A child list if B class name is in A white List"""
189        try:
190            # Will raise an exception if this child is not in the white list
191            self.childWhiteList.index(child.__class__.__name__)
192            # If no exception was raised, add child to child list
193
194            if append :
195                self.children.append(child)
196            else :
197                self.children.insert(0, child)
198
199        except ValueError:
200            # the child class is not in the white list
201            raise ChildNotPermitedError("", self, child)
202
203    def addChildren(self, children, append=True) :
204        """Add a list of child"""
205        if append:
206            # Add children at the end of the child list
207            self.children.extend(children)
208        else:
209            # Add children at the begining of the child list
210            self.children = children + self.children
211
212    def childrenToString(self, prefix=""):
213        """return raw printed children """
214        body = ""
215        for child in self.children :
216            body = body + child.__str__(prefix)
217
218        return body
219
220    def __str__(self, prefix="") :
221        """return raw printed element"""
222        selfToString = prefix + " " + self.tag + " " + str(self.option)
223        return selfToString + "\n" + self.childrenToString(prefix + "\t")
224
225    def extractChildrenByClass(self, classTypeList) :
226        """return all children whose class is in the list argument
227
228        return a list of all children whose class in the list "classTypeList" (second arguments)"""
229        selectedChildren = []
230
231        for child in  self.children :
232            for classtype in classTypeList :
233                if child.__class__ == classtype :
234                    selectedChildren.append(child)
235                    break
236        return selectedChildren
237
238    def propagate (self, context=PropagationContext()):
239        """call the propagate method of all children"""
240        for child in  self.children :
241            child.propagate(context)
242
243    def getName(self):
244        """return name option value. If none return "" """
245        return self.option.getOption("Name")
246
247    def setName(self, name):
248        self.option.setOption("Name", name)
249
250    def translate(self, translator):
251        for child in self.children:
252            child.translate(translator)
253
254# ----------------------------------------------------------
255
256class ElementWithTag (Element):
257    """Element of this class are declared with a tag  => line == "tag: .*" """
258    def extractOptions(self, line) :
259        lineWithoutTag = line.split(":", 1)[-1].strip()
260        options = super(ElementWithTag, self).extractOptions(lineWithoutTag)
261        return options
262
263# ----------------------------------------------------------
264
265class ElementWithInheritance(Element):
266    def propagate (self, context=PropagationContext) :
267        """propagate some proprieties to children"""
268
269        # copy the context so that everything that hapend next will only affect
270        # children
271        contextCopy = context.copy()
272
273        # check for inheritance
274        self.Inheritance(contextCopy)
275
276        # call the propagate method of all children
277        super(ElementWithInheritance, self).propagate(contextCopy)
278
279
280class ElementWithRuleInheritance(ElementWithInheritance):
281    """class that will give to its children its rules"""
282    def ruleInheritance(self, context):
283        """Add its rules to the context and get context rules"""
284
285        # extract all children rule and operator
286        childRules = self.extractChildrenByClass([Operator, Rule])
287
288        # get context rules
289        contextRules = context.getRules()
290
291        # adopt rules of the beginning of the context
292        self.addChildren(contextRules, append=False)
293
294        # add previously extract rules to the context
295        contextRules += childRules
296
297
298# ----------------------------------------------------------
299
300class EmptyLine (Element) :
301    """This class represents an empty line.
302
303    Will raise "EmptyLineWarning" exception at instanciation."""
304
305    tag = "emptyLine"
306    match = re.compile(r"[ \t]*\n?$").match
307    def __init__ (self, line):
308       raise EmptyLineWarning(line)
309
310# ----------------------------------------------------------
311
312class Commentary(Element):
313    """This class represents a commentary.
314
315    Will raise "CommentWarning" exception at instanciation."""
316
317    tag = "commentary"
318    optionNames = ["comment"]
319    match = re.compile(r"#").match
320    def __init__ (self, line):
321       raise CommentWarning(line)
322
323# ----------------------------------------------------------
324
325class Path (ElementWithInheritance) :
326    """class implementing the "path = value" concept"""
327    tag = "path"
328    optionNames = ["Name", "value"]
329    match = re.compile(r".+=").match
330    optionDelimiter = "="
331
332    def translate(self, translator):
333        translator.setParameter(self.getName(), self.option.getOption("value"))
334
335    def Inheritance (self, context) :
336        """check for path name inheritance"""
337        self.OptionsInheritance(context)
338
339    def OptionsInheritance (self, context) :
340        """make configuration name inheritance """
341
342        context.getPathOptions().append(self.option.copy())
343        self.setName("/".join(context.getPathOptions().getOptionItems("Name")))
344
345
346class GroupPath (Path, ElementWithTag) :
347    tag = "component"
348    match = re.compile(tag + r" *:").match
349    optionNames = ["Name"]
350    childWhiteList = ["Path", "GroupPath"]
351
352    def getPathNames (self) :
353        """Return the list of all path child name"""
354
355        pathNames = []
356
357        paths = self.extractChildrenByClass([Path])
358        for path in paths :
359            pathNames.append(path.getName())
360
361        groupPaths = self.extractChildrenByClass([GroupPath])
362        for groupPath in groupPaths :
363            pathNames += groupPath.getPathNames()
364
365        return pathNames
366
367    def translate(self, translator):
368        for child in self.extractChildrenByClass([Path, GroupPath]):
369            child.translate(translator)
370
371# ----------------------------------------------------------
372
373class Rule (Element) :
374    """class implementing the rule concept
375
376    A rule is composed of a criterion, a rule type and an criterion state.
377    It should not have any child and is propagated to all configuration in parent descendants.
378    """
379
380    tag = "rule"
381    optionNames = ["criterion", "type", "element"]
382    match = re.compile(r"[a-zA-Z0-9_.]+ +(Is|IsNot|Includes|Excludes) +[a-zA-Z0-9_.]+").match
383    childWhiteList = []
384
385    def PFWSyntax (self, prefix=""):
386
387        script = prefix + \
388                    self.option.getOption("criterion") + " " + \
389                    self.option.getOption("type") + " " + \
390                    self.option.getOption("element")
391
392        return script
393
394
395class Operator (Rule) :
396    """class implementing the operator concept
397
398    An operator contains rules and other operators
399    It is as rules propagated to all configuration children in parent descendants.
400    It should only have the name ANY or ALL to be understood by PFW.
401    """
402
403    tag = "operator"
404    optionNames = ["Name"]
405    match = re.compile(r"ANY|ALL").match
406    childWhiteList = ["Rule", "Operator"]
407
408    syntax = { "ANY" : "Any" , "ALL" : "All"}
409
410    def PFWSyntax (self, prefix=""):
411        """ return a pfw rule (ex : "Any{criterion1 is state1}") generated from "self" and its children options"""
412        script = ""
413
414        script += prefix + \
415                    self.syntax[self.getName()] + "{ "
416
417        rules = self.extractChildrenByClass([Rule, Operator])
418
419        PFWRules = []
420        for rule in rules :
421            PFWRules.append(rule.PFWSyntax(prefix + "    "))
422
423        script += (" , ").join(PFWRules)
424
425        script += prefix + " }"
426
427        return script
428
429# ----------------------------------------------------------
430
431class Configuration (ElementWithRuleInheritance, ElementWithTag) :
432    tag = "configuration"
433    optionNames = ["Name"]
434    match = re.compile(r"conf *:").match
435    childWhiteList = ["Rule", "Operator", "Path", "GroupPath"]
436
437    def composition (self, context):
438        """make all needed composition
439
440        Composition is the fact that group configuration with the same name defined
441        in a parent will give their rule children to this configuration
442        """
443
444        name = self.getName()
445        sameNameConf = context.getConfigurations().getElementsFromName(name)
446
447        sameNameConf.reverse()
448
449        for configuration in sameNameConf :
450            # add same name configuration rule children to self child list
451            self.addChildren(configuration.extractChildrenByClass([Operator, Rule]), append=False)
452
453
454    def propagate (self, context=PropagationContext) :
455        """propagate proprieties to children
456
457        make needed compositions, join ancestor name to its name,
458        and add rules previously defined rules"""
459
460        # make all needed composition
461        self.composition(context)
462
463        super(Configuration, self).propagate(context)
464
465    def Inheritance (self, context) :
466        """make configuration name and rule inheritance"""
467        # check for configuration name inheritance
468        self.OptionsInheritance(context)
469
470        # check for rule inheritance
471        self.ruleInheritance(context)
472
473    def OptionsInheritance (self, context) :
474        """make configuration name inheritance """
475
476        context.getConfigurationOptions().append(self.option.copy())
477        self.setName(".".join(context.getConfigurationOptions().getOptionItems("Name")))
478
479
480    def getRootPath (self) :
481
482        paths = self.extractChildrenByClass([Path, GroupPath])
483
484        rootPath = GroupPath()
485        rootPath.addChildren(paths)
486
487        return rootPath
488
489    def getConfigurableElements (self) :
490        """return all path name defined in this configuration"""
491
492        return self.getRootPath().getPathNames()
493
494    def getRuleString(self):
495        """Output this configuration's rule as a string"""
496
497        # Create a rootRule
498        ruleChildren = self.extractChildrenByClass([Rule, Operator])
499
500        # Do not create a root rule if there is only one fist level Operator rule
501        if len(ruleChildren) == 1 and ruleChildren[0].__class__ == Operator :
502            ruleroot = ruleChildren[0]
503
504        else :
505            ruleroot = Operator()
506            ruleroot.setName("ALL")
507            ruleroot.addChildren(ruleChildren)
508
509        return ruleroot.PFWSyntax()
510
511    def translate(self, translator):
512        translator.createConfiguration(self.getName())
513        translator.setRule(self.getRuleString())
514
515        paths = self.extractChildrenByClass([Path, GroupPath])
516        translator.setElementSequence(self.getConfigurableElements())
517        for path in paths:
518            path.translate(translator)
519
520    def copy (self) :
521        """return a shallow copy of the configuration"""
522
523        # create configuration or subclass copy
524        confCopy = self.__class__()
525
526        # add children
527        confCopy.children = list(self.children)
528
529        # add option
530        confCopy.option = self.option.copy()
531
532        return confCopy
533
534class GroupConfiguration (Configuration) :
535    tag = "GroupConfiguration"
536    optionNames = ["Name"]
537    match = re.compile(r"(supConf|confGroup|confType) *:").match
538    childWhiteList = ["Rule", "Operator", "GroupConfiguration", "Configuration", "GroupPath"]
539
540    def composition (self, context) :
541        """add itself in context for configuration composition
542
543        Composition is the fact that group configuration with the same name defined
544        in a parent will give their rule children to this configuration
545        """
546
547        # copyItself
548        selfCopy = self.copy()
549
550        # make all needed composition
551        super(GroupConfiguration, self).composition(context)
552
553        # add the copy in context for futur configuration composition
554        context.getConfigurations().append(selfCopy)
555
556
557    def getConfigurableElements (self) :
558        """return a list. Each elements consist of a list of configurable element of a configuration
559
560        return a list consisting of all configurable elements for each configuration.
561        These configurable elements are organized in a list"""
562        configurableElements = []
563
564        configurations = self.extractChildrenByClass([Configuration])
565        for configuration in configurations :
566            configurableElements.append(configuration.getConfigurableElements())
567
568        groudeConfigurations = self.extractChildrenByClass([GroupConfiguration])
569        for groudeConfiguration in groudeConfigurations :
570            configurableElements += groudeConfiguration.getConfigurableElements()
571
572        return configurableElements
573
574    def translate(self, translator):
575        for child in self.extractChildrenByClass([Configuration, GroupConfiguration]):
576            child.translate(translator)
577
578# ----------------------------------------------------------
579
580class Domain (ElementWithRuleInheritance, ElementWithTag) :
581    tag = "domain"
582    sequenceAwareKeyword = "sequenceAware"
583
584    match = re.compile(r"domain *:").match
585    optionNames = ["Name", sequenceAwareKeyword]
586    childWhiteList = ["Configuration", "GroupConfiguration", "Rule", "Operator"]
587
588    def propagate (self, context=PropagationContext) :
589        """ propagate name, sequenceAwareness and rule to children"""
590
591        # call the propagate method of all children
592        super(Domain, self).propagate(context)
593
594        self.checkConfigurableElementUnicity()
595
596    def Inheritance (self, context) :
597        """check for domain name, sequence awarness and rules inheritance"""
598        # check for domain name and sequence awarness inheritance
599        self.OptionsInheritance(context)
600
601        # check for rule inheritance
602        self.ruleInheritance(context)
603
604    def OptionsInheritance(self, context) :
605        """ make domain name and sequence awareness inheritance
606
607        join to the domain name all domain names defined in context and
608        if any domain in context is sequence aware, set sequenceAwareness to True"""
609
610        # add domain options to context
611        context.getDomainOptions().append(self.option.copy())
612
613        # set name to the junction of all domain name in context
614        self.setName(".".join(context.getDomainOptions().getOptionItems("Name")))
615
616        # get sequenceAwareness of all domains in context
617        sequenceAwareList = context.getDomainOptions().getOptionItems(self.sequenceAwareKeyword)
618        # or operation on all booleans in sequenceAwareList
619        sequenceAwareness = False
620        for sequenceAware in sequenceAwareList :
621            sequenceAwareness = sequenceAwareness or sequenceAware
622        # current domain sequenceAwareness = sequenceAwareness
623        self.option.setOption(self.sequenceAwareKeyword, sequenceAwareness)
624
625
626    def extractOptions(self, line) :
627        """Extract options from the definition line"""
628        options = super(Domain, self).extractOptions(line)
629
630        sequenceAwareIndex = self.optionNames.index(self.sequenceAwareKeyword)
631
632        # translate the keyword self.sequenceAwareKeyword if specified to boolean True,
633        # to False otherwise
634        try :
635            if options[sequenceAwareIndex] == self.sequenceAwareKeyword :
636               options[sequenceAwareIndex] = True
637            else:
638               options[sequenceAwareIndex] = False
639        except IndexError :
640            options = options + [None] * (sequenceAwareIndex - len(options)) + [False]
641        return options
642
643    def getRootConfiguration (self) :
644        """return the root configuration group"""
645        configurations = self.extractChildrenByClass([Configuration, GroupConfiguration])
646
647        configurationRoot = GroupConfiguration()
648
649        configurationRoot.addChildren(configurations)
650
651        return configurationRoot
652
653    # TODO: don't do that in the parser, let the PFW tell you that
654    def checkConfigurableElementUnicity (self):
655        """ check that all configurable elements defined in child configuration are the sames"""
656
657        # get a list. Each elements of is the configurable element list of a configuration
658        configurableElementsList = self.getRootConfiguration().getConfigurableElements()
659
660        # if at least two configurations in the domain
661        if len(configurableElementsList) > 1 :
662
663            # get first configuration configurable element list sort
664            configurableElementsList0 = list(configurableElementsList[0])
665            configurableElementsList0.sort()
666
667            for configurableElements in configurableElementsList :
668                # sort current configurable element list
669                auxConfigurableElements = list(configurableElements)
670                auxConfigurableElements.sort()
671
672                if auxConfigurableElements != configurableElementsList0 :
673                    # if different, 2 configurations those not have the same configurable element list
674                    # => one or more configurable element is missing in one of the 2 configuration
675                    raise UndefinedParameter(self.getName())
676
677
678    def translate(self, translator):
679        sequence_aware = self.option.getOption(self.sequenceAwareKeyword)
680        translator.createDomain(self.getName(), sequence_aware)
681
682        configurations = self.getRootConfiguration()
683        configurableElementsList = configurations.getConfigurableElements()
684
685        # add configurable elements
686        if len(configurableElementsList) != 0 :
687            for configurableElement in configurableElementsList[0] :
688                translator.addElement(configurableElement)
689
690        configurations.translate(translator)
691
692class GroupDomain (Domain) :
693    tag = "groupDomain"
694    match = re.compile(r"(supDomain|domainGroup) *:").match
695    childWhiteList = ["GroupDomain", "Domain", "GroupConfiguration", "Rule", "Operator"]
696
697    def translate(self, translator):
698        for child in self.extractChildrenByClass([Domain, GroupDomain]):
699            child.translate(translator)
700
701# ----------------------------------------------------------
702
703class Root(Element):
704    tag = "root"
705    childWhiteList = ["Domain", "GroupDomain"]
706
707
708# ===========================================
709""" Syntax error Exceptions"""
710# ===========================================
711
712class MySyntaxProblems(SyntaxError) :
713    comment = "syntax error in %(line)s "
714
715    def __init__(self, line=None, num=None):
716        self.setLine(line, num)
717
718    def __str__(self):
719
720        if self.line :
721            self.comment = self.comment % {"line" : repr(self.line)}
722        if self.num :
723            self.comment = "Line " + str(self.num) + ", " + self.comment
724        return self.comment
725
726    def setLine (self, line, num):
727        self.line = str(line)
728        self.num = num
729
730
731# ---------------------------------------------------------
732
733class MyPropagationError(MySyntaxProblems) :
734    """ Syntax error Exceptions used in the propagation step"""
735    pass
736
737class UndefinedParameter(MyPropagationError) :
738    comment = "Configurations in domain '%(domainName)s' do not all set the same parameters "
739    def __init__ (self, domainName):
740        self.domainName = domainName
741    def __str__ (self):
742        return self.comment % { "domainName" : self.domainName }
743
744
745# -----------------------------------------------------
746""" Syntax error Exceptions used by parser"""
747
748class MySyntaxError(MySyntaxProblems) :
749    """ Syntax error Exceptions used by parser"""
750    pass
751
752class MySyntaxWarning(MySyntaxProblems) :
753    """ Syntax warning Exceptions used by parser"""
754    pass
755
756class IndentationSyntaxError(MySyntaxError) :
757    comment = """syntax error in %(line)s has no father element.
758    You can only increment indentation by one tabutation per line")"""
759
760class EmptyLineWarning(MySyntaxWarning):
761    comment = "warning : %(line)s is an empty line and has been ommited"
762
763class CommentWarning(MySyntaxWarning):
764    comment = "warning : %(line)s is a commentary and has been ommited"
765
766class ChildNotPermitedError(MySyntaxError):
767    def __init__(self, line, fatherElement, childElement):
768        self.comment = "syntax error in %(line)s, " + fatherElement.tag + " should not have a " + childElement.tag + " child."
769        super(ChildNotPermitedError, self).__init__(line)
770
771
772class UnknownElementTypeError(MySyntaxError):
773    comment = " error in line %(line)s , not known element type were matched "
774
775class SpaceInIndentationError(MySyntaxError):
776    comment = " error in ,%(line)s space is not permited in indentation"
777
778
779# ============================================
780"""Class creating the DOM elements from a stream"""
781# ============================================
782
783class ElementsFactory(object)  :
784    """Element factory, return an instance of the first matching element
785
786    Test each element list in elementClass and instanciate it if it's methode match returns True
787    The method match is called with input line as argument
788    """
789    def __init__ (self):
790        self.elementClass = [
791        EmptyLine ,
792        Commentary,
793        GroupDomain,
794        Domain,
795        Path,
796        GroupConfiguration,
797        Configuration,
798        Operator,
799        Rule,
800        GroupPath
801        ]
802
803    def createElementFromLine (self, line) :
804        """return an instance of the first matching element
805
806        Test each element list in elementClass and instanciate it if it's methode match returns True
807        The method match is called with the argument line.
808        Raise UnknownElementTypeError if no element matched.
809        """
810        for element in self.elementClass :
811            if element.match(line) :
812                # print (line + element.__class__.__name__)
813                return element(line)
814        # if we have not find any
815        raise UnknownElementTypeError(line)
816
817#------------------------------------------------------
818
819class Parser(object) :
820    """Class implementing the parser"""
821    def __init__(self):
822        self.rankPattern = re.compile(r"^([\t ]*)(.*)")
823        self.elementFactory = ElementsFactory()
824        self.previousRank = 0
825
826    def __parseLine__(self, line):
827
828        rank, rest = self.__getRank__(line)
829
830        # instanciate the coresponding element
831        element = self.elementFactory.createElementFromLine(rest)
832
833        self.__checkIndentation__(rank)
834
835        return rank, element
836
837    def __getRank__(self, line):
838        """return the rank, the name and the option of the input line
839
840the rank is the number of tabulation (\t) at the line beginning.
841the rest is the rest of the line."""
842        # split line in rank and rest
843        rank = self.rankPattern.match(line)
844        if rank :
845            rank, rest = rank.group(1, 2)
846        else :
847            raise MySyntaxError(line)
848
849        # check for empty line
850        if rest == "" :
851            raise EmptyLineWarning(line)
852
853        # check for space in indentation
854        if rank.find(" ") > -1 :
855            raise SpaceInIndentationError(line)
856
857        rank = len (rank) + 1  # rank starts at 1
858
859
860        return rank, rest
861
862
863    def __checkIndentation__(self, rank):
864        """check if indentation > previous indentation + 1. If so, raise IndentationSyntaxError"""
865        if (rank > self.previousRank + 1) :
866            raise IndentationSyntaxError()
867        self.previousRank = rank
868
869    def parse(self, stream, verbose=False):
870        """parse a stream, usually a opened file"""
871        myroot = Root("root")
872        context = [myroot]  # root is element of rank 0
873        warnings = ""
874
875        for num, line in enumerate(stream):
876            try:
877                rank, myelement = self.__parseLine__(line)
878
879                while len(context) > rank :
880                    context.pop()
881                context.append(myelement)
882                context[-2].addChild(myelement)
883
884            except MySyntaxWarning as ex:
885                ex.setLine(line, num + 1)
886                if verbose :
887                    sys.stderr.write("{}\n".format(ex))
888
889            except MySyntaxError as  ex :
890                ex.setLine(line, num + 1)
891                raise
892
893        return myroot
894
895