#! /usr/bin/env python # # Copyright 2004 - 2020 by Ben Key # # Redistribution and use in source and binary forms, with or without modification, # are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation and/or # other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED # WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO # EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT # OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING # IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY # OF SUCH DAMAGE. # # Import all the various modules that are used. import inspect import os import string import sys from typing import Dict, List, Optional, Tuple __pyWhich_Module_Version__: str = '3.1.0' __pyWhich_Module_Doc__: str = """ This module provides the class Which, which essentially provides a Python implementation of GNU Which. """ def expand_environment_strings(source: str) -> str: """Expands environment-variable strings and replaces them with the values defined for the current user. This function is only used on Microsoft Windows. It works by calling the ExpandEnvironmentStringsW Win32 API function via ctypes.""" if (not os.name.lower() == "nt"): return source try: import ctypes import ctypes.wintypes except ImportError: return source _ExpandEnvironmentStrings = ctypes.windll.kernel32.ExpandEnvironmentStringsW _ExpandEnvironmentStrings.argtypes = [ ctypes.wintypes.LPCWSTR, ctypes.wintypes.LPWSTR, ctypes.wintypes.DWORD ] _ExpandEnvironmentStrings.restype = ctypes.wintypes.DWORD bufferSize = 32768 buf = ctypes.create_unicode_buffer(bufferSize) result = _ExpandEnvironmentStrings(source, buf, bufferSize) if (result == 0 or result > bufferSize): return source else: return buf.value def get_long_path_name(path: str) -> str: """Converts the specified path to its long form. This function is only used on Microsoft Windows. It works by calling the GetLongPathNameW Win32 API function via ctypes.""" if (not os.name.lower() == "nt"): return path try: import ctypes import ctypes.wintypes except ImportError: return path _GetLongPathName = ctypes.windll.kernel32.GetLongPathNameW _GetLongPathName.argtypes = [ ctypes.wintypes.LPCWSTR, ctypes.wintypes.LPWSTR, ctypes.wintypes.DWORD ] _GetLongPathName.restype = ctypes.wintypes.DWORD bufferSize = ctypes.wintypes.MAX_PATH * 2 buf = ctypes.create_unicode_buffer(bufferSize) result = _GetLongPathName(path, buf, bufferSize) if (result == 0 or result > bufferSize): return path else: return buf.value class Which: """A class that may be used to locate a file in a PATH environment variable. Implements functionality similar to that of GNU which in Python.""" global __pyWhich_Module_Version__ __version__: str = __pyWhich_Module_Version__ def __init__( self, file_name: str, find_all: bool = False, search_path = "PATH", skip_dot: bool = False, skip_tilde: bool = False): """Constructor of the Which class.""" self.__module_dir: str = os.path.dirname(inspect.getfile(Which)) self.__found_files: List[Tuple[str, Dict[str, int]]] = [] self.__file_name: str = file_name self.__find_all: bool = find_all self.__paths: List[str] = [] self.__ext_list: List[str] = [] self.__skip_dot: bool = skip_dot self.__skip_tilde: bool = skip_tilde if (self.__init_paths(search_path) and self.__init_ext_list()): self.__find_files() def __init_paths(self, search_path) -> bool: """Initialize the paths member variable from using the value of the variable search_path. search_path may be a list of directories, a delimited string containing a list of directories, an environment variable from which a delimited list of directories may be obtained, or a single directory name. If the search path cannot be initialized from the search_path parameter, the PATH environment variable will be used.""" self.__search_path: str = "" if (isinstance(search_path, str)): if (search_path.find(os.pathsep) == -1): if (os.environ.get(search_path)): self.__search_path = os.environ.get(search_path) elif (os.path.isdir(search_path)): self.__search_path = search_path elif (search_path.lower() != "path"): self.__search_path = os.environ.get("PATH") else: self.__search_path = search_path if (self.__search_path != None): self.__paths = self.__search_path.split(os.pathsep) elif (isinstance(search_path, list) and isinstance(search_path[0], str)): self.__paths = search_path self.__search_path = os.pathsep.join(search_path) if (len(self.__paths) == 0): return False return True def __init_ext_list(self) -> bool: """Initialize the ext_list member variable. On systems that have the environment variable PATHEXT, the value of the environment variable will be used to set the initial value of the ext_list member variable. A post processing set will then be used to ensure that certain extensions are included in the list even if the PATHEXT environment variable does not exist.""" path_ext_env = os.environ.get("PATHEXT") if (path_ext_env != None): if (os.name.lower() == "nt"): path_ext_env = path_ext_env.lower() self.__ext_list = path_ext_env.split(os.pathsep) # Make certain that certain extensions are in the g_ext_list list. if (not ".exe" in self.__ext_list): if (os.name.lower() == "nt"): self.__ext_list.append(".exe") if (not ".pl" in self.__ext_list): self.__ext_list.append(".pl") if (not ".ps1" in self.__ext_list): self.__ext_list.append(".ps1") if (not ".py" in self.__ext_list): self.__ext_list.append(".py") if (not ".pyw" in self.__ext_list): self.__ext_list.append(".pyw") if (not ".sh" in self.__ext_list): self.__ext_list.append(".sh") if (not ".tcl" in self.__ext_list): self.__ext_list.append(".tcl") # If the file system for the current operating system is case # sensitive (it is NOT WinDoze) add the uppercase form of all # extensions to the extension list. if (os.name.lower() != "nt"): for i in range(len(self.__ext_list)): self.__ext_list.append(self.__ext_list[i].upper()) self.__ext_list.sort() if (len(self.__ext_list) == 0): return False return True def __find_files(self) -> bool: """Find file{s} in paths that match the file_name.""" if ( self.__file_name == None or not isinstance(self.__file_name, str) or len(self.__file_name) == 0 ): return False candidate: str = "" matchCharacteristics: Dict[str, int] = { 'PathSearch' : 0, 'ExtListSearch' : 0, 'AppPathsSearch' : 0, 'IsExecutable' : 0 } for dir in self.__paths: if (self.__skip_dot): dot_pos: int = dir.find('.') if (dot_pos > 0 and os.path.sep == dir[dot_pos - 1]): continue if (self.__skip_tilde): tilde_pos: int = dir.find('~') if (tilde_pos > 0 and os.path.sep == dir[tilde_pos - 1]): continue candidate = os.path.join(dir, self.__file_name) if (os.path.isfile(candidate)): matchCharacteristics['PathSearch'] = 1 if (os.access(candidate, os.X_OK)): matchCharacteristics['IsExecutable'] = 1 self.__found_files.append((candidate, matchCharacteristics)) if (self.__find_all == False): break if (self.__file_name.find('.') == -1): bContinue = True for ext in self.__ext_list: file_name_l: str = self.__file_name + ext candidate = os.path.join(dir, file_name_l) if (os.path.isfile(candidate)): matchCharacteristics['PathSearch'] = 1 matchCharacteristics['ExtListSearch'] = 1 if (os.access(candidate, os.X_OK)): matchCharacteristics['IsExecutable'] = 1 self.__found_files.append((candidate, matchCharacteristics)) if (self.__find_all == False): bContinue = False break if (bContinue == False): break if (len(self.__found_files) == 0 or self.__find_all == True): candidate = self.__find_using_AppPaths(self.__file_name) if (len(candidate) != 0 and os.path.isfile(candidate)): matchCharacteristics['AppPathsSearch'] = 1 if (os.access(candidate, os.X_OK)): matchCharacteristics['IsExecutable'] = 1 if (not candidate in self.__found_files): self.__found_files.append((candidate, matchCharacteristics)) elif (len(self.__ext_list) != 0 or self.__find_all == True): for ext in self.__ext_list: file_name_l = self.__file_name + ext candidate = self.__find_using_AppPaths(file_name_l) if (len(candidate) != 0 and os.path.isfile(candidate)): matchCharacteristics['AppPathsSearch'] = 1 matchCharacteristics['ExtListSearch'] = 1 if (os.access(candidate, os.X_OK)): matchCharacteristics['IsExecutable'] = 1 if (not candidate in self.__found_files): self.__found_files.append((candidate, matchCharacteristics)) if (self.__find_all == False): break if (len(self.__found_files) == 0): return False return True def __find_using_AppPaths(self, file_name: str): """On Microsoft Windows systems, searches for the file using the HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\ registry key.""" ret_val: str = "" if (file_name is None or not isinstance(file_name, str) or len(file_name) == 0): return ret_val if (os.name.lower() == "nt"): try: import winreg sub_key_name: str = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\" + file_name key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, sub_key_name) if not key is None: reg_value, reg_type = winreg.QueryValueEx(key, None) winreg.CloseKey(key) if not reg_value is None: ret_val = get_long_path_name( os.path.realpath( expand_environment_strings(reg_value).strip('"'))) except ImportError: pass except winreg.error: pass return ret_val def get_found_files(self) -> List[Tuple[str, Dict[str, int]]]: """Returns the found files list.""" return self.__found_files def get_search_path(self) -> str: """Returns the value of the __search_path member variable.""" return self.__search_path def which(file_name: str) -> str: """A simple wrapper function around the Which class. Searches the directories listed in the PATH environment variable for the file file_name. If a match is found, the function returns the full path to the first found file. If no match is found, the function returns an empty string.""" ret_val: str = "" myWhich = Which(file_name) if (len(myWhich.get_found_files()) != 0): ret_val = myWhich.get_found_files()[0][0] return ret_val