164 lines
6.4 KiB
Python
164 lines
6.4 KiB
Python
"""
|
|
compilerlike -- framework code for building compiler-like programs.
|
|
|
|
There is a common `compiler-like' pattern in Unix scripts which is useful
|
|
for translation utilities of all sorts. A program following this pattern
|
|
behaves as a filter when no argument files are specified on the command
|
|
line, but otherwise transforms each file individually into a corresponding
|
|
output file.
|
|
|
|
This module provides framework and glue code to make such programs
|
|
easy to write. You supply a function to massage the file data. It
|
|
always takes initial name and filename arguments; depending on which
|
|
entry point you use, it can also take input and output file pointers,
|
|
or it can take a string consisting of the entire file's data and
|
|
return a replacement, or it can take in succession strings consisting
|
|
of each of the file's lines and return a translated line for each.
|
|
|
|
The fourth, optional argument of each entry point is a name
|
|
transformation function or name suffix string. If it is of string
|
|
type, the shortest suffix of each filename beginning with the first
|
|
character of the argument string is stripped off. If the first
|
|
character of the argument does not occur in the filename, no suffix is
|
|
removed. Then the name suffix argument is concatenated to the end of
|
|
the stripped filename. (Thus, a name suffix argument of ".x" will
|
|
cause the filenames foo.c and bar.d to be transformed to foo.x and
|
|
bar.x respectively.)
|
|
|
|
Argument files are transformed in left to right order in the argument list.
|
|
A filename consisting of a dash is interpreted as a directive to read from
|
|
standard input (this can be useful in pipelines).
|
|
|
|
Replacement of each file is atomic and doesn't occur until the
|
|
translation of that file has completed. Any tempfiles are removed
|
|
automatically on any exception thrown by the translation function,
|
|
and the exception is then passed upwards.
|
|
"""
|
|
|
|
# Requires Python 2.
|
|
import sys, os, filecmp, traceback
|
|
|
|
def filefilter(name, arguments, trans_data, trans_filename=None):
|
|
"Filter stdin to stdout, or file arguments to renamed files."
|
|
if not arguments:
|
|
trans_data(name, "stdin", sys.stdin, sys.stdout)
|
|
else:
|
|
for file in arguments:
|
|
if file == '-': # - is conventional for stdin
|
|
file = "stdin"
|
|
infp = sys.stdin
|
|
else:
|
|
infp = open(file)
|
|
tempfile = file + ".~%s-%d~" % (name, os.getpid())
|
|
outfp = open(tempfile, "w")
|
|
try:
|
|
trans_data(name, file, infp, outfp)
|
|
except:
|
|
os.remove(tempfile)
|
|
# Pass the exception upwards
|
|
(exc_type, exc_value, exc_traceback) = sys.exc_info()
|
|
raise exc_type, exc_value, exc_traceback
|
|
if filecmp.cmp(file, tempfile):
|
|
os.remove(tempfile)
|
|
else:
|
|
if not trans_filename:
|
|
os.rename(tempfile, file)
|
|
elif type(trans_filename) == type(""):
|
|
i = file.rfind(trans_filename[0])
|
|
if i > -1:
|
|
file = file[:i]
|
|
os.rename(tempfile, stem + trans_filename)
|
|
else:
|
|
os.rename(tempfile, trans_filename(file))
|
|
|
|
def line_by_line(name, file, infp, outfp, translate_line):
|
|
"Hook to do line-by-line translation for filters."
|
|
while 1:
|
|
line = infp.readline()
|
|
if line == "":
|
|
break
|
|
elif line: # None returns are skipped
|
|
outfp.write(translate_line(name, file, line))
|
|
|
|
def linefilter(name, arguments, trans_data, trans_filename=None):
|
|
"Filter framework for line-by-line transformation."
|
|
return filefilter(name,
|
|
arguments,
|
|
lambda name, file, infp, outfp: line_by_line(name, file, infp, outfp, trans_data),
|
|
trans_filename)
|
|
|
|
def sponge(name, arguments, trans_data, trans_filename=None):
|
|
"Read input sources entire and transform them in memory."
|
|
if not arguments:
|
|
sys.stdout.write(trans_data(name, "stdin", sys.stdin.read()))
|
|
else:
|
|
for file in arguments:
|
|
infp = open(file)
|
|
indoc = infp.read()
|
|
infp.close()
|
|
tempfile = file + ".~%s-%d~" % (name, os.getpid())
|
|
try:
|
|
outfp = open(tempfile, "w")
|
|
except OSError:
|
|
sys.stderr.write("%s: can't open tempfile" % name)
|
|
return 1
|
|
try:
|
|
outdoc = trans_data(name, file, indoc)
|
|
except:
|
|
os.remove(tempfile)
|
|
# Pass the exception upwards
|
|
(exc_type, exc_value, exc_traceback) = sys.exc_info()
|
|
raise exc_type, exc_value, exc_traceback
|
|
if outdoc == indoc:
|
|
os.remove(tempfile)
|
|
else:
|
|
outfp.write(outdoc)
|
|
if not trans_filename:
|
|
os.rename(tempfile, file)
|
|
elif type(trans_filename) == type(""):
|
|
i = file.rfind(trans_filename[0])
|
|
if i > -1:
|
|
file = file[:i]
|
|
os.rename(tempfile, file + trans_filename)
|
|
else:
|
|
os.rename(tempfile, trans_filename(file))
|
|
|
|
if __name__ == '__main__':
|
|
import getopt
|
|
|
|
def nametrans(name):
|
|
return name + ".out"
|
|
|
|
def filefilter_test(name, file, infp, outfp):
|
|
"Test hook for filefilter entry point -- put dashes before blank lines."
|
|
while 1:
|
|
line = infp.readline()
|
|
if not line:
|
|
break
|
|
if line == "\n":
|
|
outfp.write("------------------------------------------\n")
|
|
outfp.write(line)
|
|
|
|
def linefilter_test(name, file, data):
|
|
"Test hook for linefilter entry point -- wrap lines in brackets."
|
|
return "<" + data[:-1] + ">\n"
|
|
|
|
def sponge_test(name, file, data):
|
|
"Test hook for the sponge entry point -- reverse file lines."
|
|
lines = data.split("\n")
|
|
lines.reverse()
|
|
return "\n".join(lines)
|
|
|
|
(options, arguments) = getopt.getopt(sys.argv[1:], "fls")
|
|
for (switch, val) in options:
|
|
if switch == '-f':
|
|
filefilter("filefilter_test", arguments, filefilter_test,nametrans)
|
|
elif switch == '-l':
|
|
linefilter("linefilter_test", arguments, linefilter_test,nametrans)
|
|
elif switch == '-s':
|
|
sponge("sponge_test", arguments, sponge_test, ".foo")
|
|
else:
|
|
print "Unknown option."
|
|
|
|
# End
|