PageRenderTime 112ms CodeModel.GetById 57ms app.highlight 49ms RepoModel.GetById 1ms app.codeStats 0ms

/Tools/scripts/fixcid.py

http://unladen-swallow.googlecode.com/
Python | 314 lines | 232 code | 23 blank | 59 comment | 56 complexity | 76599154c605ed40790f6fb08c43b35a MD5 | raw file
  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
 37import sys
 38import re
 39import os
 40from stat import *
 41import getopt
 42
 43err = sys.stderr.write
 44dbg = err
 45rep = sys.stdout.write
 46
 47def 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
 62def 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
 91Wanted = '^[a-zA-Z0-9_]+\.[ch]$'
 92def wanted(name):
 93    return re.match(Wanted, name) >= 0
 94
 95def 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
117def 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 succes
192    return 0
193
194# Tokenizing ANSI C (partly)
195
196Identifier = '\(struct \)?[a-zA-Z_][a-zA-Z0-9_]+'
197String = '"\([^\n\\"]\|\\\\.\)*"'
198Char = '\'\([^\n\\\']\|\\\\.\)*\''
199CommentStart = '/\*'
200CommentEnd = '\*/'
201
202Hexnumber = '0[xX][0-9a-fA-F]*[uUlL]*'
203Octnumber = '0[0-7]*[uUlL]*'
204Decnumber = '[1-9][0-9]*[uUlL]*'
205Intnumber = Hexnumber + '\|' + Octnumber + '\|' + Decnumber
206Exponent = '[eE][-+]?[0-9]+'
207Pointfloat = '\([0-9]+\.[0-9]*\|\.[0-9]+\)\(' + Exponent + '\)?'
208Expfloat = '[0-9]+' + Exponent
209Floatnumber = Pointfloat + '\|' + Expfloat
210Number = Floatnumber + '\|' + Intnumber
211
212# Anything else is an operator -- don't list this explicitly because of '/*'
213
214OutsideComment = (Identifier, Number, String, Char, CommentStart)
215OutsideCommentPattern = '(' + '|'.join(OutsideComment) + ')'
216OutsideCommentProgram = re.compile(OutsideCommentPattern)
217
218InsideComment = (Identifier, Number, CommentEnd)
219InsideCommentPattern = '(' + '|'.join(InsideComment) + ')'
220InsideCommentProgram = re.compile(InsideCommentPattern)
221
222def initfixline():
223    global Program
224    Program = OutsideCommentProgram
225
226def 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
264Docomments = 0
265def setdocomments():
266    global Docomments
267    Docomments = 1
268
269Reverse = 0
270def setreverse():
271    global Reverse
272    Reverse = (not Reverse)
273
274Dict = {}
275NotInComment = {}
276def 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
313if __name__ == '__main__':
314    main()