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