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
43 err
= sys
.stderr
.write
45 rep
= sys
.stdout
.write
48 progname
= sys
.argv
[0]
49 err('Usage: ' + progname
+
50 ' [-c] [-r] [-s file] ... file-or-directory ...\n')
52 err('-c : substitute inside comments\n')
53 err('-r : reverse direction for following -s options\n')
54 err('-s substfile : add a file of substitutions\n')
56 err('Each non-empty non-comment line in a substitution file must\n')
57 err('contain exactly two words: an identifier and its replacement.\n')
58 err('Comments start with a # character and end at end of line.\n')
59 err('If an identifier is preceded with a *, it is not substituted\n')
60 err('inside a comment even when -c is specified.\n')
64 opts
, args
= getopt
.getopt(sys
.argv
[1:], 'crs:')
65 except getopt
.error
, msg
:
66 err('Options error: ' + str(msg
) + '\n')
70 if not args
: # No arguments
81 if os
.path
.isdir(arg
):
82 if recursedown(arg
): bad
= 1
83 elif os
.path
.islink(arg
):
84 err(arg
+ ': will not process symbolic links\n')
90 # Change this regular expression to select a different set of files
91 Wanted
= '^[a-zA-Z0-9_]+\.[ch]$'
93 return re
.match(Wanted
, name
) >= 0
95 def recursedown(dirname
):
96 dbg('recursedown(%r)\n' % (dirname
,))
99 names
= os
.listdir(dirname
)
100 except os
.error
, msg
:
101 err(dirname
+ ': cannot list directory: ' + str(msg
) + '\n')
106 if name
in (os
.curdir
, os
.pardir
): continue
107 fullname
= os
.path
.join(dirname
, name
)
108 if os
.path
.islink(fullname
): pass
109 elif os
.path
.isdir(fullname
):
110 subdirs
.append(fullname
)
112 if fix(fullname
): bad
= 1
113 for fullname
in subdirs
:
114 if recursedown(fullname
): bad
= 1
118 ## dbg('fix(%r)\n' % (filename,))
124 # File replacement mode
126 f
= open(filename
, 'r')
128 err(filename
+ ': cannot open: ' + str(msg
) + '\n')
130 head
, tail
= os
.path
.split(filename
)
131 tempname
= os
.path
.join(head
, '@' + tail
)
133 # If we find a match, we rewind the file and start over but
134 # now copy everything to a temp file.
141 while line
[-2:] == '\\\n':
142 nextline
= f
.readline()
143 if not nextline
: break
144 line
= line
+ nextline
146 newline
= fixline(line
)
150 g
= open(tempname
, 'w')
153 err(tempname
+': cannot create: '+
159 rep(filename
+ ':\n')
160 continue # restart from the beginning
161 rep(repr(lineno
) + '\n')
168 if filename
== '-': return 0 # Done in filter mode
170 if not g
: return 0 # No changes
172 # Finishing touch -- move files
174 # First copy the file's mode to the temp file
176 statbuf
= os
.stat(filename
)
177 os
.chmod(tempname
, statbuf
[ST_MODE
] & 07777)
178 except os
.error
, msg
:
179 err(tempname
+ ': warning: chmod failed (' + str(msg
) + ')\n')
180 # Then make a backup of the original file as filename~
182 os
.rename(filename
, filename
+ '~')
183 except os
.error
, msg
:
184 err(filename
+ ': warning: backup failed (' + str(msg
) + ')\n')
185 # Now move the temp file to the original file
187 os
.rename(tempname
, filename
)
188 except os
.error
, msg
:
189 err(filename
+ ': rename failed (' + str(msg
) + ')\n')
194 # Tokenizing ANSI C (partly)
196 Identifier
= '\(struct \)?[a-zA-Z_][a-zA-Z0-9_]+'
197 String
= '"\([^\n\\"]\|\\\\.\)*"'
198 Char
= '\'\([^\n\\\']\|\\\\.\)*\''
202 Hexnumber
= '0[xX][0-9a-fA-F]*[uUlL]*'
203 Octnumber
= '0[0-7]*[uUlL]*'
204 Decnumber
= '[1-9][0-9]*[uUlL]*'
205 Intnumber
= Hexnumber
+ '\|' + Octnumber
+ '\|' + Decnumber
206 Exponent
= '[eE][-+]?[0-9]+'
207 Pointfloat
= '\([0-9]+\.[0-9]*\|\.[0-9]+\)\(' + Exponent
+ '\)?'
208 Expfloat
= '[0-9]+' + Exponent
209 Floatnumber
= Pointfloat
+ '\|' + Expfloat
210 Number
= Floatnumber
+ '\|' + Intnumber
212 # Anything else is an operator -- don't list this explicitly because of '/*'
214 OutsideComment
= (Identifier
, Number
, String
, Char
, CommentStart
)
215 OutsideCommentPattern
= '(' + '|'.join(OutsideComment
) + ')'
216 OutsideCommentProgram
= re
.compile(OutsideCommentPattern
)
218 InsideComment
= (Identifier
, Number
, CommentEnd
)
219 InsideCommentPattern
= '(' + '|'.join(InsideComment
) + ')'
220 InsideCommentProgram
= re
.compile(InsideCommentPattern
)
224 Program
= OutsideCommentProgram
228 ## print '-->', repr(line)
231 i
= Program
.search(line
, i
)
233 found
= Program
.group(0)
234 ## if Program is InsideCommentProgram: print '...',
239 Program
= InsideCommentProgram
241 Program
= OutsideCommentProgram
243 if Dict
.has_key(found
):
245 if Program
is InsideCommentProgram
:
247 print 'Found in comment:', found
250 if NotInComment
.has_key(found
):
251 ## print 'Ignored in comment:',
252 ## print found, '-->', subst
253 ## print 'Line:', line,
256 ## print 'Substituting in comment:',
257 ## print found, '-->', subst
258 ## print 'Line:', line,
259 line
= line
[:i
] + subst
+ line
[i
+n
:]
272 Reverse
= (not Reverse
)
276 def addsubst(substfile
):
278 fp
= open(substfile
, 'r')
280 err(substfile
+ ': cannot read substfile: ' + str(msg
) + '\n')
290 i
= -1 # Happens to delete trailing \n
291 words
= line
[:i
].split()
292 if not words
: continue
293 if len(words
) == 3 and words
[0] == 'struct':
294 words
[:2] = [words
[0] + ' ' + words
[1]]
295 elif len(words
) <> 2:
296 err(substfile
+ '%s:%r: warning: bad line: %r' % (substfile
, lineno
, line
))
306 NotInComment
[key
] = value
307 if Dict
.has_key(key
):
308 err('%s:%r: warning: overriding: %r %r\n' % (substfile
, lineno
, key
, value
))
309 err('%s:%r: warning: previous: %r\n' % (substfile
, lineno
, Dict
[key
]))
313 if __name__
== '__main__':