Update to 2.7.3
[profile/ivi/python.git] / Tools / scripts / fixcid.py
1 #! /usr/bin/env python
2
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).
8 #
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
37 import sys
38 import re
39 import os
40 from stat import *
41 import getopt
42
43 err = sys.stderr.write
44 dbg = err
45 rep = sys.stdout.write
46
47 def usage():
48     progname = sys.argv[0]
49     err('Usage: ' + progname +
50               ' [-c] [-r] [-s file] ... file-or-directory ...\n')
51     err('\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')
55     err('\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')
61
62 def main():
63     try:
64         opts, args = getopt.getopt(sys.argv[1:], 'crs:')
65     except getopt.error, msg:
66         err('Options error: ' + str(msg) + '\n')
67         usage()
68         sys.exit(2)
69     bad = 0
70     if not args: # No arguments
71         usage()
72         sys.exit(2)
73     for opt, arg in opts:
74         if opt == '-c':
75             setdocomments()
76         if opt == '-r':
77             setreverse()
78         if opt == '-s':
79             addsubst(arg)
80     for arg in args:
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')
85             bad = 1
86         else:
87             if fix(arg): bad = 1
88     sys.exit(bad)
89
90 # Change this regular expression to select a different set of files
91 Wanted = '^[a-zA-Z0-9_]+\.[ch]$'
92 def wanted(name):
93     return re.match(Wanted, name) >= 0
94
95 def recursedown(dirname):
96     dbg('recursedown(%r)\n' % (dirname,))
97     bad = 0
98     try:
99         names = os.listdir(dirname)
100     except os.error, msg:
101         err(dirname + ': cannot list directory: ' + str(msg) + '\n')
102         return 1
103     names.sort()
104     subdirs = []
105     for name in names:
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)
111         elif wanted(name):
112             if fix(fullname): bad = 1
113     for fullname in subdirs:
114         if recursedown(fullname): bad = 1
115     return bad
116
117 def fix(filename):
118 ##  dbg('fix(%r)\n' % (filename,))
119     if filename == '-':
120         # Filter mode
121         f = sys.stdin
122         g = sys.stdout
123     else:
124         # File replacement mode
125         try:
126             f = open(filename, 'r')
127         except IOError, msg:
128             err(filename + ': cannot open: ' + str(msg) + '\n')
129             return 1
130         head, tail = os.path.split(filename)
131         tempname = os.path.join(head, '@' + tail)
132         g = None
133     # If we find a match, we rewind the file and start over but
134     # now copy everything to a temp file.
135     lineno = 0
136     initfixline()
137     while 1:
138         line = f.readline()
139         if not line: break
140         lineno = lineno + 1
141         while line[-2:] == '\\\n':
142             nextline = f.readline()
143             if not nextline: break
144             line = line + nextline
145             lineno = lineno + 1
146         newline = fixline(line)
147         if newline != line:
148             if g is None:
149                 try:
150                     g = open(tempname, 'w')
151                 except IOError, msg:
152                     f.close()
153                     err(tempname+': cannot create: '+
154                         str(msg)+'\n')
155                     return 1
156                 f.seek(0)
157                 lineno = 0
158                 initfixline()
159                 rep(filename + ':\n')
160                 continue # restart from the beginning
161             rep(repr(lineno) + '\n')
162             rep('< ' + line)
163             rep('> ' + newline)
164         if g is not None:
165             g.write(newline)
166
167     # End of file
168     if filename == '-': return 0 # Done in filter mode
169     f.close()
170     if not g: return 0 # No changes
171
172     # Finishing touch -- move files
173
174     # First copy the file's mode to the temp file
175     try:
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~
181     try:
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
186     try:
187         os.rename(tempname, filename)
188     except os.error, msg:
189         err(filename + ': rename failed (' + str(msg) + ')\n')
190         return 1
191     # Return success
192     return 0
193
194 # Tokenizing ANSI C (partly)
195
196 Identifier = '\(struct \)?[a-zA-Z_][a-zA-Z0-9_]+'
197 String = '"\([^\n\\"]\|\\\\.\)*"'
198 Char = '\'\([^\n\\\']\|\\\\.\)*\''
199 CommentStart = '/\*'
200 CommentEnd = '\*/'
201
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
211
212 # Anything else is an operator -- don't list this explicitly because of '/*'
213
214 OutsideComment = (Identifier, Number, String, Char, CommentStart)
215 OutsideCommentPattern = '(' + '|'.join(OutsideComment) + ')'
216 OutsideCommentProgram = re.compile(OutsideCommentPattern)
217
218 InsideComment = (Identifier, Number, CommentEnd)
219 InsideCommentPattern = '(' + '|'.join(InsideComment) + ')'
220 InsideCommentProgram = re.compile(InsideCommentPattern)
221
222 def initfixline():
223     global Program
224     Program = OutsideCommentProgram
225
226 def fixline(line):
227     global Program
228 ##  print '-->', repr(line)
229     i = 0
230     while i < len(line):
231         i = Program.search(line, i)
232         if i < 0: break
233         found = Program.group(0)
234 ##      if Program is InsideCommentProgram: print '...',
235 ##      else: print '   ',
236 ##      print found
237         if len(found) == 2:
238             if found == '/*':
239                 Program = InsideCommentProgram
240             elif found == '*/':
241                 Program = OutsideCommentProgram
242         n = len(found)
243         if Dict.has_key(found):
244             subst = Dict[found]
245             if Program is InsideCommentProgram:
246                 if not Docomments:
247                     print 'Found in comment:', found
248                     i = i + n
249                     continue
250                 if NotInComment.has_key(found):
251 ##                  print 'Ignored in comment:',
252 ##                  print found, '-->', subst
253 ##                  print 'Line:', line,
254                     subst = found
255 ##              else:
256 ##                  print 'Substituting in comment:',
257 ##                  print found, '-->', subst
258 ##                  print 'Line:', line,
259             line = line[:i] + subst + line[i+n:]
260             n = len(subst)
261         i = i + n
262     return line
263
264 Docomments = 0
265 def setdocomments():
266     global Docomments
267     Docomments = 1
268
269 Reverse = 0
270 def setreverse():
271     global Reverse
272     Reverse = (not Reverse)
273
274 Dict = {}
275 NotInComment = {}
276 def addsubst(substfile):
277     try:
278         fp = open(substfile, 'r')
279     except IOError, msg:
280         err(substfile + ': cannot read substfile: ' + str(msg) + '\n')
281         sys.exit(1)
282     lineno = 0
283     while 1:
284         line = fp.readline()
285         if not line: break
286         lineno = lineno + 1
287         try:
288             i = line.index('#')
289         except ValueError:
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))
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):
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]))
310         Dict[key] = value
311     fp.close()
312
313 if __name__ == '__main__':
314     main()