1#!/usr/bin/python3
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
36
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" \
412        and its children options"""
413        script = ""
414
415        script += prefix + \
416                    self.syntax[self.getName()] + "{ "
417
418        rules = self.extractChildrenByClass([Rule, Operator])
419
420        PFWRules = []
421        for rule in rules:
422            PFWRules.append(rule.PFWSyntax(prefix + "    "))
423
424        script += (" , ").join(PFWRules)
425
426        script += prefix + " }"
427
428        return script
429
430# ----------------------------------------------------------
431
432class Configuration(ElementWithRuleInheritance, ElementWithTag):
433    tag = "configuration"
434    optionNames = ["Name"]
435    match = re.compile(r"conf *:").match
436    childWhiteList = ["Rule", "Operator", "Path", "GroupPath"]
437
438    def composition(self, context):
439        """make all needed composition
440
441        Composition is the fact that group configuration with the same name defined
442        in a parent will give their rule children to this configuration
443        """
444
445        name = self.getName()
446        sameNameConf = context.getConfigurations().getElementsFromName(name)
447
448        sameNameConf.reverse()
449
450        for configuration in sameNameConf:
451            # add same name configuration rule children to self child list
452            self.addChildren(configuration.extractChildrenByClass([Operator, Rule]), append=False)
453
454
455    def propagate(self, context=PropagationContext):
456        """propagate proprieties to children
457
458        make needed compositions, join ancestor name to its name,
459        and add rules previously defined rules"""
460
461        # make all needed composition
462        self.composition(context)
463
464        super(Configuration, self).propagate(context)
465
466    def Inheritance(self, context):
467        """make configuration name and rule inheritance"""
468        # check for configuration name inheritance
469        self.OptionsInheritance(context)
470
471        # check for rule inheritance
472        self.ruleInheritance(context)
473
474    def OptionsInheritance(self, context):
475        """make configuration name inheritance """
476
477        context.getConfigurationOptions().append(self.option.copy())
478        self.setName(".".join(context.getConfigurationOptions().getOptionItems("Name")))
479
480
481    def getRootPath(self):
482
483        paths = self.extractChildrenByClass([Path, GroupPath])
484
485        rootPath = GroupPath()
486        rootPath.addChildren(paths)
487
488        return rootPath
489
490    def getConfigurableElements(self):
491        """return all path name defined in this configuration"""
492
493        return self.getRootPath().getPathNames()
494
495    def getRuleString(self):
496        """Output this configuration's rule as a string"""
497
498        # Create a rootRule
499        ruleChildren = self.extractChildrenByClass([Rule, Operator])
500
501        # Do not create a root rule if there is only one fist level Operator rule
502        if len(ruleChildren) == 1 and ruleChildren[0].__class__ == Operator:
503            ruleroot = ruleChildren[0]
504
505        else:
506            ruleroot = Operator()
507            ruleroot.setName("ALL")
508            ruleroot.addChildren(ruleChildren)
509
510        return ruleroot.PFWSyntax()
511
512    def translate(self, translator):
513        translator.createConfiguration(self.getName())
514        translator.setRule(self.getRuleString())
515
516        paths = self.extractChildrenByClass([Path, GroupPath])
517        translator.setElementSequence(self.getConfigurableElements())
518        for path in paths:
519            path.translate(translator)
520
521    def copy(self):
522        """return a shallow copy of the configuration"""
523
524        # create configuration or subclass copy
525        confCopy = self.__class__()
526
527        # add children
528        confCopy.children = list(self.children)
529
530        # add option
531        confCopy.option = self.option.copy()
532
533        return confCopy
534
535class GroupConfiguration(Configuration):
536    tag = "GroupConfiguration"
537    optionNames = ["Name"]
538    match = re.compile(r"(supConf|confGroup|confType) *:").match
539    childWhiteList = ["Rule", "Operator", "GroupConfiguration", "Configuration", "GroupPath"]
540
541    def composition(self, context):
542        """add itself in context for configuration composition
543
544        Composition is the fact that group configuration with the same name defined
545        in a parent will give their rule children to this configuration
546        """
547
548        # copyItself
549        selfCopy = self.copy()
550
551        # make all needed composition
552        super(GroupConfiguration, self).composition(context)
553
554        # add the copy in context for futur configuration composition
555        context.getConfigurations().append(selfCopy)
556
557
558    def getConfigurableElements(self):
559        """return a list. Each elements consist of a list of configurable element of a configuration
560
561        return a list consisting of all configurable elements for each configuration.
562        These configurable elements are organized in a list"""
563        configurableElements = []
564
565        configurations = self.extractChildrenByClass([Configuration])
566        for configuration in configurations:
567            configurableElements.append(configuration.getConfigurableElements())
568
569        groudeConfigurations = self.extractChildrenByClass([GroupConfiguration])
570        for groudeConfiguration in groudeConfigurations:
571            configurableElements += groudeConfiguration.getConfigurableElements()
572
573        return configurableElements
574
575    def translate(self, translator):
576        for child in self.extractChildrenByClass([Configuration, GroupConfiguration]):
577            child.translate(translator)
578
579# ----------------------------------------------------------
580
581class Domain(ElementWithRuleInheritance, ElementWithTag):
582    tag = "domain"
583    sequenceAwareKeyword = "sequenceAware"
584
585    match = re.compile(r"domain *:").match
586    optionNames = ["Name", sequenceAwareKeyword]
587    childWhiteList = ["Configuration", "GroupConfiguration", "Rule", "Operator"]
588
589    def propagate(self, context=PropagationContext):
590        """ propagate name, sequenceAwareness and rule to children"""
591
592        # call the propagate method of all children
593        super(Domain, self).propagate(context)
594
595        self.checkConfigurableElementUnicity()
596
597    def Inheritance(self, context):
598        """check for domain name, sequence awarness and rules inheritance"""
599        # check for domain name and sequence awarness inheritance
600        self.OptionsInheritance(context)
601
602        # check for rule inheritance
603        self.ruleInheritance(context)
604
605    def OptionsInheritance(self, context):
606        """ make domain name and sequence awareness inheritance
607
608        join to the domain name all domain names defined in context and
609        if any domain in context is sequence aware, set sequenceAwareness to True"""
610
611        # add domain options to context
612        context.getDomainOptions().append(self.option.copy())
613
614        # set name to the junction of all domain name in context
615        self.setName(".".join(context.getDomainOptions().getOptionItems("Name")))
616
617        # get sequenceAwareness of all domains in context
618        sequenceAwareList = context.getDomainOptions().getOptionItems(self.sequenceAwareKeyword)
619        # or operation on all booleans in sequenceAwareList
620        sequenceAwareness = False
621        for sequenceAware in sequenceAwareList:
622            sequenceAwareness = sequenceAwareness or sequenceAware
623        # current domain sequenceAwareness = sequenceAwareness
624        self.option.setOption(self.sequenceAwareKeyword, sequenceAwareness)
625
626
627    def extractOptions(self, line):
628        """Extract options from the definition line"""
629        options = super(Domain, self).extractOptions(line)
630
631        sequenceAwareIndex = self.optionNames.index(self.sequenceAwareKeyword)
632
633        # translate the keyword self.sequenceAwareKeyword if specified to boolean True,
634        # to False otherwise
635        try:
636            if options[sequenceAwareIndex] == self.sequenceAwareKeyword:
637                options[sequenceAwareIndex] = True
638            else:
639                options[sequenceAwareIndex] = False
640        except IndexError:
641            options = options + [None] * (sequenceAwareIndex - len(options)) + [False]
642        return options
643
644    def getRootConfiguration(self):
645        """return the root configuration group"""
646        configurations = self.extractChildrenByClass([Configuration, GroupConfiguration])
647
648        configurationRoot = GroupConfiguration()
649
650        configurationRoot.addChildren(configurations)
651
652        return configurationRoot
653
654    # TODO: don't do that in the parser, let the PFW tell you that
655    def checkConfigurableElementUnicity(self):
656        """ check that all configurable elements defined in child configuration are the sames"""
657
658        # get a list. Each elements of is the configurable element list of a configuration
659        configurableElementsList = self.getRootConfiguration().getConfigurableElements()
660
661        # if at least two configurations in the domain
662        if len(configurableElementsList) > 1:
663
664            # get first configuration configurable element list sort
665            configurableElementsList0 = list(configurableElementsList[0])
666            configurableElementsList0.sort()
667
668            for configurableElements in configurableElementsList:
669                # sort current configurable element list
670                auxConfigurableElements = list(configurableElements)
671                auxConfigurableElements.sort()
672
673                if auxConfigurableElements != configurableElementsList0:
674                    # if different, 2 configurations those not have the same configurable element
675                    # list => one or more configurable element is missing in one of the 2
676                    # configuration
677                    raise UndefinedParameter(self.getName())
678
679
680    def translate(self, translator):
681        sequence_aware = self.option.getOption(self.sequenceAwareKeyword)
682        translator.createDomain(self.getName(), sequence_aware)
683
684        configurations = self.getRootConfiguration()
685        configurableElementsList = configurations.getConfigurableElements()
686
687        # add configurable elements
688        if len(configurableElementsList) != 0:
689            for configurableElement in configurableElementsList[0]:
690                translator.addElement(configurableElement)
691
692        configurations.translate(translator)
693
694class GroupDomain(Domain):
695    tag = "groupDomain"
696    match = re.compile(r"(supDomain|domainGroup) *:").match
697    childWhiteList = ["GroupDomain", "Domain", "GroupConfiguration", "Rule", "Operator"]
698
699    def translate(self, translator):
700        for child in self.extractChildrenByClass([Domain, GroupDomain]):
701            child.translate(translator)
702
703# ----------------------------------------------------------
704
705class Root(Element):
706    tag = "root"
707    childWhiteList = ["Domain", "GroupDomain"]
708
709
710# ===========================================
711""" Syntax error Exceptions"""
712# ===========================================
713
714class MySyntaxProblems(SyntaxError):
715    comment = "syntax error in %(line)s "
716
717    def __init__(self, line=None, num=None):
718        self.setLine(line, num)
719
720    def __str__(self):
721
722        if self.line:
723            self.comment = self.comment % {"line" : repr(self.line)}
724        if self.num:
725            self.comment = "Line " + str(self.num) + ", " + self.comment
726        return self.comment
727
728    def setLine(self, line, num):
729        self.line = str(line)
730        self.num = num
731
732
733# ---------------------------------------------------------
734
735class MyPropagationError(MySyntaxProblems):
736    """ Syntax error Exceptions used in the propagation step"""
737    pass
738
739class UndefinedParameter(MyPropagationError):
740    comment = "Configurations in domain '%(domainName)s' do not all set the same parameters "
741    def __init__(self, domainName):
742        self.domainName = domainName
743    def __str__(self):
744        return self.comment % {"domainName" : self.domainName}
745
746
747# -----------------------------------------------------
748""" Syntax error Exceptions used by parser"""
749
750class MySyntaxError(MySyntaxProblems):
751    """ Syntax error Exceptions used by parser"""
752    pass
753
754class MySyntaxWarning(MySyntaxProblems):
755    """ Syntax warning Exceptions used by parser"""
756    pass
757
758class IndentationSyntaxError(MySyntaxError):
759    comment = """syntax error in %(line)s has no father element.
760    You can only increment indentation by one tabutation per line")"""
761
762class EmptyLineWarning(MySyntaxWarning):
763    comment = "warning : %(line)s is an empty line and has been ommited"
764
765class CommentWarning(MySyntaxWarning):
766    comment = "warning : %(line)s is a commentary and has been ommited"
767
768class ChildNotPermitedError(MySyntaxError):
769    def __init__(self, line, fatherElement, childElement):
770        self.comment = "syntax error in %(line)s, " + fatherElement.tag + " should not have a " \
771            + childElement.tag + " child."
772        super(ChildNotPermitedError, self).__init__(line)
773
774
775class UnknownElementTypeError(MySyntaxError):
776    comment = " error in line %(line)s , not known element type were matched "
777
778class SpaceInIndentationError(MySyntaxError):
779    comment = " error in ,%(line)s space is not permited in indentation"
780
781
782# ============================================
783"""Class creating the DOM elements from a stream"""
784# ============================================
785
786class ElementsFactory(object):
787    """Element factory, return an instance of the first matching element
788
789    Test each element list in elementClass and instanciate it if it's methode match returns True
790    The method match is called with input line as argument
791    """
792    def __init__(self):
793        self.elementClass = [
794            EmptyLine,
795            Commentary,
796            GroupDomain,
797            Domain,
798            Path,
799            GroupConfiguration,
800            Configuration,
801            Operator,
802            Rule,
803            GroupPath
804        ]
805
806    def createElementFromLine(self, line):
807        """return an instance of the first matching element
808
809        Test each element list in elementClass and instanciate it if it's methode match returns True
810        The method match is called with the argument line.
811        Raise UnknownElementTypeError if no element matched.
812        """
813        for element in self.elementClass:
814            if element.match(line):
815                # print (line + element.__class__.__name__)
816                return element(line)
817        # if we have not find any
818        raise UnknownElementTypeError(line)
819
820#------------------------------------------------------
821
822class Parser(object):
823    """Class implementing the parser"""
824    def __init__(self):
825        self.rankPattern = re.compile(r"^([\t ]*)(.*)")
826        self.elementFactory = ElementsFactory()
827        self.previousRank = 0
828
829    def __parseLine__(self, line):
830
831        rank, rest = self.__getRank__(line)
832
833        # instanciate the coresponding element
834        element = self.elementFactory.createElementFromLine(rest)
835
836        self.__checkIndentation__(rank)
837
838        return rank, element
839
840    def __getRank__(self, line):
841        """return the rank, the name and the option of the input line
842
843the rank is the number of tabulation (\t) at the line beginning.
844the rest is the rest of the line."""
845        # split line in rank and rest
846        rank = self.rankPattern.match(line)
847        if rank:
848            rank, rest = rank.group(1, 2)
849        else:
850            raise MySyntaxError(line)
851
852        # check for empty line
853        if rest == "":
854            raise EmptyLineWarning(line)
855
856        # check for space in indentation
857        if rank.find(" ") > -1:
858            raise SpaceInIndentationError(line)
859
860        rank = len(rank) + 1  # rank starts at 1
861
862
863        return rank, rest
864
865
866    def __checkIndentation__(self, rank):
867        """check if indentation > previous indentation + 1. If so, raise IndentationSyntaxError"""
868        if rank > self.previousRank + 1:
869            raise IndentationSyntaxError()
870        self.previousRank = rank
871
872    def parse(self, stream, verbose=False):
873        """parse a stream, usually a opened file"""
874        myroot = Root("root")
875        context = [myroot]  # root is element of rank 0
876
877        for num, line in enumerate(stream):
878            try:
879                rank, myelement = self.__parseLine__(line)
880
881                while len(context) > rank:
882                    context.pop()
883                context.append(myelement)
884                context[-2].addChild(myelement)
885
886            except MySyntaxWarning as ex:
887                ex.setLine(line, num + 1)
888                if verbose:
889                    sys.stderr.write("{}\n".format(ex))
890
891            except MySyntaxError as  ex:
892                ex.setLine(line, num + 1)
893                raise
894
895        return myroot
896
897