|
| 1 | +# -*- coding: utf-8 -*- |
| 2 | + |
| 3 | +__author__ = "Daniel Roy Greenfeld" |
| 4 | + |
| 5 | +__version__ = "0.6.1" |
| 6 | + |
| 7 | +import os |
| 8 | +import sys |
| 9 | + |
| 10 | +try: # Forced testing |
| 11 | + from shutil import which |
| 12 | +except ImportError: # Forced testing |
| 13 | + # Versions prior to Python 3.3 don't have shutil.which |
| 14 | + |
| 15 | + def which(cmd, mode=os.F_OK | os.X_OK, path=None): |
| 16 | + """Given a command, mode, and a PATH string, return the path which |
| 17 | + conforms to the given mode on the PATH, or None if there is no such |
| 18 | + file. |
| 19 | + `mode` defaults to os.F_OK | os.X_OK. `path` defaults to the result |
| 20 | + of os.environ.get("PATH"), or can be overridden with a custom search |
| 21 | + path. |
| 22 | + Note: This function was backported from the Python 3 source code. |
| 23 | + """ |
| 24 | + # Check that a given file can be accessed with the correct mode. |
| 25 | + # Additionally check that `file` is not a directory, as on Windows |
| 26 | + # directories pass the os.access check. |
| 27 | + |
| 28 | + def _access_check(fn, mode): |
| 29 | + return os.path.exists(fn) and os.access(fn, mode) and not os.path.isdir(fn) |
| 30 | + |
| 31 | + # If we're given a path with a directory part, look it up directly |
| 32 | + # rather than referring to PATH directories. This includes checking |
| 33 | + # relative to the current directory, e.g. ./script |
| 34 | + if os.path.dirname(cmd): |
| 35 | + if _access_check(cmd, mode): |
| 36 | + return cmd |
| 37 | + |
| 38 | + return None |
| 39 | + |
| 40 | + if path is None: |
| 41 | + path = os.environ.get("PATH", os.defpath) |
| 42 | + if not path: |
| 43 | + return None |
| 44 | + |
| 45 | + path = path.split(os.pathsep) |
| 46 | + |
| 47 | + if sys.platform == "win32": |
| 48 | + # The current directory takes precedence on Windows. |
| 49 | + if os.curdir not in path: |
| 50 | + path.insert(0, os.curdir) |
| 51 | + |
| 52 | + # PATHEXT is necessary to check on Windows. |
| 53 | + pathext = os.environ.get("PATHEXT", "").split(os.pathsep) |
| 54 | + # See if the given file matches any of the expected path |
| 55 | + # extensions. This will allow us to short circuit when given |
| 56 | + # "python.exe". If it does match, only test that one, otherwise we |
| 57 | + # have to try others. |
| 58 | + if any(cmd.lower().endswith(ext.lower()) for ext in pathext): |
| 59 | + files = [cmd] |
| 60 | + else: |
| 61 | + files = [cmd + ext for ext in pathext] |
| 62 | + else: |
| 63 | + # On other platforms you don't have things like PATHEXT to tell you |
| 64 | + # what file suffixes are executable, so just pass on cmd as-is. |
| 65 | + files = [cmd] |
| 66 | + |
| 67 | + seen = set() |
| 68 | + for dir in path: |
| 69 | + normdir = os.path.normcase(dir) |
| 70 | + if normdir not in seen: |
| 71 | + seen.add(normdir) |
| 72 | + for thefile in files: |
| 73 | + name = os.path.join(dir, thefile) |
| 74 | + if _access_check(name, mode): |
| 75 | + return name |
| 76 | + |
| 77 | + return None |
0 commit comments