3 # Change the #! line occurring in Python scripts. The new interpreter
4 # pathname must be given with a -i option.
6 # Command line arguments are files or directories to be processed.
7 # Directories are searched recursively for files whose name looks
8 # like a python module.
9 # Symbolic links are always ignored (except as explicit directory
10 # arguments). Of course, the original file is kept as a back-up
11 # (with a "~" attached to its name).
13 # Undoubtedly you can do this using find and sed or perl, but this is
14 # a nice example of Python code that recurses down a directory tree
15 # and uses regular expressions. Also note several subtleties like
16 # preserving the file's mode and avoiding to even write a temp file
17 # when no changes are needed for a file.
19 # NB: by changing only the function fixfile() you can turn this
20 # into a program for a different change to Python programs...
28 err
= sys
.stderr
.write
30 rep
= sys
.stdout
.write
32 new_interpreter
= None
35 global new_interpreter
36 usage
= ('usage: %s -i /interpreter file-or-directory ...\n' %
39 opts
, args
= getopt
.getopt(sys
.argv
[1:], 'i:')
40 except getopt
.error
as msg
:
46 new_interpreter
= a
.encode()
47 if not new_interpreter
or not new_interpreter
.startswith(b
'/') or \
49 err('-i option or file-or-directory missing\n')
54 if os
.path
.isdir(arg
):
55 if recursedown(arg
): bad
= 1
56 elif os
.path
.islink(arg
):
57 err(arg
+ ': will not process symbolic links\n')
63 ispythonprog
= re
.compile('^[a-zA-Z0-9_]+\.py$')
65 return bool(ispythonprog
.match(name
))
67 def recursedown(dirname
):
68 dbg('recursedown(%r)\n' % (dirname
,))
71 names
= os
.listdir(dirname
)
72 except os
.error
as msg
:
73 err('%s: cannot list directory: %r\n' % (dirname
, msg
))
78 if name
in (os
.curdir
, os
.pardir
): continue
79 fullname
= os
.path
.join(dirname
, name
)
80 if os
.path
.islink(fullname
): pass
81 elif os
.path
.isdir(fullname
):
82 subdirs
.append(fullname
)
84 if fix(fullname
): bad
= 1
85 for fullname
in subdirs
:
86 if recursedown(fullname
): bad
= 1
90 ## dbg('fix(%r)\n' % (filename,))
92 f
= open(filename
, 'rb')
93 except IOError as msg
:
94 err('%s: cannot open: %r\n' % (filename
, msg
))
99 rep(filename
+': no change\n')
102 head
, tail
= os
.path
.split(filename
)
103 tempname
= os
.path
.join(head
, '@' + tail
)
105 g
= open(tempname
, 'wb')
106 except IOError as msg
:
108 err('%s: cannot create: %r\n' % (tempname
, msg
))
110 rep(filename
+ ': updating\n')
114 buf
= f
.read(BUFSIZE
)
120 # Finishing touch -- move files
122 # First copy the file's mode to the temp file
124 statbuf
= os
.stat(filename
)
125 os
.chmod(tempname
, statbuf
[ST_MODE
] & 0o7777)
126 except os
.error
as msg
:
127 err('%s: warning: chmod failed (%r)\n' % (tempname
, msg
))
128 # Then make a backup of the original file as filename~
130 os
.rename(filename
, filename
+ '~')
131 except os
.error
as msg
:
132 err('%s: warning: backup failed (%r)\n' % (filename
, msg
))
133 # Now move the temp file to the original file
135 os
.rename(tempname
, filename
)
136 except os
.error
as msg
:
137 err('%s: rename failed (%r)\n' % (filename
, msg
))
143 if not line
.startswith(b
'#!'):
145 if b
"python" not in line
:
147 return b
'#! ' + new_interpreter
+ b
'\n'
149 if __name__
== '__main__':