Skip to content

bpo-37064: add option -k to Tools/scripts/pathfix.py #15548

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Sep 5, 2019
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
64 changes: 64 additions & 0 deletions Lib/test/test_tools/test_pathfix.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import os
import subprocess
import sys
import unittest
from test import support
from test.test_tools import import_tool, scriptsdir


class TestPathfixFunctional(unittest.TestCase):
script = os.path.join(scriptsdir, 'pathfix.py')

def setUp(self):
self.temp_file = support.TESTFN
self.addCleanup(support.unlink, support.TESTFN)

def pathfix(self, shebang, pathfix_flags):
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)

with open(self.temp_file, 'r', encoding='utf8') as f:
output = f.read()

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

def test_pathfix(self):
self.assertEqual(
self.pathfix(
'#! /usr/bin/env python',
['-i', '/usr/bin/python3',]),
'#! /usr/bin/python3',
)
self.assertEqual(
self.pathfix(
'#! /usr/bin/env python -R',
['-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',
)
self.assertEqual(
self.pathfix(
'#! /usr/bin/env python',
['-i', '/usr/bin/python3', '-k',]),
'#! /usr/bin/python3',
)


if __name__ == '__main__':
unittest.main()
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add flag -k to pathscript.py script: preserve shebang flags.
39 changes: 34 additions & 5 deletions Tools/scripts/pathfix.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env python3

# Change the #! line occurring in Python scripts. The new interpreter
# Change the #! line (shebang) occurring in Python scripts. The new interpreter
# pathname must be given with a -i option.
#
# Command line arguments are files or directories to be processed.
Expand All @@ -10,7 +10,12 @@
# arguments).
# The original file is kept as a back-up (with a "~" attached to its name),
# -n flag can be used to disable this.
#

# 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.


# Undoubtedly you can do this using find and sed or perl, but this is
# a nice example of Python code that recurses down a directory tree
# and uses regular expressions. Also note several subtleties like
Expand All @@ -33,16 +38,19 @@
new_interpreter = None
preserve_timestamps = False
create_backup = True
keep_flags = False


def main():
global new_interpreter
global preserve_timestamps
global create_backup
usage = ('usage: %s -i /interpreter -p -n file-or-directory ...\n' %
global keep_flags

usage = ('usage: %s -i /interpreter -p -n -k file-or-directory ...\n' %
sys.argv[0])
try:
opts, args = getopt.getopt(sys.argv[1:], 'i:pn')
opts, args = getopt.getopt(sys.argv[1:], 'i:kpn')
except getopt.error as msg:
err(str(msg) + '\n')
err(usage)
Expand All @@ -54,6 +62,8 @@ def main():
preserve_timestamps = True
if o == '-n':
create_backup = False
if o == '-k':
keep_flags = True
if not new_interpreter or not new_interpreter.startswith(b'/') or \
not args:
err('-i option or file-or-directory missing\n')
Expand All @@ -70,10 +80,14 @@ def main():
if fix(arg): bad = 1
sys.exit(bad)


ispythonprog = re.compile(r'^[a-zA-Z0-9_]+\.py$')


def ispython(name):
return bool(ispythonprog.match(name))


def recursedown(dirname):
dbg('recursedown(%r)\n' % (dirname,))
bad = 0
Expand All @@ -96,6 +110,7 @@ def recursedown(dirname):
if recursedown(fullname): bad = 1
return bad


def fix(filename):
## dbg('fix(%r)\n' % (filename,))
try:
Expand Down Expand Up @@ -164,12 +179,26 @@ def fix(filename):
# Return success
return 0


def parse_shebang(shebangline):
shebangline = shebangline.rstrip(b'\n')
start = shebangline.find(b' -')
if start == -1:
return b''
return shebangline[start:]


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

if b"python" not in line:
return line
return b'#! ' + new_interpreter + b'\n'
flags = b''
if keep_flags:
flags = parse_shebang(line)
return b'#! ' + new_interpreter + flags + b'\n'


if __name__ == '__main__':
main()