diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 18:05:20 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 18:05:20 +0000 |
commit | c86df75ab11643fa4649cfe6ed5c4692d4ee342b (patch) | |
tree | de847f47ec2669e74b9a3459319579346b7c99df /pre_commit/parse_shebang.py | |
parent | Initial commit. (diff) | |
download | pre-commit-upstream/3.6.2.tar.xz pre-commit-upstream/3.6.2.zip |
Adding upstream version 3.6.2.upstream/3.6.2
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | pre_commit/parse_shebang.py | 85 |
1 files changed, 85 insertions, 0 deletions
diff --git a/pre_commit/parse_shebang.py b/pre_commit/parse_shebang.py new file mode 100644 index 0000000..043a9b5 --- /dev/null +++ b/pre_commit/parse_shebang.py @@ -0,0 +1,85 @@ +from __future__ import annotations + +import os.path +from collections.abc import Mapping +from typing import NoReturn + +from identify.identify import parse_shebang_from_file + + +class ExecutableNotFoundError(OSError): + def to_output(self) -> tuple[int, bytes, None]: + return (1, self.args[0].encode(), None) + + +def parse_filename(filename: str) -> tuple[str, ...]: + if not os.path.exists(filename): + return () + else: + return parse_shebang_from_file(filename) + + +def find_executable( + exe: str, *, env: Mapping[str, str] | None = None, +) -> str | None: + exe = os.path.normpath(exe) + if os.sep in exe: + return exe + + environ = env if env is not None else os.environ + + if 'PATHEXT' in environ: + exts = environ['PATHEXT'].split(os.pathsep) + possible_exe_names = tuple(f'{exe}{ext}' for ext in exts) + (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: str, *, env: Mapping[str, str] | None = None) -> str: + def _error(msg: str) -> NoReturn: + raise ExecutableNotFoundError(f'Executable `{orig}` {msg}') + + if os.sep not in orig and (not os.altsep or os.altsep not in orig): + exe = find_executable(orig, env=env) + if exe is None: + _error('not found') + return exe + elif os.path.isdir(orig): + _error('is a directory') + elif not os.path.isfile(orig): + _error('not found') + elif not os.access(orig, os.X_OK): # pragma: win32 no cover + _error('is not executable') + else: + return orig + + +def normalize_cmd( + cmd: tuple[str, ...], + *, + env: Mapping[str, str] | None = None, +) -> tuple[str, ...]: + """Fixes for the following issues on windows + - https://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], env=env) + + # 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], env=env) + + return (exe,) + cmd[1:] |