Source code for hbutils.system.os.executable

"""
Overview:
    Some useful utils to locate the executable files.

    Based on `hweickert/where <https://github.com/hweickert/where>`_, \
    and this version below will be long-term maintained here.
"""
import itertools
import os
from typing import Iterable, Iterator, Optional, List

from deprecation import deprecated

from .type import is_windows
from ...config.meta import __VERSION__

__all__ = [
    'where', 'which',
]


[docs]def where(execfile: str) -> List[str]: """ Overview: Returns all matching file paths. :param execfile: Executable file to locate (such as ``python``). :return: The list of absolute paths of the executable files. Examples:: >>> from hbutils.system import where >>> >>> where('apt-get') ['/usr/bin/apt-get', '/bin/apt-get'] >>> where('bash') ['/usr/bin/bash', '/bin/bash'] >>> where('not_installed') [] """ return list(_iter_where(execfile))
[docs]@deprecated(deprecated_in="0.9", removed_in="1.0", current_version=__VERSION__, details="Use the native :func:`shutil.which` instead") def which(execfile: str) -> Optional[str]: """ Overview: Returns first matching file path, which is the one when we operate in terminal. :param execfile: Executable file to locate (such as ``python``). :return: Absolute path fo the executable file. Examples:: >>> from hbutils.system import which >>> >>> which('apt-get') '/usr/bin/apt-get' >>> which('bash') '/usr/bin/bash' >>> which('not_installed') None """ try: return next(_iter_where(execfile)) except StopIteration: return None
def _iter_where(filename: str) -> Iterator[str]: """ Like where() but returns an iterator. It is originally ``iwhere`` function, but now hidden because :func:`where` is the better choice than this. """ possible_paths = _gen_possible_matches(filename) existing_file_paths = filter(_is_executable, possible_paths) return existing_file_paths def _is_executable(filename: str) -> bool: """ Check if the file is an executable file, which should be * Exist (of course) * Is a file (not a directory) * Is executable (not a common file) """ return os.path.exists(filename) and os.path.isfile(filename) and os.access(filename, os.X_OK) # os.path.normcase and os.path.normpath is VERRRRRRRRY important here, # because the expression form of a path is actually not unique, especially on Windows. def _normpath(filename: str) -> str: return os.path.normcase(os.path.normpath(os.path.abspath(filename))) def _unique_str(siter: Iterable[str]) -> Iterator[str]: _exist_str = set() for s in siter: if s not in _exist_str: yield s def _gen_possible_matches(filename) -> Iterator[str]: path_parts = os.environ.get("PATH", "").split(os.pathsep) if is_windows(): # Only in Windows, the executable file in current directory can be called. path_parts = itertools.chain((os.curdir,), path_parts) possible_paths = map(lambda x: os.path.join(x, filename), path_parts) if is_windows(): possible_paths = itertools.chain( *map(lambda path: (path, f"{path}.bat", f"{path}.cmd", f"{path}.com", f"{path}.exe"), possible_paths)) return _unique_str(map(_normpath, possible_paths))