]> rtime.felk.cvut.cz Git - omk.git/blobdiff - omkbuild.py
omkbuild.py: Makefile.rules are constructed by replacing include directives in snippets
[omk.git] / omkbuild.py
index aab40c8d91f245b4856179d32704cb3fdf01bf85..68e555eb32d256afcc9f31f15cdec650113f2b93 100755 (executable)
@@ -13,9 +13,9 @@ Snippet syntax:
   documentation := comment*
   rules ::= text
   
-  comment ::= '#' text
-  empty-comment ::= '#'
-  text ::= [^#] ...
+  comment ::= '#' text '\n'
+  empty-comment ::= '#' '\n'
+  text ::= [^#] ... '\n'
 
  Makefile.rules policies:
 
@@ -23,23 +23,38 @@ Snippet syntax:
      as they are in snippets i.e. copyrights, documentations and rules.
 
    * On the first line of each part of the Makefile.rules, there is
-     special mart of the form #OMK@<snippet file name><EOL>. This mark
-     is used for splitting Makefile.rules back to the original
-     snippets.
+     special mark of the form:
 
+       #OMK:<snippet file name>[@<included from snippet>]<EOL>
+
+     This mark is used for splitting modified Makefile.rules back to
+     the original snippets. If <snippet file name> starts with __, it
+     is ignored during splitting.
+
+   * Toplevel snippet has name in the forb Makefile.rules.* and no
+     other (included) snippet has such name.
 """
 
 from optparse import OptionParser
 import os
+import os.path
 import sys
 import string
 import re
 
+rulesDir = "rules"
+snippetsDir = "snippets"
+
 class LineList(list):
     """List of text lines"""
     def getDiff(self, other):
         s = ''
         for i in range(len(self)):
+            if i >= len(other):
+                s += ("  Line %d differs!\n" % i)
+                s += "  -"+self[i].rstrip() + "\n"
+                s += "  +\n"
+                break
             if self[i] != other[i]:
                 s += ("  Line %d differs!\n" % i)
                 s += "  -"+self[i].rstrip() + "\n"
@@ -54,15 +69,20 @@ class LineList(list):
 
     def loadFromFile(self, fname):
         """Loads itself from file."""
-        f = open(fname, "r")
+        try:
+            f = open(fname, "r")
+        except IOError:
+            sys.stderr.write("Cannot open %s\n" % fname)
+            sys.exit(1)
+            
         self.extend(f.readlines())
         f.close
 
 class Snippet:
-    def __init__(self, fname = None):
+    def __init__(self, fname = None, name = ""):
         """Initializes the snippet and if fname is given, reads it
         from file"""
-        self.name = ""
+        self.name = name
         self.legal = LineList()
         self.doc = LineList()
         self.code = LineList()
@@ -77,6 +97,9 @@ class Snippet:
          
         f.close
 
+    def addCodeLine(self, line):
+        self.code.append(line)
+
     def readLines(self, lines):
         """Parses the snippet given as a list and stores it in itself."""
         currentPart = self.legal
@@ -86,11 +109,15 @@ class Snippet:
             if currentPart == self.legal:
                 if line.strip() == "#": counter += 1
                 else: counter = 0
-                if counter == 2: currentPart = self.doc
             if line[0] != "#": currentPart = self.code
 
             currentPart.append(line)
 
+            if counter == 2:
+                currentPart = self.doc
+                counter = 0
+
+
         if not self.doc:
             self.doc = self.legal
             self.legal = LineList()
@@ -172,29 +199,77 @@ class Snippets:
         assert isinstance(other, Snippets)
         s = ''
         for snip in self:
+            if (snip.name[0:2] == '__'): continue
             if (snip != other[snip.name]):
                 s += "Snippet %s:\n" % snip.name
                 s += snip.getDiff(other[snip.name])
         return s
 
+# Include directoves matching this r.e. will be replaced by this script
+reInclude = re.compile("^include ([^ ]*) #omkbuild")
+
 class MakefileRules(LineList):
     def __init__(self):
         self.snippets = Snippets()
         self.rules = LineList()
 
-    def combine(self):
-        """Combine self.snippets and produces self.rules"""
+    def _includeSnippets(self, filename, baseFileName="", onlyLoadSnippets=False):
+        """Recursively traverses snippets according to include
+        directives. If onlyLoadSnippets is True, self.rules is not
+        modified ..."""
+
+        if onlyLoadSnippets:
+            if filename in self.snippets:
+                sys.stderr.write("Error: Snippet included more than once\n")
+                # This is not allowed becouse it would cause problems
+                # during spliting
+                sys.exit(1)
+            self.snippets += Snippet(filename)
+
+        lines = self.snippets[filename]['code']
+
+        addMarker = 1 # The first line of the snippet should be marked
+        for line in lines:
+            match = reInclude.match(line)
+            if match:
+                # Include other snippet
+                self._includeSnippets(match.group(1).strip(),\
+                                      filename,
+                                      onlyLoadSnippets)
+                addMarker = 2 # The next line after include should be marked
+            else:
+                # Add this line to rules
+                if addMarker:
+                    if addMarker==1:
+                        line = string.rstrip(line).ljust(80)+" #OMK:%s@%s\n"%(filename,baseFileName)
+                    elif addMarker==2:
+                        line = string.rstrip(line).ljust(80)+" #OMK:%s\n"%(filename)
+                    addMarker = 0
+                if not onlyLoadSnippets:
+                    self.rules += [line]
+
+    def combineFrom(self, topLevelSnippet, onlyCheck=False):
+        """Produces self.rules from the topLevelSnippet and all
+        snippets included directly or indirectly from it."""
         self.rules = LineList()
 
-        for type in ['legal', 'doc', 'code']:
+        if not onlyCheck:
+            self._includeSnippets(topLevelSnippet, onlyLoadSnippets=True)
+
+        # Append legal and doc parts
+        for type in ['legal', 'doc']:
             for snip in self.snippets:
                 lines = snip[type]
                 if len(lines) == 0: continue
                 firstLine = string.rstrip(lines[0])
-                self.rules += [firstLine.ljust(60)+" #OMK@%s\n"%snip.name]
+                self.rules += [firstLine.ljust(80)+" #OMK:%s\n"%snip.name]
                 self.rules += lines[1:]
                 #self.rules += ['a'] # test error
 
+        # Append code parts
+        self._includeSnippets(topLevelSnippet)
+
+
     def split(self):
         """Split self.rules to the original snippets in self.snippets."""
         self.snippets = Snippets()
@@ -204,7 +279,7 @@ class MakefileRules(LineList):
     def _getSnipDicts(self):
         """Split self.rules to the original snippets, which are
         returened as dictionary of LineLists."""
-        snipBegin = re.compile("^(.*)#OMK@(.*)$")
+        snipBegin = re.compile("^(.*)#OMK:([^@]*)(?:@(.*))?\n$")
         snipDict = dict()
         currentLinesList = None
 
@@ -213,17 +288,24 @@ class MakefileRules(LineList):
             if match:
                 line = match.group(1).rstrip() + "\n"
                 snipName = match.group(2)
+                includedFrom = match.group(3)
+                if includedFrom:
+                    if not includedFrom in snipDict: snipDict[includedFrom] = LineList()
+                    snipDict[includedFrom].append("include %s #omkbuild\n" % snipName);
+
                 if not snipName in snipDict:
                     snipDict[snipName] = LineList()
                 currentLinesList = snipDict[snipName]
-
-            currentLinesList.append(line);
+            
+            if currentLinesList != None:
+                currentLinesList.append(line);
+                
         return snipDict
 
 
 def parseCommandLine():
     parser = OptionParser(usage = """
-    %prog [-o FILE] snippet1 snippet2 ...      build Makefile.rules from snippets
+    %prog [-o FILE] top-level-snippet      build Makefile.rules from the top-level-snippet and included ones
     %prog [-o - ] -s Makfile.rules
     """)
     parser.add_option("-s", "--split",
@@ -233,12 +315,14 @@ def parseCommandLine():
                       action="store", dest="output", default=False, metavar="RULES",
                       help="Write Makefile.rules to file RULES")
     (options, args) = parser.parse_args()
+    if len(args) > 1:
+        parser.print_help()
+        sys.exit(1)
     return options, args
 
-def buildRules(fnames, output):
+def buildRules(topLevelSnippet, output):
     rules = MakefileRules()
-    rules.snippets.loadFromFiles(fnames)
-    rules.combine()
+    rules.combineFrom(topLevelSnippet)
 
     rulesCheck = MakefileRules()
     rulesCheck.rules = rules.rules
@@ -262,9 +346,19 @@ def splitRules(rulesFN, output):
 
     rulesCheck = MakefileRules()
     rulesCheck.snippets = rules.snippets
-    rulesCheck.combine()
 
-    # The order of rules might be different
+    topLevelSnippet = None
+    for snip in rules.snippets:
+        if snip.name.startswith("Makefile.rules."):
+            topLevelSnippet = snip.name
+    if not topLevelSnippet:
+        sys.stderr.write("No toplevel snippet (Makefile.rules.*) found\n")
+        sys.exit(1)
+        
+    rulesCheck.combineFrom(topLevelSnippet, onlyCheck=True)
+
+    # The comparsion is not that simple. The order of rules might be
+    # different. FIXME: Is this still true?
 #     if rules.rules != rulesCheck.rules:
 #         sys.stderr.write("Consistency error:\n")
 #         diff = rules.rules.getDiff(rulesCheck.rules)
@@ -272,19 +366,21 @@ def splitRules(rulesFN, output):
 #         sys.exit(1)
 
     for snip in rules.snippets:
-        print snip.__class__
-#     f = None
-#     if output == "-": f = sys.stdout
-#     f.writelines(rules.rules)
-#     f.close()
-
+        if snip.name[0:2] == "__":
+            continue
+        print snip.name
+        f = None
+        if output == "-": f = sys.stdout
+        else: f = open(snip.name, "w+")
+        f.writelines(snip.asLinesList())
+        f.close()
 
 def main():
     (options, args) = parseCommandLine()
     if options.split:
         splitRules(options.split, options.output)
     else:
-        buildRules(args, options.output)
+        buildRules(args[0], options.output)
 
 
 if __name__ == "__main__": main()