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