Home | History | Annotate | Download | only in scripts
      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()
    315