blob: b96abf4f45f4734543397037b7dfc399ddf25aaa [file] [log] [blame]
Guido van Rossum0af9a281992-01-01 18:38:21 +00001#! /ufs/guido/bin/sgi/python
2#! /usr/local/python
3
Guido van Rossum318a91c1992-01-01 19:22:25 +00004# Fix Python source files to use the new equality test operator, i.e.,
Guido van Rossum0af9a281992-01-01 18:38:21 +00005# if x = y: ...
Guido van Rossum318a91c1992-01-01 19:22:25 +00006# is changed to
7# if x == y: ...
8# The script correctly tokenizes the Python program to reliably
9# distinguish between assignments and equality tests.
Guido van Rossum0af9a281992-01-01 18:38:21 +000010#
11# Command line arguments are files or directories to be processed.
12# Directories are searched recursively for files whose name looks
13# like a python module.
14# Symbolic links are always ignored (except as explicit directory
15# arguments). Of course, the original file is kept as a back-up
16# (with a "~" attached to its name).
Guido van Rossumbecdad31992-03-02 16:18:31 +000017# It complains about binaries (files containing null bytes)
18# and about files that are ostensibly not Python files: if the first
19# line starts with '#!' and does not contain the string 'python'.
Guido van Rossum0af9a281992-01-01 18:38:21 +000020#
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 regex
34import posix
35import path
36from stat import *
Guido van Rossumbecdad31992-03-02 16:18:31 +000037import string
Guido van Rossum0af9a281992-01-01 18:38:21 +000038
39err = sys.stderr.write
40dbg = err
41rep = sys.stdout.write
42
43def main():
44 bad = 0
45 if not sys.argv[1:]: # No arguments
46 err('usage: ' + argv[0] + ' file-or-directory ...\n')
47 sys.exit(2)
48 for arg in sys.argv[1:]:
49 if path.isdir(arg):
50 if recursedown(arg): bad = 1
51 elif path.islink(arg):
52 err(arg + ': will not process symbolic links\n')
53 bad = 1
54 else:
55 if fix(arg): bad = 1
56 sys.exit(bad)
57
58ispythonprog = regex.compile('^[a-zA-Z0-9_]+\.py$')
59def ispython(name):
60 return ispythonprog.match(name) >= 0
61
62def recursedown(dirname):
63 dbg('recursedown(' + `dirname` + ')\n')
64 bad = 0
65 try:
66 names = posix.listdir(dirname)
67 except posix.error, msg:
68 err(dirname + ': cannot list directory: ' + `msg` + '\n')
69 return 1
70 names.sort()
71 subdirs = []
72 for name in names:
73 if name in ('.', '..'): continue
74 fullname = path.join(dirname, name)
75 if path.islink(fullname): pass
76 elif path.isdir(fullname):
77 subdirs.append(fullname)
78 elif ispython(name):
79 if fix(fullname): bad = 1
80 for fullname in subdirs:
81 if recursedown(fullname): bad = 1
82 return bad
83
84def fix(filename):
Guido van Rossum318a91c1992-01-01 19:22:25 +000085## dbg('fix(' + `filename` + ')\n')
Guido van Rossum0af9a281992-01-01 18:38:21 +000086 try:
87 f = open(filename, 'r')
88 except IOError, msg:
89 err(filename + ': cannot open: ' + `msg` + '\n')
90 return 1
91 head, tail = path.split(filename)
92 tempname = path.join(head, '@' + tail)
93 g = None
94 # If we find a match, we rewind the file and start over but
95 # now copy everything to a temp file.
96 lineno = 0
97 while 1:
98 line = f.readline()
99 if not line: break
100 lineno = lineno + 1
Guido van Rossumbecdad31992-03-02 16:18:31 +0000101 if g is None and '\0' in line:
102 # Check for binary files
103 err(filename + ': contains null bytes; not fixed\n')
104 f.close()
105 return 1
106 if lineno == 1 and g is None and line[:2] == '#!':
107 # Check for non-Python scripts
108 words = string.split(line[2:])
109 if words and regex.search('[pP]ython', words[0]) < 0:
110 msg = filename + ': ' + words[0]
111 msg = msg + ' script; not fixed\n'
112 err(msg)
113 f.close()
114 return 1
Guido van Rossum0af9a281992-01-01 18:38:21 +0000115 while line[-2:] == '\\\n':
116 nextline = f.readline()
117 if not nextline: break
118 line = line + nextline
119 lineno = lineno + 1
120 newline = fixline(line)
121 if newline != line:
122 if g is None:
123 try:
124 g = open(tempname, 'w')
125 except IOError, msg:
126 f.close()
127 err(tempname+': cannot create: '+\
128 `msg`+'\n')
129 return 1
130 f.seek(0)
131 lineno = 0
132 rep(filename + ':\n')
133 continue # restart from the beginning
134 rep(`lineno` + '\n')
135 rep('< ' + line)
136 rep('> ' + newline)
137 if g is not None:
138 g.write(newline)
139
140 # End of file
141 f.close()
142 if not g: return 0 # No changes
143
144 # Finishing touch -- move files
145
146 # First copy the file's mode to the temp file
147 try:
148 statbuf = posix.stat(filename)
149 posix.chmod(tempname, statbuf[ST_MODE] & 07777)
150 except posix.error, msg:
151 err(tempname + ': warning: chmod failed (' + `msg` + ')\n')
152 # Then make a backup of the original file as filename~
153 try:
154 posix.rename(filename, filename + '~')
155 except posix.error, msg:
156 err(filename + ': warning: backup failed (' + `msg` + ')\n')
157 # Now move the temp file to the original file
158 try:
159 posix.rename(tempname, filename)
160 except posix.error, msg:
161 err(filename + ': rename failed (' + `msg` + ')\n')
162 return 1
163 # Return succes
164 return 0
165
Guido van Rossum318a91c1992-01-01 19:22:25 +0000166
167from tokenize import tokenprog
168
169match = {'if':':', 'elif':':', 'while':':', 'return':'\n', \
170 '(':')', '[':']', '{':'}', '`':'`'}
Guido van Rossum0af9a281992-01-01 18:38:21 +0000171
172def fixline(line):
Guido van Rossum318a91c1992-01-01 19:22:25 +0000173 # Quick check for easy case
174 if '=' not in line: return line
175
176 i, n = 0, len(line)
177 stack = []
178 while i < n:
179 j = tokenprog.match(line, i)
180 if j < 0:
181 # A bad token; forget about the rest of this line
182 print '(Syntax error:)'
183 print line,
184 return line
185 a, b = tokenprog.regs[3] # Location of the token proper
186 token = line[a:b]
187 i = i+j
188 if stack and token == stack[-1]:
189 del stack[-1]
190 elif match.has_key(token):
191 stack.append(match[token])
192 elif token == '=' and stack:
193 line = line[:a] + '==' + line[b:]
194 i, n = a + len('=='), len(line)
195 elif token == '==' and not stack:
196 print '(Warning: \'==\' at top level:)'
197 print line,
Guido van Rossum0af9a281992-01-01 18:38:21 +0000198 return line
199
Guido van Rossum318a91c1992-01-01 19:22:25 +0000200
Guido van Rossum0af9a281992-01-01 18:38:21 +0000201main()