#!/usr/bin/env python
+"""
+This script has two main functions:
+
+ 1) Combine several snippets to form Makefile.rules file
+ 2) Split Makefile.rules file to the originnal snippets
+
+Snippet syntax:
+
+ snippet ::= legal? documentation rules
+
+ legal ::= comment* empty-comment empty-comment
+ documentation := comment*
+ rules ::= text
+
+ comment ::= '#' text '\n'
+ empty-comment ::= '#' '\n'
+ text ::= [^#] ... '\n'
+
+ Makefile.rules policies:
+
+ * All the parts of the Makefile.rules are ordered in the same order
+ 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 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 re
-class Snippet:
+rulesDir = "rules"
+snippetsDir = "snippets"
- def __init__(self, fname):
- self.name = ""
- self.legal = []
- self.doc = []
- self.code = []
- self.read(fname)
+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"
+ s += " +"+other[i].rstrip() + "\n"
+ break
+ return s
+
+ def __str__(self):
+ s = ''
+ for l in self: s += l
+ return s
- def read(self, fname):
+ def loadFromFile(self, fname):
+ """Loads itself from file."""
+ 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, name = ""):
+ """Initializes the snippet and if fname is given, reads it
+ from file"""
+ self.name = name
+ self.legal = LineList()
+ self.doc = LineList()
+ self.code = LineList()
+ if fname: self.loadFromFile(fname)
+
+ def loadFromFile(self, fname):
+ """Loads snippet from file."""
self.name = fname
f = open(fname, "r")
- current = self.legal
+
+ self.readLines(f.readlines())
+
+ 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
counter = 0
- for line in f:
- if current == self.legal:
+
+ for line in lines:
+ if currentPart == self.legal:
if line.strip() == "#": counter += 1
else: counter = 0
- if counter == 2: current = self.doc
- if line[0] != "#": current = self.code
-
- current.append(line)
+ 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 = []
-
- f.close
+ self.legal = LineList()
+
+ def asLinesList(self):
+ lines = LineList()
+ for type in ['legal', 'doc', 'code']:
+ for line in self.__dict__[type]:
+ lines.append(line)
+ return lines
def __str__(self):
- s = "Snippet: %s\n" % self.name
- s += " Legal: %d lines\n" % len(self.legal)
- s += " Doc: %d lines\n" % len(self.doc)
- s += " Code: %d lines\n" % len(self.code)
+ return str(self.asLinesList())
+
+ def __repr__(self):
+ s = "<Snippet: %s>" % self.name
return s
+ def __eq__(self, other):
+ return \
+ self.name == other.name and \
+ self.legal == other.legal and \
+ self.doc == other.doc and \
+ self.code == other.code
+
+ def __ne__(self, other):
+ return not self == other
+
+ def __getitem__(self, key):
+ return {
+ 'legal': self.legal,
+ 'doc' : self.doc,
+ 'code' : self.code
+ }[key]
+
+ def getDiff(self, other):
+ return self.asLinesList().getDiff(other.asLinesList())
+
+class Snippets:
+ """Collection of snippets, where snippets can be accessed
+ individually by name (like dictionary) or sequentionaly in the
+ order they were added."""
+ def __init__(self):
+ self._snippets = dict()
+ self._order = list()
+
+ def __iadd__(self, snippet):
+ assert isinstance(snippet, Snippet)
+ self._snippets[snippet.name] = snippet
+ self._order += [snippet]
+ return self
+
+ def __getitem__(self, key):
+ return self._snippets[key]
+
+ def __contains__(self, item):
+ return item in self._snippets
+
+ def __iter__(self):
+ return iter(self._order)
+
+ def __eq__(self, other):
+ return self._snippets == other._snippets
+
+ def __ne__(self, other):
+ return not self == other
+
+ def loadFromFiles(self, fnames):
+ """Reads the snippets from several files and adds them to itself."""
+ for fn in fnames:
+ self += Snippet(fn)
+
+ def loadFromDict(self, snipDict):
+ """Adds snippets to itself from dictionary of LineLists."""
+ for s in snipDict:
+ snip = Snippet()
+ snip.name = s
+ snip.readLines(snipDict[s])
+ self += snip
+
+ def getDiff(self, other):
+ 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 _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 = line.rstrip().ljust(80)+" #OMK:%s@%s\n"%(filename,baseFileName)
+ elif addMarker==2:
+ line = line.rstrip().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()
+
+ 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 = lines[0].rstrip()
+ 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()
+ snipDict = self._getSnipDicts()
+ self.snippets.loadFromDict(snipDict)
+
+ def _getSnipDicts(self):
+ """Split self.rules to the original snippets, which are
+ returened as dictionary of LineLists."""
+ snipBegin = re.compile("^(.*)#OMK:([^@]*)(?:@(.*))?\n$")
+ snipDict = dict()
+ currentLinesList = None
+
+ for line in self.rules:
+ match = snipBegin.match(line)
+ 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]
+
+ if currentLinesList != None:
+ currentLinesList.append(line);
+
+ return snipDict
+
def parseCommandLine():
parser = OptionParser(usage = """
- %prog [-o FILE] snippet1 snippet2 ... build Makefile.rules from snippets
- %prog --split Makfile.rules
+ %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",
action="store", dest="split", default=False, metavar="RULES",
help="Split given Makefile.rules to the original snippets")
parser.add_option("-o", "--output",
action="store", dest="output", default=False, metavar="RULES",
- help="Split given Makefile.rules to the original snippets")
+ 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 splitRules(rules):
- pass
+def buildRules(topLevelSnippet, output):
+ rules = MakefileRules()
+ rules.combineFrom(topLevelSnippet)
-def buildRules(snippets, output):
+ rulesCheck = MakefileRules()
+ rulesCheck.rules = rules.rules
+ rulesCheck.split()
+
+ if rules.snippets != rulesCheck.snippets:
+ sys.stderr.write("Consistency error:\n")
+ diff = rules.snippets.getDiff(rulesCheck.snippets)
+ sys.stderr.write(diff)
+ sys.exit(1)
+
if output:
- f = open(output, "w+")
- else:
- f = sys.stdout
+ try: os.makedirs(os.path.dirname(output))
+ except: pass
+ f = open(output,"w+")
+ else: f = sys.stdout
+ f.writelines(rules.rules)
+ f.close()
- parts = []
+def splitRules(rulesFN, output):
+ rules = MakefileRules()
+ rules.rules.loadFromFile(rulesFN)
+ rules.split()
+
+ rulesCheck = MakefileRules()
+ rulesCheck.snippets = rules.snippets
+
+ 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)
- for snip in snippets:
- parts.append(Snippet(snip))
+ rulesCheck.combineFrom(topLevelSnippet, onlyCheck=True)
- for type in ['legal', 'doc', 'code']:
- for snip in parts:
- f.writelines(snip.__dict__[type])
- f.close()
+ # 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)
+# sys.stderr.write(diff)
+# sys.exit(1)
+
+ for snip in rules.snippets:
+ 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)
+ splitRules(options.split, options.output)
else:
- buildRules(args, options.output)
+ buildRules(args[0], options.output)
-if __name__ == "__main__": main()
+if __name__ == "__main__": main()