|
1 #! /usr/bin/env python |
|
2 |
|
3 # Change the #! line occurring in Python scripts. The new interpreter |
|
4 # pathname must be given with a -i option. |
|
5 # |
|
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). |
|
12 # |
|
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. |
|
18 # |
|
19 # NB: by changing only the function fixfile() you can turn this |
|
20 # into a program for a different change to Python programs... |
|
21 |
|
22 import sys |
|
23 import re |
|
24 import os |
|
25 from stat import * |
|
26 import getopt |
|
27 |
|
28 err = sys.stderr.write |
|
29 dbg = err |
|
30 rep = sys.stdout.write |
|
31 |
|
32 new_interpreter = None |
|
33 |
|
34 def main(): |
|
35 global new_interpreter |
|
36 usage = ('usage: %s -i /interpreter file-or-directory ...\n' % |
|
37 sys.argv[0]) |
|
38 try: |
|
39 opts, args = getopt.getopt(sys.argv[1:], 'i:') |
|
40 except getopt.error, msg: |
|
41 err(msg + '\n') |
|
42 err(usage) |
|
43 sys.exit(2) |
|
44 for o, a in opts: |
|
45 if o == '-i': |
|
46 new_interpreter = a |
|
47 if not new_interpreter or new_interpreter[0] != '/' or not args: |
|
48 err('-i option or file-or-directory missing\n') |
|
49 err(usage) |
|
50 sys.exit(2) |
|
51 bad = 0 |
|
52 for arg in args: |
|
53 if os.path.isdir(arg): |
|
54 if recursedown(arg): bad = 1 |
|
55 elif os.path.islink(arg): |
|
56 err(arg + ': will not process symbolic links\n') |
|
57 bad = 1 |
|
58 else: |
|
59 if fix(arg): bad = 1 |
|
60 sys.exit(bad) |
|
61 |
|
62 ispythonprog = re.compile('^[a-zA-Z0-9_]+\.py$') |
|
63 def ispython(name): |
|
64 return ispythonprog.match(name) >= 0 |
|
65 |
|
66 def recursedown(dirname): |
|
67 dbg('recursedown(%r)\n' % (dirname,)) |
|
68 bad = 0 |
|
69 try: |
|
70 names = os.listdir(dirname) |
|
71 except os.error, msg: |
|
72 err('%s: cannot list directory: %r\n' % (dirname, msg)) |
|
73 return 1 |
|
74 names.sort() |
|
75 subdirs = [] |
|
76 for name in names: |
|
77 if name in (os.curdir, os.pardir): continue |
|
78 fullname = os.path.join(dirname, name) |
|
79 if os.path.islink(fullname): pass |
|
80 elif os.path.isdir(fullname): |
|
81 subdirs.append(fullname) |
|
82 elif ispython(name): |
|
83 if fix(fullname): bad = 1 |
|
84 for fullname in subdirs: |
|
85 if recursedown(fullname): bad = 1 |
|
86 return bad |
|
87 |
|
88 def fix(filename): |
|
89 ## dbg('fix(%r)\n' % (filename,)) |
|
90 try: |
|
91 f = open(filename, 'r') |
|
92 except IOError, msg: |
|
93 err('%s: cannot open: %r\n' % (filename, msg)) |
|
94 return 1 |
|
95 line = f.readline() |
|
96 fixed = fixline(line) |
|
97 if line == fixed: |
|
98 rep(filename+': no change\n') |
|
99 f.close() |
|
100 return |
|
101 head, tail = os.path.split(filename) |
|
102 tempname = os.path.join(head, '@' + tail) |
|
103 try: |
|
104 g = open(tempname, 'w') |
|
105 except IOError, msg: |
|
106 f.close() |
|
107 err('%s: cannot create: %r\n' % (tempname, msg)) |
|
108 return 1 |
|
109 rep(filename + ': updating\n') |
|
110 g.write(fixed) |
|
111 BUFSIZE = 8*1024 |
|
112 while 1: |
|
113 buf = f.read(BUFSIZE) |
|
114 if not buf: break |
|
115 g.write(buf) |
|
116 g.close() |
|
117 f.close() |
|
118 |
|
119 # Finishing touch -- move files |
|
120 |
|
121 # First copy the file's mode to the temp file |
|
122 try: |
|
123 statbuf = os.stat(filename) |
|
124 os.chmod(tempname, statbuf[ST_MODE] & 07777) |
|
125 except os.error, msg: |
|
126 err('%s: warning: chmod failed (%r)\n' % (tempname, msg)) |
|
127 # Then make a backup of the original file as filename~ |
|
128 try: |
|
129 os.rename(filename, filename + '~') |
|
130 except os.error, msg: |
|
131 err('%s: warning: backup failed (%r)\n' % (filename, msg)) |
|
132 # Now move the temp file to the original file |
|
133 try: |
|
134 os.rename(tempname, filename) |
|
135 except os.error, msg: |
|
136 err('%s: rename failed (%r)\n' % (filename, msg)) |
|
137 return 1 |
|
138 # Return succes |
|
139 return 0 |
|
140 |
|
141 def fixline(line): |
|
142 if not line.startswith('#!'): |
|
143 return line |
|
144 if "python" not in line: |
|
145 return line |
|
146 return '#! %s\n' % new_interpreter |
|
147 |
|
148 if __name__ == '__main__': |
|
149 main() |