1#! /usr/bin/env python 2 3# Fix Python source files to avoid using 4# def method(self, (arg1, ..., argn)): 5# instead of the more rational 6# def method(self, arg1, ..., argn): 7# 8# Command line arguments are files or directories to be processed. 9# Directories are searched recursively for files whose name looks 10# like a python module. 11# Symbolic links are always ignored (except as explicit directory 12# arguments). Of course, the original file is kept as a back-up 13# (with a "~" attached to its name). 14# It complains about binaries (files containing null bytes) 15# and about files that are ostensibly not Python files: if the first 16# line starts with '#!' and does not contain the string 'python'. 17# 18# Changes made are reported to stdout in a diff-like format. 19# 20# Undoubtedly you can do this using find and sed or perl, but this is 21# a nice example of Python code that recurses down a directory tree 22# and uses regular expressions. Also note several subtleties like 23# preserving the file's mode and avoiding to even write a temp file 24# when no changes are needed for a file. 25# 26# NB: by changing only the function fixline() you can turn this 27# into a program for a different change to Python programs... 28 29import sys 30import re 31import os 32from stat import * 33 34err = sys.stderr.write 35dbg = err 36rep = sys.stdout.write 37 38def main(): 39 bad = 0 40 if not sys.argv[1:]: # No arguments 41 err('usage: ' + sys.argv[0] + ' file-or-directory ...\n') 42 sys.exit(2) 43 for arg in sys.argv[1:]: 44 if os.path.isdir(arg): 45 if recursedown(arg): bad = 1 46 elif os.path.islink(arg): 47 err(arg + ': will not process symbolic links\n') 48 bad = 1 49 else: 50 if fix(arg): bad = 1 51 sys.exit(bad) 52 53ispythonprog = re.compile('^[a-zA-Z0-9_]+\.py$') 54def ispython(name): 55 return ispythonprog.match(name) >= 0 56 57def recursedown(dirname): 58 dbg('recursedown(%r)\n' % (dirname,)) 59 bad = 0 60 try: 61 names = os.listdir(dirname) 62 except os.error, msg: 63 err('%s: cannot list directory: %r\n' % (dirname, msg)) 64 return 1 65 names.sort() 66 subdirs = [] 67 for name in names: 68 if name in (os.curdir, os.pardir): continue 69 fullname = os.path.join(dirname, name) 70 if os.path.islink(fullname): pass 71 elif os.path.isdir(fullname): 72 subdirs.append(fullname) 73 elif ispython(name): 74 if fix(fullname): bad = 1 75 for fullname in subdirs: 76 if recursedown(fullname): bad = 1 77 return bad 78 79def fix(filename): 80## dbg('fix(%r)\n' % (filename,)) 81 try: 82 f = open(filename, 'r') 83 except IOError, msg: 84 err('%s: cannot open: %r\n' % (filename, msg)) 85 return 1 86 head, tail = os.path.split(filename) 87 tempname = os.path.join(head, '@' + tail) 88 g = None 89 # If we find a match, we rewind the file and start over but 90 # now copy everything to a temp file. 91 lineno = 0 92 while 1: 93 line = f.readline() 94 if not line: break 95 lineno = lineno + 1 96 if g is None and '\0' in line: 97 # Check for binary files 98 err(filename + ': contains null bytes; not fixed\n') 99 f.close() 100 return 1 101 if lineno == 1 and g is None and line[:2] == '#!': 102 # Check for non-Python scripts 103 words = line[2:].split() 104 if words and re.search('[pP]ython', words[0]) < 0: 105 msg = filename + ': ' + words[0] 106 msg = msg + ' script; not fixed\n' 107 err(msg) 108 f.close() 109 return 1 110 while line[-2:] == '\\\n': 111 nextline = f.readline() 112 if not nextline: break 113 line = line + nextline 114 lineno = lineno + 1 115 newline = fixline(line) 116 if newline != line: 117 if g is None: 118 try: 119 g = open(tempname, 'w') 120 except IOError, msg: 121 f.close() 122 err('%s: cannot create: %r\n' % (tempname, msg)) 123 return 1 124 f.seek(0) 125 lineno = 0 126 rep(filename + ':\n') 127 continue # restart from the beginning 128 rep(repr(lineno) + '\n') 129 rep('< ' + line) 130 rep('> ' + newline) 131 if g is not None: 132 g.write(newline) 133 134 # End of file 135 f.close() 136 if not g: return 0 # No changes 137 138 # Finishing touch -- move files 139 140 # First copy the file's mode to the temp file 141 try: 142 statbuf = os.stat(filename) 143 os.chmod(tempname, statbuf[ST_MODE] & 07777) 144 except os.error, msg: 145 err('%s: warning: chmod failed (%r)\n' % (tempname, msg)) 146 # Then make a backup of the original file as filename~ 147 try: 148 os.rename(filename, filename + '~') 149 except os.error, msg: 150 err('%s: warning: backup failed (%r)\n' % (filename, msg)) 151 # Now move the temp file to the original file 152 try: 153 os.rename(tempname, filename) 154 except os.error, msg: 155 err('%s: rename failed (%r)\n' % (filename, msg)) 156 return 1 157 # Return succes 158 return 0 159 160 161fixpat = '^[ \t]+def +[a-zA-Z0-9_]+ *( *self *, *(( *(.*) *)) *) *:' 162fixprog = re.compile(fixpat) 163 164def fixline(line): 165 if fixprog.match(line) >= 0: 166 (a, b), (c, d) = fixprog.regs[1:3] 167 line = line[:a] + line[c:d] + line[b:] 168 return line 169 170if __name__ == '__main__': 171 main() 172