]> rtime.felk.cvut.cz Git - omk.git/blob - omkbuild.py
Update wvtool
[omk.git] / omkbuild.py
1 #!/usr/bin/env python
2 """
3 This script has two main functions:
4
5   1) Combine several snippets to form Makefile.rules file
6   2) Split Makefile.rules file to the originnal snippets
7
8 Snippet syntax:
9
10   snippet ::= legal? documentation rules
11   
12   legal ::= comment* empty-comment empty-comment
13   documentation := comment*
14   rules ::= text
15   
16   comment ::= '#' text '\n'
17   empty-comment ::= '#' '\n'
18   text ::= [^#] ... '\n'
19
20  Makefile.rules policies:
21
22    * All the parts of the Makefile.rules are ordered in the same order
23      as they are in snippets i.e. copyrights, documentations and rules.
24
25    * On the first line of each part of the Makefile.rules, there is
26      special mark of the form:
27
28        #OMK:<snippet file name>[@<included from snippet>]<EOL>
29
30      This mark is used for splitting modified Makefile.rules back to
31      the original snippets. If <snippet file name> starts with __, it
32      is ignored during splitting.
33
34    * Toplevel snippet has name in the forb Makefile.rules.* and no
35      other (included) snippet has such name.
36 """
37
38 from optparse import OptionParser
39 import os
40 import os.path
41 import sys
42 import re
43
44 rulesDir = "rules"
45 snippetsDir = "snippets"
46
47 class LineList(list):
48     """List of text lines"""
49     def getDiff(self, other):
50         s = ''
51         for i in range(len(self)):
52             if i >= len(other):
53                 s += ("  Line %d differs!\n" % i)
54                 s += "  -"+self[i].rstrip() + "\n"
55                 s += "  +\n"
56                 break
57             if self[i] != other[i]:
58                 s += ("  Line %d differs!\n" % i)
59                 s += "  -"+self[i].rstrip() + "\n"
60                 s += "  +"+other[i].rstrip() + "\n"
61                 break
62         return s
63     
64     def __str__(self):
65         s = ''
66         for l in self: s += l
67         return s
68
69     def loadFromFile(self, fname):
70         """Loads itself from file."""
71         try:
72             f = open(fname, "r")
73         except IOError:
74             sys.stderr.write("Cannot open %s\n" % fname)
75             sys.exit(1)
76             
77         self.extend(f.readlines())
78         f.close
79
80 class Snippet:
81     def __init__(self, fname = None, name = ""):
82         """Initializes the snippet and if fname is given, reads it
83         from file"""
84         self.name = name
85         self.legal = LineList()
86         self.doc = LineList()
87         self.code = LineList()
88         if fname: self.loadFromFile(fname)
89         
90     def loadFromFile(self, fname):
91         """Loads snippet from file."""
92         self.name = fname
93         f = open(fname, "r")
94         
95         self.readLines(f.readlines())
96          
97         f.close
98
99     def addCodeLine(self, line):
100         self.code.append(line)
101
102     def readLines(self, lines):
103         """Parses the snippet given as a list and stores it in itself."""
104         currentPart = self.legal
105         counter = 0
106
107         for line in lines:
108             if currentPart == self.legal:
109                 if line.strip() == "#": counter += 1
110                 else: counter = 0
111             if line[0] != "#": currentPart = self.code
112
113             currentPart.append(line)
114
115             if counter == 2:
116                 currentPart = self.doc
117                 counter = 0
118
119
120         if not self.doc:
121             self.doc = self.legal
122             self.legal = LineList()
123
124     def asLinesList(self):
125         lines = LineList()
126         for type in ['legal', 'doc', 'code']:
127             for line in self.__dict__[type]:
128                 lines.append(line)
129         return lines
130
131     def __str__(self):
132         return str(self.asLinesList())
133
134     def __repr__(self):
135         s = "<Snippet: %s>" % self.name
136         return s
137
138     def __eq__(self, other):
139         return \
140             self.name == other.name and \
141             self.legal == other.legal and \
142             self.doc == other.doc and \
143             self.code == other.code
144
145     def __ne__(self, other):
146         return not self == other
147
148     def __getitem__(self, key):
149         return {
150             'legal': self.legal,
151             'doc'  : self.doc,
152             'code' : self.code
153             }[key]
154
155     def getDiff(self, other):
156         return self.asLinesList().getDiff(other.asLinesList())
157
158 class Snippets:
159     """Collection of snippets, where snippets can be accessed
160     individually by name (like dictionary) or sequentionaly in the
161     order they were added."""
162     def __init__(self):
163         self._snippets = dict()
164         self._order = list()
165
166     def __iadd__(self, snippet):
167         assert isinstance(snippet, Snippet)
168         self._snippets[snippet.name] = snippet
169         self._order += [snippet]
170         return self
171
172     def __getitem__(self, key):
173         return self._snippets[key]
174
175     def __contains__(self, item):
176         return item in self._snippets
177
178     def __iter__(self):
179         return iter(self._order)
180
181     def __eq__(self, other):
182         return self._snippets == other._snippets
183
184     def __ne__(self, other):
185         return not self == other
186
187     def loadFromFiles(self, fnames):
188         """Reads the snippets from several files and adds them to itself."""
189         for fn in fnames:
190             self += Snippet(fn)
191         
192     def loadFromDict(self, snipDict):
193         """Adds snippets to itself from dictionary of LineLists."""
194         for s in snipDict:
195             snip = Snippet()
196             snip.name = s
197             snip.readLines(snipDict[s])
198             self += snip
199
200     def getDiff(self, other):
201         assert isinstance(other, Snippets)
202         s = ''
203         for snip in self:
204             if (snip.name[0:2] == '__'): continue
205             if (snip != other[snip.name]):
206                 s += "Snippet %s:\n" % snip.name
207                 s += snip.getDiff(other[snip.name])
208         return s
209
210 # Include directoves matching this r.e. will be replaced by this script
211 reInclude = re.compile("^include ([^ ]*) #omkbuild")
212
213 class MakefileRules(LineList):
214     def __init__(self):
215         self.snippets = Snippets()
216         self.rules = LineList()
217
218     def _includeSnippets(self, filename, baseFileName="", onlyLoadSnippets=False):
219         """Recursively traverses snippets according to include
220         directives. If onlyLoadSnippets is True, self.rules is not
221         modified ..."""
222
223         if onlyLoadSnippets:
224             if filename in self.snippets:
225                 sys.stderr.write("Error: Snippet included more than once\n")
226                 # This is not allowed becouse it would cause problems
227                 # during spliting
228                 sys.exit(1)
229             self.snippets += Snippet(filename)
230
231         lines = self.snippets[filename]['code']
232
233         addMarker = 1 # The first line of the snippet should be marked
234         for line in lines:
235             match = reInclude.match(line)
236             if match:
237                 # Include other snippet
238                 self._includeSnippets(match.group(1).strip(),\
239                                       filename,
240                                       onlyLoadSnippets)
241                 addMarker = 2 # The next line after include should be marked
242             else:
243                 # Add this line to rules
244                 if addMarker:
245                     if addMarker==1:
246                         line = line.rstrip().ljust(80)+" #OMK:%s@%s\n"%(filename,baseFileName)
247                     elif addMarker==2:
248                         line = line.rstrip().ljust(80)+" #OMK:%s\n"%(filename)
249                     addMarker = 0
250                 if not onlyLoadSnippets:
251                     self.rules += [line]
252
253     def combineFrom(self, topLevelSnippet, onlyCheck=False):
254         """Produces self.rules from the topLevelSnippet and all
255         snippets included directly or indirectly from it."""
256         self.rules = LineList()
257
258         if not onlyCheck:
259             self._includeSnippets(topLevelSnippet, onlyLoadSnippets=True)
260
261         # Append legal and doc parts
262         for type in ['legal', 'doc']:
263             for snip in self.snippets:
264                 lines = snip[type]
265                 if len(lines) == 0: continue
266                 firstLine = lines[0].rstrip()
267                 self.rules += [firstLine.ljust(80)+" #OMK:%s\n"%snip.name]
268                 self.rules += lines[1:]
269                 #self.rules += ['a'] # test error
270
271         # Append code parts
272         self._includeSnippets(topLevelSnippet)
273
274
275     def split(self):
276         """Split self.rules to the original snippets in self.snippets."""
277         self.snippets = Snippets()
278         snipDict = self._getSnipDicts()
279         self.snippets.loadFromDict(snipDict)
280
281     def _getSnipDicts(self):
282         """Split self.rules to the original snippets, which are
283         returened as dictionary of LineLists."""
284         snipBegin = re.compile("^(.*)#OMK:([^@]*)(?:@(.*))?\n$")
285         snipDict = dict()
286         currentLinesList = None
287
288         for line in self.rules:
289             match = snipBegin.match(line)
290             if match:
291                 line = match.group(1).rstrip() + "\n"
292                 snipName = match.group(2)
293                 includedFrom = match.group(3)
294                 if includedFrom:
295                     if not includedFrom in snipDict: snipDict[includedFrom] = LineList()
296                     snipDict[includedFrom].append("include %s #omkbuild\n" % snipName);
297
298                 if not snipName in snipDict:
299                     snipDict[snipName] = LineList()
300                 currentLinesList = snipDict[snipName]
301             
302             if currentLinesList != None:
303                 currentLinesList.append(line);
304                 
305         return snipDict
306
307
308 def parseCommandLine():
309     parser = OptionParser(usage = """
310     %prog [-o FILE] top-level-snippet      build Makefile.rules from the top-level-snippet and included ones
311     %prog [-o - ] -s Makfile.rules
312     """)
313     parser.add_option("-s", "--split",
314                       action="store", dest="split", default=False, metavar="RULES",
315                       help="Split given Makefile.rules to the original snippets")
316     parser.add_option("-o", "--output",
317                       action="store", dest="output", default=False, metavar="RULES",
318                       help="Write Makefile.rules to file RULES")
319     (options, args) = parser.parse_args()
320     if len(args) > 1:
321         parser.print_help()
322         sys.exit(1)
323     return options, args
324
325 def buildRules(topLevelSnippet, output):
326     rules = MakefileRules()
327     rules.combineFrom(topLevelSnippet)
328
329     rulesCheck = MakefileRules()
330     rulesCheck.rules = rules.rules
331     rulesCheck.split()
332
333     if rules.snippets != rulesCheck.snippets:
334         sys.stderr.write("Consistency error:\n")
335         diff = rules.snippets.getDiff(rulesCheck.snippets)
336         sys.stderr.write(diff)
337         sys.exit(1)
338         
339     if output:
340         try: os.makedirs(os.path.dirname(output))
341         except: pass
342         f = open(output,"w+")
343     else: f = sys.stdout
344     f.writelines(rules.rules)
345     f.close()
346
347 def splitRules(rulesFN, output):
348     rules = MakefileRules()
349     rules.rules.loadFromFile(rulesFN)
350     rules.split()
351
352     rulesCheck = MakefileRules()
353     rulesCheck.snippets = rules.snippets
354
355     topLevelSnippet = None
356     for snip in rules.snippets:
357         if snip.name.startswith("Makefile.rules."):
358             topLevelSnippet = snip.name
359     if not topLevelSnippet:
360         sys.stderr.write("No toplevel snippet (Makefile.rules.*) found\n")
361         sys.exit(1)
362         
363     rulesCheck.combineFrom(topLevelSnippet, onlyCheck=True)
364
365     # The comparsion is not that simple. The order of rules might be
366     # different. FIXME: Is this still true?
367 #     if rules.rules != rulesCheck.rules:
368 #         sys.stderr.write("Consistency error:\n")
369 #         diff = rules.rules.getDiff(rulesCheck.rules)
370 #         sys.stderr.write(diff)
371 #         sys.exit(1)
372
373     for snip in rules.snippets:
374         if snip.name[0:2] == "__":
375             continue
376         print(snip.name)
377         f = None
378         if output == "-": f = sys.stdout
379         else: f = open(snip.name, "w+")
380         f.writelines(snip.asLinesList())
381         f.close()
382
383 def main():
384     (options, args) = parseCommandLine()
385     if options.split:
386         splitRules(options.split, options.output)
387     else:
388         buildRules(args[0], options.output)
389
390
391 if __name__ == "__main__": main()