blob: 204c963e2c7348f289a35b9278ed1ff7fe98be36 [file] [log] [blame]
Jack Jansen581fa782000-12-14 22:29:58 +00001"""An attempt at an unweave script.
2Jack Jansen, jack@oratrix.com, 13-Dec-00
3"""
4import re
5import sys
6import macfs
7import os
Jack Jansen61c64c92000-12-14 23:35:01 +00008import macostools
Jack Jansen581fa782000-12-14 22:29:58 +00009
10BEGINDEFINITION=re.compile("^<<(?P<name>.*)>>=\s*")
Jack Jansen61c64c92000-12-14 23:35:01 +000011USEDEFINITION=re.compile("^(?P<pre>.*)<<(?P<name>.*)>>(?P<post>[^=].*)")
Jack Jansen581fa782000-12-14 22:29:58 +000012ENDDEFINITION=re.compile("^@")
13
Jack Jansen0eea1662001-02-13 22:58:56 +000014DEFAULT_CONFIG="""
15config = [
16 ("^.*\.cp$", ":unweave-src"),
17 ("^.*\.h$", ":unweave-include"),
18]
19genlinedirectives = 0
20gencomments = 1
21"""
22
Jack Jansen581fa782000-12-14 22:29:58 +000023class Processor:
Jack Jansen0eea1662001-02-13 22:58:56 +000024 def __init__(self, filename, config={}):
Jack Jansen581fa782000-12-14 22:29:58 +000025 self.items = {}
26 self.filename = filename
27 self.fp = open(filename)
28 self.lineno = 0
29 self.resolving = {}
30 self.resolved = {}
31 self.pushback = None
Jack Jansen0eea1662001-02-13 22:58:56 +000032 # Options
33 if config.has_key("genlinedirectives"):
34 self.genlinedirectives = config["genlinedirectives"]
35 else:
36 self.genlinedirectives = 1
37 if config.has_key("gencomments"):
38 self.gencomments = config["gencomments"]
39 else:
40 self.gencomments = 0
Jack Jansen581fa782000-12-14 22:29:58 +000041
42 def _readline(self):
43 """Read a line. Allow for pushback"""
44 if self.pushback:
45 rv = self.pushback
46 self.pushback = None
47 return rv
48 self.lineno = self.lineno + 1
Jack Jansen61c64c92000-12-14 23:35:01 +000049 return self.lineno, self.fp.readline()
Jack Jansen581fa782000-12-14 22:29:58 +000050
Jack Jansen61c64c92000-12-14 23:35:01 +000051 def _linedirective(self, lineno):
52 """Return a #line cpp directive for this file position"""
53 return '#line %d "%s"\n'%(lineno-3, os.path.split(self.filename)[1])
Jack Jansen581fa782000-12-14 22:29:58 +000054
55 def _readitem(self):
56 """Read the definition of an item. Insert #line where needed. """
Jack Jansen61c64c92000-12-14 23:35:01 +000057 rv = []
Jack Jansen581fa782000-12-14 22:29:58 +000058 while 1:
Jack Jansen61c64c92000-12-14 23:35:01 +000059 lineno, line = self._readline()
Jack Jansen581fa782000-12-14 22:29:58 +000060 if not line:
61 break
62 if ENDDEFINITION.search(line):
63 break
64 if BEGINDEFINITION.match(line):
Jack Jansen61c64c92000-12-14 23:35:01 +000065 self.pushback = lineno, line
Jack Jansen581fa782000-12-14 22:29:58 +000066 break
67 mo = USEDEFINITION.match(line)
68 if mo:
69 pre = mo.group('pre')
70 if pre:
Jack Jansen0eea1662001-02-13 22:58:56 +000071## rv.append((lineno, pre+'\n'))
72 rv.append((lineno, pre))
Jack Jansen61c64c92000-12-14 23:35:01 +000073 rv.append((lineno, line))
Jack Jansen581fa782000-12-14 22:29:58 +000074 if mo:
Jack Jansen61c64c92000-12-14 23:35:01 +000075 post = mo.group('post')
76 if post and post != '\n':
77 rv.append((lineno, post))
Jack Jansen581fa782000-12-14 22:29:58 +000078 return rv
79
80 def _define(self, name, value):
81 """Define an item, or append to an existing definition"""
82 if self.items.has_key(name):
83 self.items[name] = self.items[name] + value
84 else:
85 self.items[name] = value
86
87 def read(self):
88 """Read the source file and store all definitions"""
Jack Jansen0eea1662001-02-13 22:58:56 +000089 savedcomment = []
Jack Jansen581fa782000-12-14 22:29:58 +000090 while 1:
Jack Jansen61c64c92000-12-14 23:35:01 +000091 lineno, line = self._readline()
Jack Jansen581fa782000-12-14 22:29:58 +000092 if not line: break
93 mo = BEGINDEFINITION.search(line)
94 if mo:
95 name = mo.group('name')
96 value = self._readitem()
Jack Jansen0eea1662001-02-13 22:58:56 +000097 if self.gencomments:
98 defline = [(lineno, '// <%s>=\n'%name)]
99 if savedcomment:
100 savedcomment = savedcomment + [(lineno, '//\n')] + defline
101 else:
102 savedcomment = defline
103 savedcomment = self._extendlines(savedcomment)
104 value = savedcomment + value
105 savedcomment = []
Jack Jansen581fa782000-12-14 22:29:58 +0000106 self._define(name, value)
107 else:
Jack Jansen0eea1662001-02-13 22:58:56 +0000108 if self.gencomments:
109 # It seems initial blank lines are ignored:-(
110 if savedcomment or line.strip():
111 savedcomment.append((lineno, '// '+line))
112
113 def _extendlines(self, comment):
114 # This routine mimicks some artefact of Matthias' code.
115 rv = []
116 for lineno, line in comment:
117 line = line[:-1]
118 if len(line) < 75:
119 line = line + (75-len(line))*' '
120 line = line + '\n'
121 rv.append((lineno, line))
122 return rv
123
Jack Jansen581fa782000-12-14 22:29:58 +0000124 def resolve(self):
125 """Resolve all references"""
126 for name in self.items.keys():
127 self._resolve_one(name)
128
129 def _resolve_one(self, name):
130 """Resolve references in one definition, recursively"""
131 # First check for unknown macros and recursive calls
132 if not self.items.has_key(name):
133 print "Undefined macro:", name
134 return ['<<%s>>'%name]
135 if self.resolving.has_key(name):
136 print "Recursive macro:", name
137 return ['<<%s>>'%name]
138 # Then check that we haven't handled this one before
139 if self.resolved.has_key(name):
140 return self.items[name]
141 # No rest for the wicked: we have work to do.
142 self.resolving[name] = 1
143 result = []
Jack Jansen61c64c92000-12-14 23:35:01 +0000144 for lineno, line in self.items[name]:
Jack Jansen581fa782000-12-14 22:29:58 +0000145 mo = USEDEFINITION.search(line)
146 if mo:
147 # We replace the complete line. Is this correct?
148 macro = mo.group('name')
149 replacement = self._resolve_one(macro)
150 result = result + replacement
151 else:
Jack Jansen61c64c92000-12-14 23:35:01 +0000152 result.append((lineno, line))
Jack Jansen581fa782000-12-14 22:29:58 +0000153 self.items[name] = result
154 self.resolved[name] = 1
155 del self.resolving[name]
156 return result
157
158 def save(self, dir, pattern):
159 """Save macros that match pattern to folder dir"""
160 # Compile the pattern, if needed
161 if type(pattern) == type(''):
162 pattern = re.compile(pattern)
163 # If the directory is relative it is relative to the sourcefile
164 if not os.path.isabs(dir):
165 sourcedir = os.path.split(self.filename)[0]
166 dir = os.path.join(sourcedir, dir)
167 for name in self.items.keys():
168 if pattern.search(name):
169 pathname = os.path.join(dir, name)
Jack Jansen61c64c92000-12-14 23:35:01 +0000170 data = self._addlinedirectives(self.items[name])
Jack Jansen581fa782000-12-14 22:29:58 +0000171 self._dosave(pathname, data)
172
Jack Jansen61c64c92000-12-14 23:35:01 +0000173 def _addlinedirectives(self, data):
174 curlineno = -100
175 rv = []
176 for lineno, line in data:
177 curlineno = curlineno + 1
Jack Jansen0eea1662001-02-13 22:58:56 +0000178 if self.genlinedirectives and line and line != '\n' and lineno != curlineno:
Jack Jansen61c64c92000-12-14 23:35:01 +0000179 rv.append(self._linedirective(lineno))
180 curlineno = lineno
181 rv.append(line)
182 return rv
Jack Jansen581fa782000-12-14 22:29:58 +0000183
184 def _dosave(self, pathname, data):
185 """Save data to pathname, unless it is identical to what is there"""
186 if os.path.exists(pathname):
187 olddata = open(pathname).readlines()
188 if olddata == data:
189 return
Jack Jansen61c64c92000-12-14 23:35:01 +0000190 macostools.mkdirs(os.path.split(pathname)[0])
Jack Jansen581fa782000-12-14 22:29:58 +0000191 fp = open(pathname, "w").writelines(data)
192
Jack Jansen61c64c92000-12-14 23:35:01 +0000193def process(file, config):
Jack Jansen0eea1662001-02-13 22:58:56 +0000194 pr = Processor(file, config)
Jack Jansen581fa782000-12-14 22:29:58 +0000195 pr.read()
196 pr.resolve()
Jack Jansen0eea1662001-02-13 22:58:56 +0000197 for pattern, folder in config['config']:
Jack Jansen61c64c92000-12-14 23:35:01 +0000198 pr.save(folder, pattern)
199
200def readconfig():
201 """Read a configuration file, if it doesn't exist create it."""
202 configname = sys.argv[0] + '.config'
203 if not os.path.exists(configname):
Jack Jansen0eea1662001-02-13 22:58:56 +0000204 confstr = DEFAULT_CONFIG
Jack Jansen61c64c92000-12-14 23:35:01 +0000205 open(configname, "w").write(confstr)
206 print "Created config file", configname
207## print "Please check and adapt (if needed)"
208## sys.exit(0)
209 namespace = {}
210 execfile(configname, namespace)
Jack Jansen0eea1662001-02-13 22:58:56 +0000211 return namespace
Jack Jansen581fa782000-12-14 22:29:58 +0000212
213def main():
Jack Jansen61c64c92000-12-14 23:35:01 +0000214 config = readconfig()
Jack Jansen581fa782000-12-14 22:29:58 +0000215 if len(sys.argv) > 1:
Jack Jansen61c64c92000-12-14 23:35:01 +0000216 for file in sys.argv[1:]:
217 if file[-3:] == '.nw':
218 print "Processing", file
219 process(file, config)
220 else:
221 print "Skipping", file
Jack Jansen581fa782000-12-14 22:29:58 +0000222 else:
223 fss, ok = macfs.PromptGetFile("Select .nw source file", "TEXT")
224 if not ok:
225 sys.exit(0)
Jack Jansen0eea1662001-02-13 22:58:56 +0000226 process(fss.as_pathname(), config)
Jack Jansen581fa782000-12-14 22:29:58 +0000227
228if __name__ == "__main__":
229 main()