summaryrefslogtreecommitdiffstats
path: root/pre_commit/parse_shebang.py
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2020-03-24 21:59:15 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2020-03-24 21:59:15 +0000
commit63fad53303381388673073de580a32088a4ef0fe (patch)
treea2c5c329ee5e79a220fac7e079283235fecc0cda /pre_commit/parse_shebang.py
parentInitial commit. (diff)
downloadpre-commit-63fad53303381388673073de580a32088a4ef0fe.tar.xz
pre-commit-63fad53303381388673073de580a32088a4ef0fe.zip
Adding upstream version 2.2.0.upstream/2.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'pre_commit/parse_shebang.py')
-rw-r--r--pre_commit/parse_shebang.py84
1 files changed, 84 insertions, 0 deletions
diff --git a/pre_commit/parse_shebang.py b/pre_commit/parse_shebang.py
new file mode 100644
index 0000000..d344a1d
--- /dev/null
+++ b/pre_commit/parse_shebang.py
@@ -0,0 +1,84 @@
+import os.path
+from typing import Mapping
+from typing import Optional
+from typing import Tuple
+from typing import TYPE_CHECKING
+
+from identify.identify import parse_shebang_from_file
+
+if TYPE_CHECKING:
+ from typing import NoReturn
+
+
+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, _environ: Optional[Mapping[str, str]] = None,
+) -> Optional[str]:
+ 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:
+ 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) -> 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)
+ 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, ...]) -> 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])
+
+ # 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:]