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,
7 # it is a user option (off by default).
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.
14 # The option -r reverses the sense of the substitutions (this may be
15 # useful to undo a particular substitution).
17 # If the old identifier is prefixed with a '*' (with no intervening
18 # whitespace), then it will not be substituted inside comments.
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.
25 # Symbolic links are always ignored (except as explicit directory
28 # The original files are kept as back-up with a "~" suffix.
30 # Changes made are reported to stdout in a diff-like format.
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
44 err
= sys
.stderr
.write
46 rep
= sys
.stdout
.write
49 progname
= sys
.argv
[0]
50 err('Usage: ' + progname
+
51 ' [-c] [-r] [-s file] ... file-or-directory ...\n')
53 err('-c : substitute inside comments\n')
54 err('-r : reverse direction for following -s options\n')
55 err('-s substfile : add a file of substitutions\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')
60 err('If an identifier is preceded with a *, it is not substituted\n')
61 err('inside a comment even when -c is specified.\n')
65 opts
, args
= getopt
.getopt(sys
.argv
[1:], 'crs:')
66 except getopt
.error
, msg
:
67 err('Options error: ' + str(msg
) + '\n')
71 if not args
: # No arguments
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')
91 # Change this regular expression to select a different set of files
92 Wanted
= '^[a-zA-Z0-9_]+\.[ch]$'
94 return regex
.match(Wanted
, name
) >= 0
96 def recursedown(dirname
):
97 dbg('recursedown(' + `dirname`
+ ')\n')
100 names
= os
.listdir(dirname
)
101 except os
.error
, msg
:
102 err(dirname
+ ': cannot list directory: ' + str(msg
) + '\n')
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
)
113 if fix(fullname
): bad
= 1
114 for fullname
in subdirs
:
115 if recursedown(fullname
): bad
= 1
119 ## dbg('fix(' + `filename` + ')\n')
125 # File replacement mode
127 f
= open(filename
, 'r')
129 err(filename
+ ': cannot open: ' + str(msg
) + '\n')
131 head
, tail
= os
.path
.split(filename
)
132 tempname
= os
.path
.join(head
, '@' + tail
)
134 # If we find a match, we rewind the file and start over but
135 # now copy everything to a temp file.
142 while line
[-2:] == '\\\n':
143 nextline
= f
.readline()
144 if not nextline
: break
145 line
= line
+ nextline
147 newline
= fixline(line
)
151 g
= open(tempname
, 'w')
154 err(tempname
+': cannot create: '+
160 rep(filename
+ ':\n')
161 continue # restart from the beginning
169 if filename
== '-': return 0 # Done in filter mode
171 if not g
: return 0 # No changes
173 # Finishing touch -- move files
175 # First copy the file's mode to the temp file
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~
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
188 os
.rename(tempname
, filename
)
189 except os
.error
, msg
:
190 err(filename
+ ': rename failed (' + str(msg
) + ')\n')
195 # Tokenizing ANSI C (partly)
197 Identifier
= '\(struct \)?[a-zA-Z_][a-zA-Z0-9_]+'
198 String
= '"\([^\n\\"]\|\\\\.\)*"'
199 Char
= '\'\([^\n\\\']\|\\\\.\)*\''
203 Hexnumber
= '0[xX][0-9a-fA-F]*[uUlL]*'
204 Octnumber
= '0[0-7]*[uUlL]*'
205 Decnumber
= '[1-9][0-9]*[uUlL]*'
206 Intnumber
= Hexnumber
+ '\|' + Octnumber
+ '\|' + Decnumber
207 Exponent
= '[eE][-+]?[0-9]+'
208 Pointfloat
= '\([0-9]+\.[0-9]*\|\.[0-9]+\)\(' + Exponent
+ '\)?'
209 Expfloat
= '[0-9]+' + Exponent
210 Floatnumber
= Pointfloat
+ '\|' + Expfloat
211 Number
= Floatnumber
+ '\|' + Intnumber
213 # Anything else is an operator -- don't list this explicitly because of '/*'
215 OutsideComment
= (Identifier
, Number
, String
, Char
, CommentStart
)
216 OutsideCommentPattern
= '\(' + string
.joinfields(OutsideComment
, '\|') + '\)'
217 OutsideCommentProgram
= regex
.compile(OutsideCommentPattern
)
219 InsideComment
= (Identifier
, Number
, CommentEnd
)
220 InsideCommentPattern
= '\(' + string
.joinfields(InsideComment
, '\|') + '\)'
221 InsideCommentProgram
= regex
.compile(InsideCommentPattern
)
225 Program
= OutsideCommentProgram
229 ## print '-->', `line`
232 i
= Program
.search(line
, i
)
234 found
= Program
.group(0)
235 ## if Program is InsideCommentProgram: print '...',
240 Program
= InsideCommentProgram
242 Program
= OutsideCommentProgram
244 if Dict
.has_key(found
):
246 if Program
is InsideCommentProgram
:
248 print 'Found in comment:', found
251 if NotInComment
.has_key(found
):
252 ## print 'Ignored in comment:',
253 ## print found, '-->', subst
254 ## print 'Line:', line,
257 ## print 'Substituting in comment:',
258 ## print found, '-->', subst
259 ## print 'Line:', line,
260 line
= line
[:i
] + subst
+ line
[i
+n
:]
273 Reverse
= (not Reverse
)
277 def addsubst(substfile
):
279 fp
= open(substfile
, 'r')
281 err(substfile
+ ': cannot read substfile: ' + str(msg
) + '\n')
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
) == 3 and words
[0] == 'struct':
295 words
[:2] = [words
[0] + ' ' + words
[1]]
296 elif len(words
) <> 2:
297 err(substfile
+ ':' + `lineno`
+
298 ': warning: bad line: ' + line
)
308 NotInComment
[key
] = value
309 if Dict
.has_key(key
):
310 err(substfile
+ ':' + `lineno`
+
311 ': warning: overriding: ' +
312 key
+ ' ' + value
+ '\n')
313 err(substfile
+ ':' + `lineno`
+
314 ': warning: previous: ' + Dict
[key
] + '\n')