blob: b5147c921a7d9663c0798263eca826c2e7900ed0 [file] [log] [blame]
Guido van Rossumaa895c71993-06-10 14:43:53 +00001#! /ufs/guido/bin/sgi/python
2#! /usr/local/bin/python
3
4# Perform massive identifier substitution on C source files.
5# This actually tokenizes the files (to some extent) so it can
6# avoid making substitutions inside strings or comments.
7# Inside strings, substitutions are never made; inside comments,
8# it is a user option (on by default).
9#
10# The substitutions are read from one or more files whose lines,
11# when not empty, after stripping comments starting with #,
12# must contain exactly two words separated by whitespace: the
13# old identifier and its replacement.
14#
15# The option -r reverses the sense of the substitutions (this may be
16# useful to undo a particular substitution).
17#
18# If the old identifier is prefixed with a '*' (with no intervening
19# whitespace), then it will not be substituted inside comments.
20#
21# Command line arguments are files or directories to be processed.
22# Directories are searched recursively for files whose name looks
23# like a C file (ends in .h or .c). The special filename '-' means
24# operate in filter mode: read stdin, write stdout.
25#
26# Symbolic links are always ignored (except as explicit directory
27# arguments).
28#
29# The original files are kept as back-up with a "~" suffix.
30#
31# Changes made are reported to stdout in a diff-like format.
32#
33# NB: by changing only the function fixline() you can turn this
34# into a program for different changes to C source files; by
35# changing the function wanted() you can make a different selection of
36# files.
37
38import sys
39import regex
40import string
41import os
42from stat import *
43import getopt
44
45err = sys.stderr.write
46dbg = err
47rep = sys.stdout.write
48
49def usage():
Guido van Rossumf62f6871994-01-07 10:55:15 +000050 progname = sys.argv[0]
51 err('Usage: ' + progname +
52 ' [-c] [-r] [-s file] ... file-or-directory ...\n')
Guido van Rossumaa895c71993-06-10 14:43:53 +000053 err('\n')
Guido van Rossumf62f6871994-01-07 10:55:15 +000054 err('-c : substitute inside comments\n')
Guido van Rossumaa895c71993-06-10 14:43:53 +000055 err('-r : reverse direction for following -s options\n')
56 err('-s substfile : add a file of substitutions\n')
57 err('\n')
58 err('Each non-empty non-comment line in a substitution file must\n')
59 err('contain exactly two words: an identifier and its replacement.\n')
60 err('Comments start with a # character and end at end of line.\n')
Guido van Rossumf62f6871994-01-07 10:55:15 +000061 err('If an identifier is preceded with a *, it is not substituted\n')
62 err('inside a comment even when -c is specified.\n')
Guido van Rossumaa895c71993-06-10 14:43:53 +000063
64def main():
65 try:
Guido van Rossumf62f6871994-01-07 10:55:15 +000066 opts, args = getopt.getopt(sys.argv[1:], 'crs:')
Guido van Rossumaa895c71993-06-10 14:43:53 +000067 except getopt.error, msg:
68 err('Options error: ' + str(msg) + '\n')
69 usage()
70 sys.exit(2)
71 bad = 0
72 if not args: # No arguments
73 usage()
74 sys.exit(2)
75 for opt, arg in opts:
Guido van Rossumf62f6871994-01-07 10:55:15 +000076 if opt == '-c':
77 setdocomments()
Guido van Rossumaa895c71993-06-10 14:43:53 +000078 if opt == '-r':
79 setreverse()
80 if opt == '-s':
81 addsubst(arg)
82 for arg in args:
83 if os.path.isdir(arg):
84 if recursedown(arg): bad = 1
85 elif os.path.islink(arg):
86 err(arg + ': will not process symbolic links\n')
87 bad = 1
88 else:
89 if fix(arg): bad = 1
90 sys.exit(bad)
91
92# Change this regular expression to select a different set of files
93Wanted = '^[a-zA-Z0-9_]+\.[ch]$'
94def wanted(name):
95 return regex.match(Wanted, name) >= 0
96
97def recursedown(dirname):
98 dbg('recursedown(' + `dirname` + ')\n')
99 bad = 0
100 try:
101 names = os.listdir(dirname)
102 except os.error, msg:
103 err(dirname + ': cannot list directory: ' + str(msg) + '\n')
104 return 1
105 names.sort()
106 subdirs = []
107 for name in names:
108 if name in (os.curdir, os.pardir): continue
109 fullname = os.path.join(dirname, name)
110 if os.path.islink(fullname): pass
111 elif os.path.isdir(fullname):
112 subdirs.append(fullname)
113 elif wanted(name):
114 if fix(fullname): bad = 1
115 for fullname in subdirs:
116 if recursedown(fullname): bad = 1
117 return bad
118
119def fix(filename):
120## dbg('fix(' + `filename` + ')\n')
121 if filename == '-':
122 # Filter mode
123 f = sys.stdin
124 g = sys.stdout
125 else:
126 # File replacement mode
127 try:
128 f = open(filename, 'r')
129 except IOError, msg:
130 err(filename + ': cannot open: ' + str(msg) + '\n')
131 return 1
132 head, tail = os.path.split(filename)
133 tempname = os.path.join(head, '@' + tail)
134 g = None
135 # If we find a match, we rewind the file and start over but
136 # now copy everything to a temp file.
137 lineno = 0
138 initfixline()
139 while 1:
140 line = f.readline()
141 if not line: break
142 lineno = lineno + 1
143 while line[-2:] == '\\\n':
144 nextline = f.readline()
145 if not nextline: break
146 line = line + nextline
147 lineno = lineno + 1
148 newline = fixline(line)
149 if newline != line:
150 if g is None:
151 try:
152 g = open(tempname, 'w')
153 except IOError, msg:
154 f.close()
Guido van Rossumf62f6871994-01-07 10:55:15 +0000155 err(tempname+': cannot create: '+
Guido van Rossumaa895c71993-06-10 14:43:53 +0000156 str(msg)+'\n')
157 return 1
158 f.seek(0)
159 lineno = 0
160 initfixline()
161 rep(filename + ':\n')
162 continue # restart from the beginning
163 rep(`lineno` + '\n')
164 rep('< ' + line)
165 rep('> ' + newline)
166 if g is not None:
167 g.write(newline)
168
169 # End of file
170 if filename == '-': return 0 # Done in filter mode
171 f.close()
172 if not g: return 0 # No changes
173
174 # Finishing touch -- move files
175
176 # First copy the file's mode to the temp file
177 try:
178 statbuf = os.stat(filename)
179 os.chmod(tempname, statbuf[ST_MODE] & 07777)
180 except os.error, msg:
181 err(tempname + ': warning: chmod failed (' + str(msg) + ')\n')
182 # Then make a backup of the original file as filename~
183 try:
184 os.rename(filename, filename + '~')
185 except os.error, msg:
186 err(filename + ': warning: backup failed (' + str(msg) + ')\n')
187 # Now move the temp file to the original file
188 try:
189 os.rename(tempname, filename)
190 except os.error, msg:
191 err(filename + ': rename failed (' + str(msg) + ')\n')
192 return 1
193 # Return succes
194 return 0
195
196# Tokenizing ANSI C (partly)
197
198Identifier = '[a-zA-Z_][a-zA-Z0-9_]+'
199String = '"\([^\n\\"]\|\\\\.\)*"'
200Char = '\'\([^\n\\\']\|\\\\.\)*\''
201CommentStart = '/\*'
202CommentEnd = '\*/'
203
204Hexnumber = '0[xX][0-9a-fA-F]*[uUlL]*'
205Octnumber = '0[0-7]*[uUlL]*'
206Decnumber = '[1-9][0-9]*[uUlL]*'
207Intnumber = Hexnumber + '\|' + Octnumber + '\|' + Decnumber
208Exponent = '[eE][-+]?[0-9]+'
209Pointfloat = '\([0-9]+\.[0-9]*\|\.[0-9]+\)\(' + Exponent + '\)?'
210Expfloat = '[0-9]+' + Exponent
211Floatnumber = Pointfloat + '\|' + Expfloat
212Number = Floatnumber + '\|' + Intnumber
213
214# Anything else is an operator -- don't list this explicitly because of '/*'
215
216OutsideComment = (Identifier, Number, String, Char, CommentStart)
217OutsideCommentPattern = '\(' + string.joinfields(OutsideComment, '\|') + '\)'
218OutsideCommentProgram = regex.compile(OutsideCommentPattern)
219
220InsideComment = (Identifier, Number, CommentEnd)
221InsideCommentPattern = '\(' + string.joinfields(InsideComment, '\|') + '\)'
222InsideCommentProgram = regex.compile(InsideCommentPattern)
223
224def initfixline():
225 global Program
226 Program = OutsideCommentProgram
227
228def fixline(line):
229 global Program
230## print '-->', `line`
231 i = 0
232 while i < len(line):
233 i = Program.search(line, i)
234 if i < 0: break
235 found = Program.group(0)
236## if Program is InsideCommentProgram: print '...',
237## else: print ' ',
238## print found
239 if len(found) == 2:
240 if found == '/*':
241 Program = InsideCommentProgram
242 elif found == '*/':
243 Program = OutsideCommentProgram
244 n = len(found)
245 if Dict.has_key(found):
246 subst = Dict[found]
247 if Program is InsideCommentProgram:
Guido van Rossumf62f6871994-01-07 10:55:15 +0000248 if not Docomments:
249 print 'Found in comment:', found
250 continue
Guido van Rossumaa895c71993-06-10 14:43:53 +0000251 if NotInComment.has_key(found):
Guido van Rossumaa895c71993-06-10 14:43:53 +0000252## print 'Ignored in comment:',
253## print found, '-->', subst
254## print 'Line:', line,
255 subst = found
Guido van Rossumf62f6871994-01-07 10:55:15 +0000256## else:
Guido van Rossumaa895c71993-06-10 14:43:53 +0000257## print 'Substituting in comment:',
258## print found, '-->', subst
259## print 'Line:', line,
260 line = line[:i] + subst + line[i+n:]
261 n = len(subst)
262 i = i + n
263 return line
264
Guido van Rossumf62f6871994-01-07 10:55:15 +0000265Docomments = 0
266def setdocomments():
267 global Docomments
268 Docomments = 1
269
Guido van Rossumaa895c71993-06-10 14:43:53 +0000270Reverse = 0
271def setreverse():
272 global Reverse
273 Reverse = (not Reverse)
274
275Dict = {}
276NotInComment = {}
277def addsubst(substfile):
278 try:
279 fp = open(substfile, 'r')
280 except IOError, msg:
281 err(substfile + ': cannot read substfile: ' + str(msg) + '\n')
282 sys.exit(1)
283 lineno = 0
284 while 1:
285 line = fp.readline()
286 if not line: break
287 lineno = lineno + 1
288 try:
289 i = string.index(line, '#')
290 except string.index_error:
291 i = -1 # Happens to delete trailing \n
292 words = string.split(line[:i])
293 if not words: continue
294 if len(words) <> 2:
Guido van Rossumf62f6871994-01-07 10:55:15 +0000295 err(substfile + ':' + `lineno` +
Guido van Rossumaa895c71993-06-10 14:43:53 +0000296 ': warning: bad line: ' + line)
297 continue
298 if Reverse:
299 [value, key] = words
300 else:
301 [key, value] = words
302 if value[0] == '*':
303 value = value[1:]
304 if key[0] == '*':
305 key = key[1:]
306 NotInComment[key] = value
307 if Dict.has_key(key):
Guido van Rossumf62f6871994-01-07 10:55:15 +0000308 err(substfile + ':' + `lineno` +
309 ': warning: overriding: ' +
Guido van Rossumaa895c71993-06-10 14:43:53 +0000310 key + ' ' + value + '\n')
Guido van Rossumf62f6871994-01-07 10:55:15 +0000311 err(substfile + ':' + `lineno` +
Guido van Rossumaa895c71993-06-10 14:43:53 +0000312 ': warning: previous: ' + Dict[key] + '\n')
313 Dict[key] = value
314 fp.close()
315
316main()