Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 56 additions & 17 deletions Lib/test/test_tools/test_pathfix.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,51 +17,90 @@ def setUp(self):
self.temp_file = support.TESTFN
self.addCleanup(support.unlink, support.TESTFN)

def pathfix(self, shebang, pathfix_flags):
def pathfix(self, shebang, pathfix_flags, exitcode=0, stdout='', stderr=''):
with open(self.temp_file, 'w', encoding='utf8') as f:
f.write(f'{shebang}\n' + 'print("Hello world")\n')

proc = subprocess.run(
[sys.executable, self.script,
*pathfix_flags, '-n', self.temp_file],
capture_output=True)
self.assertEqual(proc.returncode, 0, proc)
capture_output=True, text=1)

if stdout == '' and proc.returncode == 0:
stdout = f'{self.temp_file}: updating\n'
self.assertEqual(proc.returncode, exitcode, proc)
self.assertEqual(proc.stdout, stdout, proc)
self.assertEqual(proc.stderr, stderr, proc)

with open(self.temp_file, 'r', encoding='utf8') as f:
output = f.read()
Comment thread
vstinner marked this conversation as resolved.

lines = output.split('\n')
self.assertEqual(lines[1:], ['print("Hello world")', ''])
shebang = lines[0]
return shebang
new_shebang = lines[0]

if proc.returncode != 0:
self.assertEqual(shebang, new_shebang)

return new_shebang

def test_pathfix(self):
self.assertEqual(
self.pathfix(
'#! /usr/bin/env python',
['-i', '/usr/bin/python3',]),
'#! /usr/bin/python3',
)
['-i', '/usr/bin/python3']),
'#! /usr/bin/python3')
self.assertEqual(
self.pathfix(
'#! /usr/bin/env python -R',
['-i', '/usr/bin/python3', ]),
'#! /usr/bin/python3',
)
['-i', '/usr/bin/python3']),
'#! /usr/bin/python3')

def test_pathfix_keeping_flags(self):
self.assertEqual(
self.pathfix(
'#! /usr/bin/env python -R',
['-i', '/usr/bin/python3', '-k',]),
'#! /usr/bin/python3 -R',
)
['-i', '/usr/bin/python3', '-k']),
'#! /usr/bin/python3 -R')
self.assertEqual(
self.pathfix(
'#! /usr/bin/env python',
['-i', '/usr/bin/python3', '-k',]),
'#! /usr/bin/python3',
)
['-i', '/usr/bin/python3', '-k']),
'#! /usr/bin/python3')

def test_pathfix_adding_flag(self):
self.assertEqual(
self.pathfix(
'#! /usr/bin/env python',
['-i', '/usr/bin/python3', '-a', 's']),
'#! /usr/bin/python3 -s')
self.assertEqual(
self.pathfix(
'#! /usr/bin/env python -S',
['-i', '/usr/bin/python3', '-a', 's']),
'#! /usr/bin/python3 -s')
self.assertEqual(
self.pathfix(
'#! /usr/bin/env python -V',
['-i', '/usr/bin/python3', '-a', 'v', '-k']),
'#! /usr/bin/python3 -vV')
self.assertEqual(
self.pathfix(
'#! /usr/bin/env python',
['-i', '/usr/bin/python3', '-a', 'Rs']),
'#! /usr/bin/python3 -Rs')
self.assertEqual(
self.pathfix(
'#! /usr/bin/env python -W default',
['-i', '/usr/bin/python3', '-a', 's', '-k']),
'#! /usr/bin/python3 -sW default')

def test_pathfix_adding_errors(self):
self.pathfix(
'#! /usr/bin/env python -E',
['-i', '/usr/bin/python3', '-a', 'W default', '-k'],
exitcode=2,
stderr="-a option doesn't support whitespaces")


if __name__ == '__main__':
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
Add flag -k to pathscript.py script: preserve shebang flags.
Add option -k to pathscript.py script: preserve shebang flags.
Add option -a to pathscript.py script: add flags.
35 changes: 30 additions & 5 deletions Tools/scripts/pathfix.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
# Sometimes you may find shebangs with flags such as `#! /usr/bin/env python -si`.
# Normally, pathfix overwrites the entire line, including the flags.
# To change interpreter and keep flags from the original shebang line, use -k.
# If you want to keep flags and add to them one single literal flag, use option -a.


# Undoubtedly you can do this using find and sed or perl, but this is
Expand All @@ -39,18 +40,20 @@
preserve_timestamps = False
create_backup = True
keep_flags = False
add_flags = b''


def main():
global new_interpreter
global preserve_timestamps
global create_backup
global keep_flags
global add_flags

usage = ('usage: %s -i /interpreter -p -n -k file-or-directory ...\n' %
usage = ('usage: %s -i /interpreter -p -n -k -a file-or-directory ...\n' %
sys.argv[0])
try:
opts, args = getopt.getopt(sys.argv[1:], 'i:kpn')
opts, args = getopt.getopt(sys.argv[1:], 'i:a:kpn')
except getopt.error as msg:
err(str(msg) + '\n')
err(usage)
Expand All @@ -64,6 +67,11 @@ def main():
create_backup = False
if o == '-k':
keep_flags = True
if o == '-a':
add_flags = a.encode()
if b' ' in add_flags:
err("-a option doesn't support whitespaces")
sys.exit(2)
if not new_interpreter or not new_interpreter.startswith(b'/') or \
not args:
err('-i option or file-or-directory missing\n')
Expand Down Expand Up @@ -188,15 +196,32 @@ def parse_shebang(shebangline):
return shebangline[start:]


def populate_flags(shebangline):
old_flags = b''
if keep_flags:
old_flags = parse_shebang(shebangline)
if old_flags:
old_flags = old_flags[2:]
if not (old_flags or add_flags):
return b''
# On Linux, the entire string following the interpreter name
# is passed as a single argument to the interpreter.
# e.g. "#! /usr/bin/python3 -W Error -s" runs "/usr/bin/python3 "-W Error -s"
# so shebang should have single '-' where flags are given and
# flag might need argument for that reasons adding new flags is
# between '-' and original flags
# e.g. #! /usr/bin/python3 -sW Error
return b' -' + add_flags + old_flags
Comment thread
vstinner marked this conversation as resolved.


def fixline(line):
if not line.startswith(b'#!'):
return line

if b"python" not in line:
return line
flags = b''
if keep_flags:
flags = parse_shebang(line)

flags = populate_flags(line)
return b'#! ' + new_interpreter + flags + b'\n'


Expand Down