#! /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 built in modules that are used. import getopt import inspect import os import string import sys from typing import Dict, List, Optional, Tuple # Add script directory to sys.path. # This is complicated due to the fact that __file__ is not always defined. # # Note: This must be done before importing pyWhich. def GetScriptFile() -> str: """Obtains the full path and file name of the Python script.""" if (hasattr(GetScriptFile, "file")): return GetScriptFile.file ret: str = "" try: # The easy way. Just use __file__. # Unfortunately, __file__ is not available when cx_freeze is used or in IDLE. ret = os.path.realpath(__file__) except NameError: # The hard way. if (len(sys.argv) > 0 and len(sys.argv[0]) > 0 and os.path.isabs(sys.argv[0])): ret = os.path.realpath(sys.argv[0]) else: ret = os.path.realpath(inspect.getfile(GetScriptFile)) if (not os.path.exists(ret)): # If cx_freeze is used the value of the ret variable at this point is in # the following format: {PathToExeFile}\{NameOfPythonSourceFile}. This # makes it necessary to strip off the file name to get the correct path. ret = os.path.dirname(ret) GetScriptFile.file: str = ret return GetScriptFile.file def GetScriptDirectory() -> str: """Obtains the path to the directory containing the script.""" if (hasattr(GetScriptDirectory, "dir")): return GetScriptDirectory.dir module_path: str = GetScriptFile() GetScriptDirectory.dir: str = os.path.dirname(module_path) return GetScriptDirectory.dir sys.path.insert(0, GetScriptDirectory()) import pyWhich try: import locale import gettext locale.setlocale(locale.LC_ALL, "") locale_dir: str = os.path.join(GetScriptDirectory(), "share", "locale") gettext.install("Python_which", locale_dir, unicode=1) except Exception: _ = lambda s: s __Python_Which_Version__: str = '3.1.0' __version__: str = '3.1.0' __doc__: str = _(""" Python which version %(version)s, Copyright 2004 - 2020 by Ben Key. Python which is essentially a clone of GNU which implemented in python. Shows the full path of (shell) commands. Usage: which.py [options] file_name [...] Options: --version, --VERSION, -v, -V Print version information and exit successfully. --help, --HELP, -h, -H, -? Print this help message and exit successfully. --skip-dot, --SKIP-DOT Skip directories in PATH that start with a dot. --skip-tilde, --SKIP-TILDE Skip directories in PATH that start with a tilde. --all, --ALL, -a, -A Process all matches in PATH, not just the first. --env, --ENV, -e, -E {ENV_VAR} Allows you to indicate that Python which should use the environment variable {ENV_VAR} to obtain the search path instead of using the 'PATH' environment variable. NOTE: This is a departure from GNU which. --search-path, --SEARCH-PATH, -s, -S {SEARCH_PATH} Allows you to indicate that Python which should use the specified {SEARCH_PATH}. If {SEARCH_PATH} contains the character %(path_separator)c it is interpreted as a delimited list of directories. Otherwise, it is interpreted as an environment variable from which a delimited list of directories may be obtained. NOTE: This is a departure from GNU which. --method, --METHOD Allows you to indicate that Python which should provide information on how the file was found, either by searching the path or by using the AppPaths registry key. NOTE: This is a departure from GNU which. --match-characteristics, --MATCH-CHARACTERISTICS Allows you to indicate that Python which should display the match characteristics for each file found. The match characteristics is a dictionary object containing the following fields. PathSearch ExtListSearch AppPathsSearch IsExecutable NOTE: This is a departure from GNU which. """) __version_info__: str = _(""" Python which version %(version)s, Copyright 2004 - 2020 by Ben Key. Python which is essentially a clone of GNU which implemented in python. Python which comes with ABSOLUTELY NO WARRANTY. This program is free software; your freedom to use, change and distribute this program is covered by the 2-Clause BSD License. """) __not_found_message__: str = _(""" no %(file_name)s in (%(search_path)s) """) # Global variables g_first_arg_index: int = 0 def set_first_arg_index(first_arg_index: int): global g_first_arg_index g_first_arg_index = first_arg_index def get_first_arg_index() -> int: global g_first_arg_index return g_first_arg_index class WhichCommandLineInfo: """A class that parses the command line arguments that are passed to this script.""" def __init__(self, argv: List[str] = None): """Constructor of the WhichCommandLineInfo class.""" self.__file_names: List[str] = [] self.__show_usage: bool = False self.__show_version: bool = False self.__find_all: bool = False self.__search_path: str = "PATH" self.__skip_dot: bool = False self.__skip_tilde: bool = False self.__show_method: bool = False self.__show_match_characteristics: bool = False self.__argv: Optional[List[str]] = argv if (None == argv): self.__argv = sys.argv self.__parse_cmd_line() def __parse_cmd_line(self): """Parses the command line options.""" parse_opts = True if (1 == len(self.__argv)): self.__show_usage = True elif (len(self.__argv) > 1): arg1 = self.__argv[1] arg1 = arg1.lower() if ( arg1 == "/?" or arg1 == "/h" or arg1 == "/help" ): self.__show_usage = True return try: opts, args = getopt.getopt( self.__argv[get_first_arg_index():], "hH?vVaAe:E:s:S:", [ "help", "HELP", "version", "VERSION", "all", "ALL", "env=", "ENV=", "skip-dot", "SKIP-DOT", "skip-tilde", "SKIP-TILDE", "search-path=", "SEARCH-PATH=", "method", "METHOD", "match-characteristics", "MATCH-CHARACTERISTICS" ]) except getopt.GetoptError as error: print(error.msg) self.__show_usage = True parse_opts = False if (parse_opts): for o, a in opts: if (o in ("-h", "-H", "-?", "--help", "--HELP")): self.__show_usage = True elif (o in ("-v", "-V", "--version", "--VERSION")): self.__show_version = True elif (o in ("-a", "-A", "--all", "--ALL")): self.__find_all = True elif (o in ("-e", "-E", "--env", "--ENV")): if (os.environ.get(a) != None): self.__search_path = a elif (o in ("-s", "-S", "--search-path", "--SEARCH-PATH")): self.__search_path = a elif (o in ("--skip-dot", "--SKIP-DOT")): self.__skip_dot = True elif (o in ("--skip-tilde", "--SKIP-TILDE")): self.__skip_tilde = True elif (o in ("--method", "--METHOD")): self.__show_method = True elif (o in ("--match-characteristics", "--MATCH-CHARACTERISTICS")): self.__show_match_characteristics = True for file in args: self.__file_names.append(file) def show_usage(self) -> bool: """Returns the value of the __show_usage member variable.""" return self.__show_usage def show_version(self) -> bool: """Returns the value of the __show_version member variable.""" return self.__show_version def find_all(self) -> bool: """Returns the value of the __find_all member variable.""" return self.__find_all def search_path(self) -> str: """Returns the value of the __search_path member variable.""" return self.__search_path def skip_dot(self) -> bool: """Returns the value of the __skip_dot member variable.""" return self.__skip_dot def skip_tilde(self) -> bool: """Returns the value of the __skip_tilde member variable.""" return self.__skip_tilde def show_method(self) -> bool: """Returns the value of the __show_method member variable.""" return self.__show_method def show_match_characteristics(self) -> bool: """Returns the value of the __show_match_characteristics member variable.""" return self.__show_match_characteristics def get_file_names(self) -> List[str]: """Returns the list of file names to search for.""" return self.__file_names def is_option_string(self, s: str) -> bool: """Returns True if the specified string is a option string. Returns False otherwise.""" test = s.lower() options = ["-a", "--all", "-e", "--env", "--skip-dot", "--skip-tilde"] if test in options: return True return False def usage(): """Prints the usage information for Python which.""" print(__doc__ % {'version' : __version__, 'path_separator' : os.pathsep}) def version(): """Prints the version information for Python which.""" print(__version_info__ % {'version' : __version__}) def not_found(file_name: str, search_path: str): """Prints the message that informs the user that the file they requested was not found.""" print(__not_found_message__ % { 'file_name' : file_name, 'search_path' : search_path }) def main(argv: List[str] = None) -> int: """The main entry point for Python which. Used when which.py is called non interactively to parse the command line arguments, call which where appropriate, and to print the results. """ if (argv == None): argv = sys.argv cmdLine = WhichCommandLineInfo(argv) if (cmdLine.show_usage()): usage() return 0 elif (cmdLine.show_version()): version() return 0 if (len(cmdLine.get_file_names()) == 0): return 1 ret_val: int = 0 for file_name in cmdLine.get_file_names(): if (cmdLine.is_option_string(file_name) == False): which = pyWhich.Which( file_name, cmdLine.find_all(), cmdLine.search_path(), cmdLine.skip_dot(), cmdLine.skip_tilde()) if (len(which.get_found_files()) == 0): ret_val = ret_val + 1 not_found(file_name, which.get_search_path()) else: for found in which.get_found_files(): foundPath: str = found[0] foundMatchCharacteristics = found[1] output: str = foundPath if (cmdLine.show_method()): foundMethod: str = '' if (foundMatchCharacteristics['PathSearch']): foundMethod = _('Path Search') elif (foundMatchCharacteristics['AppPathsSearch']): foundMethod = _('AppPaths Search') output += _(' [{method}]').format(method=foundMethod) if (cmdLine.show_match_characteristics()): if (not output.endswith(os.linesep)): output += os.linesep output += _(' {match}').format(match=foundMatchCharacteristics) print(output) return ret_val if (__name__ == "__main__"): set_first_arg_index(1) sys.exit(main(sys.argv))