blob: 52fe4a2e1985a36902508f45bdcc91d0a02f303f [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
Eric S. Raymond29bb1152001-08-20 13:16:30 +000010This module provides framework and glue code to make such programs
11easy to write. You supply a function to massage the file data. It
12always takes initial name and filename arguments; depending on which
13entry point you use, it can also take input and output file pointers,
14or it can take a string consisting of the entire file's data and
15return a replacement, or it can take in succession strings consisting
16of each of the file's lines and return a translated line for each.
17
18The fourth, optional argument of each entry point is a name
19transformation function or name suffix string. If it is of string
20type, the shortest suffix of each filename beginning with the first
21character of the argument string is stripped off. If the first
22character of the argument does not occur in the filename, no suffix is
23removed. Then the name suffix argument is concatenated to the end of
24the stripped filename. (Thus, a name suffix argument of ".x" will
25cause the filenames foo.c and bar.d to be transformed to foo.x and
26bar.x respectively.)
Eric S. Raymondb60f2d02001-08-18 09:24:38 +000027
28Argument files are transformed in left to right order in the argument list.
29A filename consisting of a dash is interpreted as a directive to read from
30standard input (this can be useful in pipelines).
31
32Replacement of each file is atomic and doesn't occur until the
33translation of that file has completed. Any tempfiles are removed
34automatically on any exception thrown by the translation function,
35and the exception is then passed upwards.
36"""
37
38# Requires Python 2.
Eric S. Raymondb60f2d02001-08-18 09:24:38 +000039import sys, os, filecmp, traceback
40
41def filefilter(name, arguments, trans_data, trans_filename=None):
42 "Filter stdin to stdout, or file arguments to renamed files."
43 if not arguments:
Eric S. Raymond29bb1152001-08-20 13:16:30 +000044 trans_data(name, "stdin", sys.stdin, sys.stdout)
Eric S. Raymondb60f2d02001-08-18 09:24:38 +000045 else:
46 for file in arguments:
Tim Peters7c005af2001-08-20 21:48:00 +000047 if file == '-': # - is conventional for stdin
Eric S. Raymondb60f2d02001-08-18 09:24:38 +000048 file = "stdin"
49 infp = sys.stdin
50 else:
51 infp = open(file)
52 tempfile = file + ".~%s-%d~" % (name, os.getpid())
53 outfp = open(tempfile, "w")
54 try:
Eric S. Raymond29bb1152001-08-20 13:16:30 +000055 trans_data(name, file, infp, outfp)
Eric S. Raymondb60f2d02001-08-18 09:24:38 +000056 except:
57 os.remove(tempfile)
58 # Pass the exception upwards
59 (exc_type, exc_value, exc_traceback) = sys.exc_info()
60 raise exc_type, exc_value, exc_traceback
61 if filecmp.cmp(file, tempfile):
62 os.remove(tempfile)
63 else:
64 if not trans_filename:
65 os.rename(tempfile, file)
66 elif type(trans_filename) == type(""):
67 i = file.rfind(trans_filename[0])
68 if i > -1:
69 file = file[:i]
70 os.rename(tempfile, stem + trans_filename)
71 else:
72 os.rename(tempfile, trans_filename(file))
73
Eric S. Raymond29bb1152001-08-20 13:16:30 +000074def line_by_line(name, file, infp, outfp, translate_line):
Eric S. Raymondb60f2d02001-08-18 09:24:38 +000075 "Hook to do line-by-line translation for filters."
76 while 1:
77 line = infp.readline()
78 if line == "":
79 break
Tim Peters7c005af2001-08-20 21:48:00 +000080 elif line: # None returns are skipped
Eric S. Raymond29bb1152001-08-20 13:16:30 +000081 outfp.write(translate_line(name, file, line))
Eric S. Raymondb60f2d02001-08-18 09:24:38 +000082
83def linefilter(name, arguments, trans_data, trans_filename=None):
84 "Filter framework for line-by-line transformation."
85 return filefilter(name,
86 arguments,
Eric S. Raymond29bb1152001-08-20 13:16:30 +000087 lambda name, file, infp, outfp: line_by_line(name, file, infp, outfp, trans_data),
Eric S. Raymondb60f2d02001-08-18 09:24:38 +000088 trans_filename)
89
90def sponge(name, arguments, trans_data, trans_filename=None):
91 "Read input sources entire and transform them in memory."
92 if not arguments:
Eric S. Raymond29bb1152001-08-20 13:16:30 +000093 sys.stdout.write(trans_data(name, "stdin", sys.stdin.read()))
Eric S. Raymondb60f2d02001-08-18 09:24:38 +000094 else:
95 for file in arguments:
96 infp = open(file)
97 indoc = infp.read()
98 infp.close()
99 tempfile = file + ".~%s-%d~" % (name, os.getpid())
100 try:
101 outfp = open(tempfile, "w")
102 except OSError:
103 sys.stderr.write("%s: can't open tempfile" % name)
104 return 1
105 try:
Eric S. Raymond29bb1152001-08-20 13:16:30 +0000106 outdoc = trans_data(name, file, indoc)
Eric S. Raymondb60f2d02001-08-18 09:24:38 +0000107 except:
108 os.remove(tempfile)
109 # Pass the exception upwards
110 (exc_type, exc_value, exc_traceback) = sys.exc_info()
111 raise exc_type, exc_value, exc_traceback
112 if outdoc == indoc:
113 os.remove(tempfile)
114 else:
115 outfp.write(outdoc)
116 if not trans_filename:
117 os.rename(tempfile, file)
118 elif type(trans_filename) == type(""):
119 i = file.rfind(trans_filename[0])
120 if i > -1:
121 file = file[:i]
122 os.rename(tempfile, file + trans_filename)
123 else:
124 os.rename(tempfile, trans_filename(file))
125
126if __name__ == '__main__':
127 import getopt
128
129 def nametrans(name):
130 return name + ".out"
131
Eric S. Raymond29bb1152001-08-20 13:16:30 +0000132 def filefilter_test(name, file, infp, outfp):
Eric S. Raymondb60f2d02001-08-18 09:24:38 +0000133 "Test hook for filefilter entry point -- put dashes before blank lines."
134 while 1:
135 line = infp.readline()
136 if not line:
137 break
138 if line == "\n":
139 outfp.write("------------------------------------------\n")
140 outfp.write(line)
141
Eric S. Raymond29bb1152001-08-20 13:16:30 +0000142 def linefilter_test(name, file, data):
Eric S. Raymondb60f2d02001-08-18 09:24:38 +0000143 "Test hook for linefilter entry point -- wrap lines in brackets."
144 return "<" + data[:-1] + ">\n"
145
Eric S. Raymond29bb1152001-08-20 13:16:30 +0000146 def sponge_test(name, file, data):
Eric S. Raymondb60f2d02001-08-18 09:24:38 +0000147 "Test hook for the sponge entry point -- reverse file lines."
148 lines = data.split("\n")
149 lines.reverse()
150 return "\n".join(lines)
151
152 (options, arguments) = getopt.getopt(sys.argv[1:], "fls")
153 for (switch, val) in options:
154 if switch == '-f':
155 filefilter("filefilter_test", arguments, filefilter_test,nametrans)
156 elif switch == '-l':
157 linefilter("linefilter_test", arguments, linefilter_test,nametrans)
158 elif switch == '-s':
159 sponge("sponge_test", arguments, sponge_test, ".foo")
160 else:
161 print "Unknown option."
162
163# End