"""Interfaces to locate executable files on file system."""importitertoolsimportosfrompathlibimportPathfromxonsh.built_insimportXSHfromxonsh.lib.itertoolsimportunique_everseenfromxonsh.platformimportON_WINDOWS
[docs]defget_possible_names(name,env=None):"""Expand name to all possible variants based on `PATHEXT`. PATHEXT is a Windows convention containing extensions to be considered when searching for an executable file. Conserves order of any extensions found and gives precedence to the bare name. """env=envifenvisnotNoneelseXSH.envenv_pathext=env.get("PATHEXT",[])ifnotenv_pathext:return[name]upper=name.upper()==nameextensions=[""]+env_pathextreturn[name+(ext.upper()ifupperelseext.lower())forextinextensions]
[docs]defclear_paths(paths):"""Remove duplicates and nonexistent directories from paths."""returnfilter(os.path.isdir,unique_everseen(map(os.path.realpath,paths)))
[docs]defget_paths(env=None):"""Return tuple with deduplicated and existent paths from ``$PATH``."""env=envifenvisnotNoneelseXSH.envreturntuple(reversed(tuple(clear_paths(env.get("PATH")or[]))))
[docs]defis_executable_in_windows(filepath,env=None):"""Check the file is executable in Windows."""filepath=Path(filepath)try:try:ifnotfilepath.is_file():returnFalseexceptOSError:returnFalseenv=envifenvisnotNoneelseXSH.envreturnany(s.lower()==filepath.suffix.lower()forsinenv.get("PATHEXT",[]))exceptFileNotFoundError:# On Windows, there's no guarantee for the directory to really# exist even if isdir returns True. This may happen for instance# if the path contains trailing spaces.returnFalse
[docs]defis_executable_in_posix(filepath):"""Check the file is executable in POSIX."""try:returnfilepath.is_file()andos.access(filepath,os.X_OK)exceptOSError:# broken Symlink are neither dir not filespassreturnFalse
[docs]deflocate_executable(name,env=None):"""Search executable binary name in ``$PATH`` and return full path."""returnlocate_file(name,env=env,check_executable=True,use_pathext=True)
[docs]deflocate_file(name,env=None,check_executable=False,use_pathext=False):"""Search file name in the current working directory and in ``$PATH`` and return full path."""returnlocate_relative_path(name,env,check_executable,use_pathext)orlocate_file_in_path_env(name,env,check_executable,use_pathext)
[docs]deflocate_relative_path(name,env=None,check_executable=False,use_pathext=False):"""Return absolute path by relative file path. We should not locate files without prefix (e.g. ``"binfile"``) by security reasons like other shells. If directory has "binfile" it can be called only by providing prefix "./binfile" explicitly. """p=Path(name)ifname.startswith(("./","../",".\\","..\\","~/"))orp.is_absolute():possible_names=get_possible_names(p.name,env)ifuse_pathextelse[p.name]forpossible_nameinpossible_names:filepath=p.parent/possible_nametry:ifnotfilepath.is_file()or(check_executableandnotis_executable(filepath)):continuereturnstr(p.absolute())exceptPermissionError:continue
[docs]deflocate_file_in_path_env(name,env=None,check_executable=False,use_pathext=False):"""Search file name in ``$PATH`` and return full path. Compromise. There is no way to get case sensitive file name without listing all files. If the file name is ``CaMeL.exe`` and we found that ``camel.EXE`` exists there is no way to get back the case sensitive name. We don't want to read the list of files in all ``$PATH`` directories because of performance reasons. So we're ok to get existent but case insensitive (or different) result from resolver. May be in the future file systems as well as Python Path will be smarter to get the case sensitive name. The task for reading and returning case sensitive filename we give to completer in interactive mode with ``commands_cache``. """env=envifenvisnotNoneelseXSH.envenv_path=env.get("PATH",[])paths=tuple(clear_paths(env_path))possible_names=get_possible_names(name,env)ifuse_pathextelse[name]forpath,possible_nameinitertools.product(paths,possible_names):filepath=Path(path)/possible_nametry:ifnotfilepath.is_file()or(check_executableandnotis_executable(filepath)):continuereturnstr(filepath)exceptPermissionError:continue