Files
pre-commit/pre_commit/parse_shebang.py
Alex Hutton 42000c4521 Fix eslint on windows
- The bare exe was the first filename attempted to match,
  this changes means it will be matched last, allowing other files
  to be matched if they exist. The result is that eslint now works
  on Windows.
2017-02-10 18:09:00 +11:00

101 lines
2.5 KiB
Python

from __future__ import absolute_import
from __future__ import unicode_literals
import io
import os.path
import shlex
import string
printable = frozenset(string.printable)
class ExecutableNotFoundError(OSError):
def to_output(self):
return (1, self.args[0].encode('UTF-8'), b'')
def parse_bytesio(bytesio):
"""Parse the shebang from a file opened for reading binary."""
if bytesio.read(2) != b'#!':
return ()
first_line = bytesio.readline()
try:
first_line = first_line.decode('US-ASCII')
except UnicodeDecodeError:
return ()
# Require only printable ascii
for c in first_line:
if c not in printable:
return ()
cmd = tuple(shlex.split(first_line))
if cmd[0] == '/usr/bin/env':
cmd = cmd[1:]
return cmd
def parse_filename(filename):
"""Parse the shebang given a filename."""
if not os.path.exists(filename) or not os.access(filename, os.X_OK):
return ()
with io.open(filename, 'rb') as f:
return parse_bytesio(f)
def find_executable(exe, _environ=None):
exe = os.path.normpath(exe)
if os.sep in exe:
return exe
environ = _environ if _environ is not None else os.environ
if 'PATHEXT' in environ:
possible_exe_names = tuple(
exe + ext.lower() for ext in environ['PATHEXT'].split(os.pathsep)
) + (exe,)
else:
possible_exe_names = (exe,)
for path in environ.get('PATH', '').split(os.pathsep):
for possible_exe_name in possible_exe_names:
joined = os.path.join(path, possible_exe_name)
if os.path.isfile(joined) and os.access(joined, os.X_OK):
return joined
else:
return None
def normexe(orig_exe):
if os.sep not in orig_exe:
exe = find_executable(orig_exe)
if exe is None:
raise ExecutableNotFoundError(
'Executable `{}` not found'.format(orig_exe),
)
return exe
else:
return orig_exe
def normalize_cmd(cmd):
"""Fixes for the following issues on windows
- http://bugs.python.org/issue8557
- windows does not parse shebangs
This function also makes deep-path shebangs work just fine
"""
# Use PATH to determine the executable
exe = normexe(cmd[0])
# Figure out the shebang from the resulting command
cmd = parse_filename(exe) + (exe,) + cmd[1:]
# This could have given us back another bare executable
exe = normexe(cmd[0])
return (exe,) + cmd[1:]