260 lines
7.1 KiB
Python
260 lines
7.1 KiB
Python
"""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()
|