Skip to content

Commit 50254ac

Browse files
PatrikKopkanvstinner
authored andcommitted
bpo-37064: Add option -k to Tools/scripts/pathfix.py (GH-15548)
Add flag -k to pathscript.py script: preserve shebang flags.
1 parent 3038e87 commit 50254ac

File tree

3 files changed

+99
-5
lines changed

3 files changed

+99
-5
lines changed

Lib/test/test_tools/test_pathfix.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import os
2+
import subprocess
3+
import sys
4+
import unittest
5+
from test import support
6+
from test.test_tools import import_tool, scriptsdir
7+
8+
9+
class TestPathfixFunctional(unittest.TestCase):
10+
script = os.path.join(scriptsdir, 'pathfix.py')
11+
12+
def setUp(self):
13+
self.temp_file = support.TESTFN
14+
self.addCleanup(support.unlink, support.TESTFN)
15+
16+
def pathfix(self, shebang, pathfix_flags):
17+
with open(self.temp_file, 'w', encoding='utf8') as f:
18+
f.write(f'{shebang}\n' + 'print("Hello world")\n')
19+
20+
proc = subprocess.run(
21+
[sys.executable, self.script,
22+
*pathfix_flags, '-n', self.temp_file],
23+
capture_output=True)
24+
self.assertEqual(proc.returncode, 0, proc)
25+
26+
with open(self.temp_file, 'r', encoding='utf8') as f:
27+
output = f.read()
28+
29+
lines = output.split('\n')
30+
self.assertEqual(lines[1:], ['print("Hello world")', ''])
31+
shebang = lines[0]
32+
return shebang
33+
34+
def test_pathfix(self):
35+
self.assertEqual(
36+
self.pathfix(
37+
'#! /usr/bin/env python',
38+
['-i', '/usr/bin/python3',]),
39+
'#! /usr/bin/python3',
40+
)
41+
self.assertEqual(
42+
self.pathfix(
43+
'#! /usr/bin/env python -R',
44+
['-i', '/usr/bin/python3', ]),
45+
'#! /usr/bin/python3',
46+
)
47+
48+
def test_pathfix_keeping_flags(self):
49+
self.assertEqual(
50+
self.pathfix(
51+
'#! /usr/bin/env python -R',
52+
['-i', '/usr/bin/python3', '-k',]),
53+
'#! /usr/bin/python3 -R',
54+
)
55+
self.assertEqual(
56+
self.pathfix(
57+
'#! /usr/bin/env python',
58+
['-i', '/usr/bin/python3', '-k',]),
59+
'#! /usr/bin/python3',
60+
)
61+
62+
63+
if __name__ == '__main__':
64+
unittest.main()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add flag -k to pathscript.py script: preserve shebang flags.

Tools/scripts/pathfix.py

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#!/usr/bin/env python3
22

3-
# Change the #! line occurring in Python scripts. The new interpreter
3+
# Change the #! line (shebang) occurring in Python scripts. The new interpreter
44
# pathname must be given with a -i option.
55
#
66
# Command line arguments are files or directories to be processed.
@@ -10,7 +10,12 @@
1010
# arguments).
1111
# The original file is kept as a back-up (with a "~" attached to its name),
1212
# -n flag can be used to disable this.
13-
#
13+
14+
# Sometimes you may find shebangs with flags such as `#! /usr/bin/env python -si`.
15+
# Normally, pathfix overwrites the entire line, including the flags.
16+
# To change interpreter and keep flags from the original shebang line, use -k.
17+
18+
1419
# Undoubtedly you can do this using find and sed or perl, but this is
1520
# a nice example of Python code that recurses down a directory tree
1621
# and uses regular expressions. Also note several subtleties like
@@ -33,16 +38,19 @@
3338
new_interpreter = None
3439
preserve_timestamps = False
3540
create_backup = True
41+
keep_flags = False
3642

3743

3844
def main():
3945
global new_interpreter
4046
global preserve_timestamps
4147
global create_backup
42-
usage = ('usage: %s -i /interpreter -p -n file-or-directory ...\n' %
48+
global keep_flags
49+
50+
usage = ('usage: %s -i /interpreter -p -n -k file-or-directory ...\n' %
4351
sys.argv[0])
4452
try:
45-
opts, args = getopt.getopt(sys.argv[1:], 'i:pn')
53+
opts, args = getopt.getopt(sys.argv[1:], 'i:kpn')
4654
except getopt.error as msg:
4755
err(str(msg) + '\n')
4856
err(usage)
@@ -54,6 +62,8 @@ def main():
5462
preserve_timestamps = True
5563
if o == '-n':
5664
create_backup = False
65+
if o == '-k':
66+
keep_flags = True
5767
if not new_interpreter or not new_interpreter.startswith(b'/') or \
5868
not args:
5969
err('-i option or file-or-directory missing\n')
@@ -70,10 +80,14 @@ def main():
7080
if fix(arg): bad = 1
7181
sys.exit(bad)
7282

83+
7384
ispythonprog = re.compile(r'^[a-zA-Z0-9_]+\.py$')
85+
86+
7487
def ispython(name):
7588
return bool(ispythonprog.match(name))
7689

90+
7791
def recursedown(dirname):
7892
dbg('recursedown(%r)\n' % (dirname,))
7993
bad = 0
@@ -96,6 +110,7 @@ def recursedown(dirname):
96110
if recursedown(fullname): bad = 1
97111
return bad
98112

113+
99114
def fix(filename):
100115
## dbg('fix(%r)\n' % (filename,))
101116
try:
@@ -164,12 +179,26 @@ def fix(filename):
164179
# Return success
165180
return 0
166181

182+
183+
def parse_shebang(shebangline):
184+
shebangline = shebangline.rstrip(b'\n')
185+
start = shebangline.find(b' -')
186+
if start == -1:
187+
return b''
188+
return shebangline[start:]
189+
190+
167191
def fixline(line):
168192
if not line.startswith(b'#!'):
169193
return line
194+
170195
if b"python" not in line:
171196
return line
172-
return b'#! ' + new_interpreter + b'\n'
197+
flags = b''
198+
if keep_flags:
199+
flags = parse_shebang(line)
200+
return b'#! ' + new_interpreter + flags + b'\n'
201+
173202

174203
if __name__ == '__main__':
175204
main()

0 commit comments

Comments
 (0)