py-cvs-2001_07_13 (Rev 1.3) merge
[python/dscho.git] / Mac / scripts / unweave.py
blobf14287edbda72b44c8e1b02cdf864350938c6b26
1 """An attempt at an unweave script.
2 Jack Jansen, jack@oratrix.com, 13-Dec-00
3 """
4 import re
5 import sys
6 import macfs
7 import os
8 import macostools
10 BEGINDEFINITION=re.compile("^<<(?P<name>.*)>>=\s*")
11 USEDEFINITION=re.compile("^(?P<pre>.*)<<(?P<name>.*)>>(?P<post>[^=].*)")
12 ENDDEFINITION=re.compile("^@")
13 GREMLINS=re.compile("[\xa0\xca]")
15 DEFAULT_CONFIG="""
16 filepatterns = [
17 ("^.*\.cp$", ":unweave-src"),
18 ("^.*\.h$", ":unweave-include"),
20 genlinedirectives = 0
21 gencomments = 1
22 """
24 class Processor:
25 def __init__(self, filename, config={}):
26 self.items = {}
27 self.filename = filename
28 self.fp = open(filename)
29 self.lineno = 0
30 self.resolving = {}
31 self.resolved = {}
32 self.pushback = None
33 # Options
34 if config.has_key("genlinedirectives"):
35 self.genlinedirectives = config["genlinedirectives"]
36 else:
37 self.genlinedirectives = 1
38 if config.has_key("gencomments"):
39 self.gencomments = config["gencomments"]
40 else:
41 self.gencomments = 0
42 if config.has_key("filepatterns"):
43 self.filepatterns = config["filepatterns"]
44 else:
45 self.filepatterns = []
46 self.filepattern_relist = []
47 for pat, dummy in self.filepatterns:
48 self.filepattern_relist.append(re.compile(pat))
50 def _readline(self):
51 """Read a line. Allow for pushback"""
52 if self.pushback:
53 rv = self.pushback
54 self.pushback = None
55 return rv
56 self.lineno = self.lineno + 1
57 return self.lineno, self.fp.readline()
59 def _linedirective(self, lineno):
60 """Return a #line cpp directive for this file position"""
61 return '#line %d "%s"\n'%(lineno-3, os.path.split(self.filename)[1])
63 def _readitem(self):
64 """Read the definition of an item. Insert #line where needed. """
65 rv = []
66 while 1:
67 lineno, line = self._readline()
68 if not line:
69 break
70 if ENDDEFINITION.search(line):
71 break
72 if BEGINDEFINITION.match(line):
73 self.pushback = lineno, line
74 break
75 mo = USEDEFINITION.match(line)
76 if mo:
77 pre = mo.group('pre')
78 if pre:
79 ## rv.append((lineno, pre+'\n'))
80 rv.append((lineno, pre))
81 rv.append((lineno, line))
82 if mo:
83 post = mo.group('post')
84 if post and post != '\n':
85 rv.append((lineno, post))
86 return rv
88 def _define(self, name, value):
89 """Define an item, or append to an existing definition"""
90 if self.items.has_key(name):
91 self.items[name] = self.items[name] + value
92 else:
93 self.items[name] = value
95 def read(self):
96 """Read the source file and store all definitions"""
97 savedcomment = []
98 while 1:
99 lineno, line = self._readline()
100 if not line: break
101 mo = BEGINDEFINITION.search(line)
102 if mo:
103 name = mo.group('name')
104 value = self._readitem()
105 if self.gencomments:
106 defline = [(lineno, '// <%s>=\n'%name)]
107 if savedcomment:
108 savedcomment = savedcomment + [(lineno, '//\n')] + defline
109 else:
110 savedcomment = defline
111 savedcomment = self._processcomment(savedcomment)
112 value = savedcomment + value
113 savedcomment = []
114 isfilepattern = 0
115 for rexp in self.filepattern_relist:
116 if rexp.search(name):
117 isfilepattern = 1
118 break
119 if 0 and not isfilepattern:
120 value = self._addspace(value)
121 self._define(name, value)
122 else:
123 if self.gencomments:
124 # It seems initial blank lines are ignored:-(
125 if savedcomment or line.strip():
126 savedcomment.append((lineno, '// '+line))
128 def _processcomment(self, comment):
129 # This routine mimicks some artefact of Matthias' code.
130 rv = []
131 for lineno, line in comment:
132 line = line[:-1]
133 line = GREMLINS.subn(' ', line)[0]
134 if len(line) < 75:
135 line = line + (75-len(line))*' '
136 line = line + '\n'
137 rv.append((lineno, line))
138 return rv
140 def _addspace(self, value, howmany):
141 # Yet another routine to mimick yet another artefact
142 rv = value[0:1]
143 for lineno, line in value[1:]:
144 rv.append((lineno, (' '*howmany)+line))
145 return rv
147 def resolve(self):
148 """Resolve all references"""
149 for name in self.items.keys():
150 self._resolve_one(name)
152 def _resolve_one(self, name):
153 """Resolve references in one definition, recursively"""
154 # First check for unknown macros and recursive calls
155 if not self.items.has_key(name):
156 print "Undefined macro:", name
157 return ['<<%s>>'%name]
158 if self.resolving.has_key(name):
159 print "Recursive macro:", name
160 return ['<<%s>>'%name]
161 # Then check that we haven't handled this one before
162 if self.resolved.has_key(name):
163 return self.items[name]
164 # No rest for the wicked: we have work to do.
165 self.resolving[name] = 1
166 result = []
167 lastlineincomplete = 0
168 for lineno, line in self.items[name]:
169 mo = USEDEFINITION.search(line)
170 if mo:
171 # We replace the complete line. Is this correct?
172 macro = mo.group('name')
173 replacement = self._resolve_one(macro)
174 if lastlineincomplete:
175 replacement = self._addspace(replacement, lastlineincomplete)
176 result = result + replacement
177 else:
178 result.append((lineno, line))
179 if line[-1] == '\n':
180 lastlineincomplete = 0
181 else:
182 lastlineincomplete = len(line)
183 self.items[name] = result
184 self.resolved[name] = 1
185 del self.resolving[name]
186 return result
188 def save(self, dir, pattern):
189 """Save macros that match pattern to folder dir"""
190 # Compile the pattern, if needed
191 if type(pattern) == type(''):
192 pattern = re.compile(pattern)
193 # If the directory is relative it is relative to the sourcefile
194 if not os.path.isabs(dir):
195 sourcedir = os.path.split(self.filename)[0]
196 dir = os.path.join(sourcedir, dir)
197 for name in self.items.keys():
198 if pattern.search(name):
199 pathname = os.path.join(dir, name)
200 data = self._addlinedirectives(self.items[name])
201 self._dosave(pathname, data)
203 def _addlinedirectives(self, data):
204 curlineno = -100
205 rv = []
206 for lineno, line in data:
207 curlineno = curlineno + 1
208 if self.genlinedirectives and line and line != '\n' and lineno != curlineno:
209 rv.append(self._linedirective(lineno))
210 curlineno = lineno
211 rv.append(line)
212 return rv
214 def _dosave(self, pathname, data):
215 """Save data to pathname, unless it is identical to what is there"""
216 if os.path.exists(pathname):
217 olddata = open(pathname).readlines()
218 if olddata == data:
219 return
220 macostools.mkdirs(os.path.split(pathname)[0])
221 fp = open(pathname, "w").writelines(data)
223 def process(file, config):
224 pr = Processor(file, config)
225 pr.read()
226 pr.resolve()
227 for pattern, folder in config['filepatterns']:
228 pr.save(folder, pattern)
230 def readconfig():
231 """Read a configuration file, if it doesn't exist create it."""
232 configname = sys.argv[0] + '.config'
233 if not os.path.exists(configname):
234 confstr = DEFAULT_CONFIG
235 open(configname, "w").write(confstr)
236 print "Created config file", configname
237 ## print "Please check and adapt (if needed)"
238 ## sys.exit(0)
239 namespace = {}
240 execfile(configname, namespace)
241 return namespace
243 def main():
244 config = readconfig()
245 if len(sys.argv) > 1:
246 for file in sys.argv[1:]:
247 if file[-3:] == '.nw':
248 print "Processing", file
249 process(file, config)
250 else:
251 print "Skipping", file
252 else:
253 fss, ok = macfs.PromptGetFile("Select .nw source file", "TEXT")
254 if not ok:
255 sys.exit(0)
256 process(fss.as_pathname(), config)
258 if __name__ == "__main__":
259 main()