1+ #! /ufs/guido/bin/sgi/python
12#! /usr/local/python
23
34# Fix Python source files to use the new class definition syntax,
1314# arguments). Of course, the original file is kept as a back-up
1415# (with a "~" attached to its name).
1516#
16- # Undoubtedly you can do this using find and sed, but this is
17+ # Changes made are reported to stdout in a diff-like format.
18+ #
19+ # Undoubtedly you can do this using find and sed or perl, but this is
1720# a nice example of Python code that recurses down a directory tree
1821# and uses regular expressions. Also note several subtleties like
1922# preserving the file's mode and avoiding to even write a temp file
2023# when no changes are needed for a file.
2124#
22- # Changes made are reported to stdout in a diff-like format.
25+ # NB: by changing only the function fixline() you can turn this
26+ # into a program for a different change to Python programs...
2327
2428import sys
25- import regexp
29+ import regex
2630import posix
2731import path
28- import string
2932from stat import *
3033
3134err = sys .stderr .write
3538def main ():
3639 bad = 0
3740 if not sys .argv [1 :]: # No arguments
38- err ('usage: classfix file-or-directory ...\n ' )
41+ err ('usage: ' + argv [ 0 ] + ' file-or-directory ...\n ' )
3942 sys .exit (2 )
4043 for arg in sys .argv [1 :]:
4144 if path .isdir (arg ):
@@ -47,7 +50,9 @@ def main():
4750 if fix (arg ): bad = 1
4851 sys .exit (bad )
4952
50- ispython = regexp .compile ('^[a-zA-Z0-9_]+\.py$' ).match # This is a method!
53+ ispythonprog = regex .compile ('^[a-zA-Z0-9_]+\.py$' )
54+ def ispython (name ):
55+ return ispythonprog .match (name ) >= 0
5156
5257def recursedown (dirname ):
5358 dbg ('recursedown(' + `dirname` + ')\n ' )
@@ -57,81 +62,66 @@ def recursedown(dirname):
5762 except posix .error , msg :
5863 err (dirname + ': cannot list directory: ' + `msg` + '\n ' )
5964 return 1
65+ names .sort ()
66+ subdirs = []
6067 for name in names :
6168 if name in ('.' , '..' ): continue
6269 fullname = path .join (dirname , name )
6370 if path .islink (fullname ): pass
6471 elif path .isdir (fullname ):
65- if recursedown (fullname ): bad = 1
72+ subdirs . append (fullname )
6673 elif ispython (name ):
6774 if fix (fullname ): bad = 1
75+ for fullname in subdirs :
76+ if recursedown (fullname ): bad = 1
6877 return bad
6978
70- # This expression doesn't catch *all* class definition headers,
71- # but it's darn pretty close.
72- classexpr = '^([ \t ]*class +[a-zA-Z0-9_]+) *\( *\) *((=.*)?):'
73- findclass = regexp .compile (classexpr ).match # This is a method!
74-
75- baseexpr = '^ *(.*) *\( *\) *$'
76- findbase = regexp .compile (baseexpr ).match # This is a method, too!
77-
7879def fix (filename ):
79- ## dbg('fix(' + `filename` + ')\n')
80+ dbg ('fix(' + `filename` + ')\n ' )
8081 try :
8182 f = open (filename , 'r' )
8283 except IOError , msg :
8384 err (filename + ': cannot open: ' + `msg` + '\n ' )
8485 return 1
8586 head , tail = path .split (filename )
8687 tempname = path .join (head , '@' + tail )
87- tf = None
88+ g = None
8889 # If we find a match, we rewind the file and start over but
8990 # now copy everything to a temp file.
91+ lineno = 0
9092 while 1 :
9193 line = f .readline ()
9294 if not line : break
93- res = findclass (line )
94- if not res :
95- if tf : tf .write (line )
96- continue
97- if not tf :
98- try :
99- tf = open (tempname , 'w' )
100- except IOError , msg :
101- f .close ()
102- err (tempname + ': cannot create: ' + `msg` + '\n ' )
103- return 1
104- rep (filename + ':\n ' )
105- # Rewind the input file and start all over:
106- f .seek (0 )
107- continue
108- a0 , b0 = res [0 ] # Whole match (up to ':')
109- a1 , b1 = res [1 ] # First subexpression (up to classname)
110- a2 , b2 = res [2 ] # Second subexpression (=.*)
111- head = line [:b1 ]
112- tail = line [b0 :] # Unmatched rest of line
113- if a2 = b2 : # No base classes -- easy case
114- newline = head + ':' + tail
115- else :
116- # Get rid of leading '='
117- basepart = line [a2 + 1 :b2 ]
118- # Extract list of base expressions
119- bases = string .splitfields (basepart , ',' )
120- # Strip trailing '()' from each base expression
121- for i in range (len (bases )):
122- res = findbase (bases [i ])
123- if res :
124- (x0 , y0 ), (x1 , y1 ) = res
125- bases [i ] = bases [i ][x1 :y1 ]
126- # Join the bases back again and build the new line
127- basepart = string .joinfields (bases , ', ' )
128- newline = head + '(' + basepart + '):' + tail
129- rep ('< ' + line )
130- rep ('> ' + newline )
131- tf .write (newline )
95+ lineno = lineno + 1
96+ while line [- 2 :] == '\\ \n ' :
97+ nextline = f .readline ()
98+ if not nextline : break
99+ line = line + nextline
100+ lineno = lineno + 1
101+ newline = fixline (line )
102+ if newline != line :
103+ if g is None :
104+ try :
105+ g = open (tempname , 'w' )
106+ except IOError , msg :
107+ f .close ()
108+ err (tempname + ': cannot create: ' + \
109+ `msg` + '\n ' )
110+ return 1
111+ f .seek (0 )
112+ lineno = 0
113+ rep (filename + ':\n ' )
114+ continue # restart from the beginning
115+ rep (`lineno` + '\n ' )
116+ rep ('< ' + line )
117+ rep ('> ' + newline )
118+ if g is not None :
119+ g .write (newline )
120+
121+ # End of file
132122 f .close ()
133- if not tf : return 0 # No changes
134-
123+ if not g : return 0 # No changes
124+
135125 # Finishing touch -- move files
136126
137127 # First copy the file's mode to the temp file
@@ -154,4 +144,46 @@ def fix(filename):
154144 # Return succes
155145 return 0
156146
147+ # This expression doesn't catch *all* class definition headers,
148+ # but it's pretty darn close.
149+ classexpr = '^\([ \t ]*class +[a-zA-Z0-9_]+\) *( *) *\(\(=.*\)?\):'
150+ classprog = regex .compile (classexpr )
151+
152+ # Expressions for finding base class expressions.
153+ baseexpr = '^ *\(.*\) *( *) *$'
154+ baseprog = regex .compile (baseexpr )
155+
156+ import string
157+
158+ def 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 = string .splitfields (basepart , ',' )
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 = string .joinfields (bases , ', ' )
186+
187+ return head + '(' + basepart + '):' + tail
188+
157189main ()
0 commit comments