summaryrefslogtreecommitdiffstats
path: root/Lib
diff options
context:
space:
mode:
Diffstat (limited to 'Lib')
-rw-r--r--Lib/compilerlike.py154
1 files changed, 154 insertions, 0 deletions
diff --git a/Lib/compilerlike.py b/Lib/compilerlike.py
new file mode 100644
index 0000000..618f6bb
--- /dev/null
+++ b/Lib/compilerlike.py
@@ -0,0 +1,154 @@
+"""
+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; depending
+on which entry point you use, it can 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.
+
+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.
+from __future__ import nested_scopes
+
+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("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(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, 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, line))
+
+def linefilter(name, arguments, trans_data, trans_filename=None):
+ "Filter framework for line-by-line transformation."
+ return filefilter(name,
+ arguments,
+ lambda name, infp, outfp: line_by_line(name, 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, 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, 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, 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, data):
+ "Test hook for linefilter entry point -- wrap lines in brackets."
+ return "<" + data[:-1] + ">\n"
+
+ def sponge_test(name, 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