1#! /usr/bin/env python 2 3# This script is obsolete -- it is kept for historical purposes only. 4# 5# Fix Python source files to use the new class definition syntax, i.e., 6# the syntax used in Python versions before 0.9.8: 7# class C() = base(), base(), ...: ... 8# is changed to the current syntax: 9# class C(base, base, ...): ... 10# 11# The script uses heuristics to find class definitions that usually 12# work but occasionally can fail; carefully check the output! 13# 14# Command line arguments are files or directories to be processed. 15# Directories are searched recursively for files whose name looks 16# like a python module. 17# Symbolic links are always ignored (except as explicit directory 18# arguments). Of course, the original file is kept as a back-up 19# (with a "~" attached to its name). 20# 21# Changes made are reported to stdout in a diff-like format. 22# 23# Undoubtedly you can do this using find and sed or perl, but this is 24# a nice example of Python code that recurses down a directory tree 25# and uses regular expressions. Also note several subtleties like 26# preserving the file's mode and avoiding to even write a temp file 27# when no changes are needed for a file. 28# 29# NB: by changing only the function fixline() you can turn this 30# into a program for a different change to Python programs... 31 32import sys 33import re 34import os 35from stat import * 36 37err = sys.stderr.write 38dbg = err 39rep = sys.stdout.write 40 41def main(): 42 bad = 0 43 if not sys.argv[1:]: # No arguments 44 err('usage: ' + sys.argv[0] + ' file-or-directory ...\n') 45 sys.exit(2) 46 for arg in sys.argv[1:]: 47 if os.path.isdir(arg): 48 if recursedown(arg): bad = 1 49 elif os.path.islink(arg): 50 err(arg + ': will not process symbolic links\n') 51 bad = 1 52 else: 53 if fix(arg): bad = 1 54 sys.exit(bad) 55 56ispythonprog = re.compile('^[a-zA-Z0-9_]+\.py$') 57def ispython(name): 58 return ispythonprog.match(name) >= 0 59 60def recursedown(dirname): 61 dbg('recursedown(%r)\n' % (dirname,)) 62 bad = 0 63 try: 64 names = os.listdir(dirname) 65 except os.error, msg: 66 err('%s: cannot list directory: %r\n' % (dirname, msg)) 67 return 1 68 names.sort() 69 subdirs = [] 70 for name in names: 71 if name in (os.curdir, os.pardir): continue 72 fullname = os.path.join(dirname, name) 73 if os.path.islink(fullname): pass 74 elif os.path.isdir(fullname): 75 subdirs.append(fullname) 76 elif ispython(name): 77 if fix(fullname): bad = 1 78 for fullname in subdirs: 79 if recursedown(fullname): bad = 1 80 return bad 81 82def fix(filename): 83## dbg('fix(%r)\n' % (filename,)) 84 try: 85 f = open(filename, 'r') 86 except IOError, msg: 87 err('%s: cannot open: %r\n' % (filename, msg)) 88 return 1 89 head, tail = os.path.split(filename) 90 tempname = os.path.join(head, '@' + tail) 91 g = None 92 # If we find a match, we rewind the file and start over but 93 # now copy everything to a temp file. 94 lineno = 0 95 while 1: 96 line = f.readline() 97 if not line: break 98 lineno = lineno + 1 99 while line[-2:] == '\\\n': 100 nextline = f.readline() 101 if not nextline: break 102 line = line + nextline 103 lineno = lineno + 1 104 newline = fixline(line) 105 if newline != line: 106 if g is None: 107 try: 108 g = open(tempname, 'w') 109 except IOError, msg: 110 f.close() 111 err('%s: cannot create: %r\n' % (tempname, msg)) 112 return 1 113 f.seek(0) 114 lineno = 0 115 rep(filename + ':\n') 116 continue # restart from the beginning 117 rep(repr(lineno) + '\n') 118 rep('< ' + line) 119 rep('> ' + newline) 120 if g is not None: 121 g.write(newline) 122 123 # End of file 124 f.close() 125 if not g: return 0 # No changes 126 127 # Finishing touch -- move files 128 129 # First copy the file's mode to the temp file 130 try: 131 statbuf = os.stat(filename) 132 os.chmod(tempname, statbuf[ST_MODE] & 07777) 133 except os.error, msg: 134 err('%s: warning: chmod failed (%r)\n' % (tempname, msg)) 135 # Then make a backup of the original file as filename~ 136 try: 137 os.rename(filename, filename + '~') 138 except os.error, msg: 139 err('%s: warning: backup failed (%r)\n' % (filename, msg)) 140 # Now move the temp file to the original file 141 try: 142 os.rename(tempname, filename) 143 except os.error, msg: 144 err('%s: rename failed (%r)\n' % (filename, msg)) 145 return 1 146 # Return succes 147 return 0 148 149# This expression doesn't catch *all* class definition headers, 150# but it's pretty darn close. 151classexpr = '^([ \t]*class +[a-zA-Z0-9_]+) *( *) *((=.*)?):' 152classprog = re.compile(classexpr) 153 154# Expressions for finding base class expressions. 155baseexpr = '^ *(.*) *( *) *$' 156baseprog = re.compile(baseexpr) 157 158def fixline(line): 159 if classprog.match(line) < 0: # No 'class' keyword -- no change 160 return line 161 162 (a0, b0), (a1, b1), (a2, b2) = classprog.regs[:3] 163 # a0, b0 = Whole match (up to ':') 164 # a1, b1 = First subexpression (up to classname) 165 # a2, b2 = Second subexpression (=.*) 166 head = line[:b1] 167 tail = line[b0:] # Unmatched rest of line 168 169 if a2 == b2: # No base classes -- easy case 170 return head + ':' + tail 171 172 # Get rid of leading '=' 173 basepart = line[a2+1:b2] 174 175 # Extract list of base expressions 176 bases = basepart.split(',') 177 178 # Strip trailing '()' from each base expression 179 for i in range(len(bases)): 180 if baseprog.match(bases[i]) >= 0: 181 x1, y1 = baseprog.regs[1] 182 bases[i] = bases[i][x1:y1] 183 184 # Join the bases back again and build the new line 185 basepart = ', '.join(bases) 186 187 return head + '(' + basepart + '):' + tail 188 189if __name__ == '__main__': 190 main() 191