blob: 618f6bb21c1e955529796c3790d04a548d28cf37 [file] [log] [blame]
Eric S. Raymondb60f2d02001-08-18 09:24:38 +00001"""
2compilerlike -- framework code for building compiler-like programs.
3
4There is a common `compiler-like' pattern in Unix scripts which is useful
5for translation utilities of all sorts. A program following this pattern
6behaves as a filter when no argument files are specified on the command
7line, but otherwise transforms each file individually into a corresponding
8output file.
9
10This module provides framework and glue code to make such programs easy
11to write. You supply a function to massage the file data; depending
12on which entry point you use, it can take input and output file pointers,
13or it can take a string consisting of the entire file's data and return
14a replacement, or it can take in succession strings consisting of each
15of the file's lines and return a translated line for each.
16
17Argument files are transformed in left to right order in the argument list.
18A filename consisting of a dash is interpreted as a directive to read from
19standard input (this can be useful in pipelines).
20
21Replacement of each file is atomic and doesn't occur until the
22translation of that file has completed. Any tempfiles are removed
23automatically on any exception thrown by the translation function,
24and the exception is then passed upwards.
25"""
26
27# Requires Python 2.
28from __future__ import nested_scopes
29
30import sys, os, filecmp, traceback
31
32def filefilter(name, arguments, trans_data, trans_filename=None):
33 "Filter stdin to stdout, or file arguments to renamed files."
34 if not arguments:
35 trans_data("stdin", sys.stdin, sys.stdout)
36 else:
37 for file in arguments:
38 if file == '-': # - is conventional for stdin
39 file = "stdin"
40 infp = sys.stdin
41 else:
42 infp = open(file)
43 tempfile = file + ".~%s-%d~" % (name, os.getpid())
44 outfp = open(tempfile, "w")
45 try:
46 trans_data(file, infp, outfp)
47 except:
48 os.remove(tempfile)
49 # Pass the exception upwards
50 (exc_type, exc_value, exc_traceback) = sys.exc_info()
51 raise exc_type, exc_value, exc_traceback
52 if filecmp.cmp(file, tempfile):
53 os.remove(tempfile)
54 else:
55 if not trans_filename:
56 os.rename(tempfile, file)
57 elif type(trans_filename) == type(""):
58 i = file.rfind(trans_filename[0])
59 if i > -1:
60 file = file[:i]
61 os.rename(tempfile, stem + trans_filename)
62 else:
63 os.rename(tempfile, trans_filename(file))
64
65def line_by_line(name, infp, outfp, translate_line):
66 "Hook to do line-by-line translation for filters."
67 while 1:
68 line = infp.readline()
69 if line == "":
70 break
71 elif line: # None returns are skipped
72 outfp.write(translate_line(name, line))
73
74def linefilter(name, arguments, trans_data, trans_filename=None):
75 "Filter framework for line-by-line transformation."
76 return filefilter(name,
77 arguments,
78 lambda name, infp, outfp: line_by_line(name, infp, outfp, trans_data),
79 trans_filename)
80
81def sponge(name, arguments, trans_data, trans_filename=None):
82 "Read input sources entire and transform them in memory."
83 if not arguments:
84 sys.stdout.write(trans_data(name, sys.stdin.read()))
85 else:
86 for file in arguments:
87 infp = open(file)
88 indoc = infp.read()
89 infp.close()
90 tempfile = file + ".~%s-%d~" % (name, os.getpid())
91 try:
92 outfp = open(tempfile, "w")
93 except OSError:
94 sys.stderr.write("%s: can't open tempfile" % name)
95 return 1
96 try:
97 outdoc = trans_data(name, indoc)
98 except:
99 os.remove(tempfile)
100 # Pass the exception upwards
101 (exc_type, exc_value, exc_traceback) = sys.exc_info()
102 raise exc_type, exc_value, exc_traceback
103 if outdoc == indoc:
104 os.remove(tempfile)
105 else:
106 outfp.write(outdoc)
107 if not trans_filename:
108 os.rename(tempfile, file)
109 elif type(trans_filename) == type(""):
110 i = file.rfind(trans_filename[0])
111 if i > -1:
112 file = file[:i]
113 os.rename(tempfile, file + trans_filename)
114 else:
115 os.rename(tempfile, trans_filename(file))
116
117if __name__ == '__main__':
118 import getopt
119
120 def nametrans(name):
121 return name + ".out"
122
123 def filefilter_test(name, infp, outfp):
124 "Test hook for filefilter entry point -- put dashes before blank lines."
125 while 1:
126 line = infp.readline()
127 if not line:
128 break
129 if line == "\n":
130 outfp.write("------------------------------------------\n")
131 outfp.write(line)
132
133 def linefilter_test(name, data):
134 "Test hook for linefilter entry point -- wrap lines in brackets."
135 return "<" + data[:-1] + ">\n"
136
137 def sponge_test(name, data):
138 "Test hook for the sponge entry point -- reverse file lines."
139 lines = data.split("\n")
140 lines.reverse()
141 return "\n".join(lines)
142
143 (options, arguments) = getopt.getopt(sys.argv[1:], "fls")
144 for (switch, val) in options:
145 if switch == '-f':
146 filefilter("filefilter_test", arguments, filefilter_test,nametrans)
147 elif switch == '-l':
148 linefilter("linefilter_test", arguments, linefilter_test,nametrans)
149 elif switch == '-s':
150 sponge("sponge_test", arguments, sponge_test, ".foo")
151 else:
152 print "Unknown option."
153
154# End