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