diff options
Diffstat (limited to 'python/which/which.py')
-rw-r--r-- | python/which/which.py | 335 |
1 files changed, 335 insertions, 0 deletions
diff --git a/python/which/which.py b/python/which/which.py new file mode 100644 index 000000000..8dd790518 --- /dev/null +++ b/python/which/which.py @@ -0,0 +1,335 @@ +#!/usr/bin/env python +# Copyright (c) 2002-2005 ActiveState Corp. +# See LICENSE.txt for license details. +# Author: +# Trent Mick (TrentM@ActiveState.com) +# Home: +# http://trentm.com/projects/which/ + +r"""Find the full path to commands. + +which(command, path=None, verbose=0, exts=None) + Return the full path to the first match of the given command on the + path. + +whichall(command, path=None, verbose=0, exts=None) + Return a list of full paths to all matches of the given command on + the path. + +whichgen(command, path=None, verbose=0, exts=None) + Return a generator which will yield full paths to all matches of the + given command on the path. + +By default the PATH environment variable is searched (as well as, on +Windows, the AppPaths key in the registry), but a specific 'path' list +to search may be specified as well. On Windows, the PATHEXT environment +variable is applied as appropriate. + +If "verbose" is true then a tuple of the form + (<fullpath>, <matched-where-description>) +is returned for each match. The latter element is a textual description +of where the match was found. For example: + from PATH element 0 + from HKLM\SOFTWARE\...\perl.exe +""" + +_cmdlnUsage = """ + Show the full path of commands. + + Usage: + which [<options>...] [<command-name>...] + + Options: + -h, --help Print this help and exit. + -V, --version Print the version info and exit. + + -a, --all Print *all* matching paths. + -v, --verbose Print out how matches were located and + show near misses on stderr. + -q, --quiet Just print out matches. I.e., do not print out + near misses. + + -p <altpath>, --path=<altpath> + An alternative path (list of directories) may + be specified for searching. + -e <exts>, --exts=<exts> + Specify a list of extensions to consider instead + of the usual list (';'-separate list, Windows + only). + + Show the full path to the program that would be run for each given + command name, if any. Which, like GNU's which, returns the number of + failed arguments, or -1 when no <command-name> was given. + + Near misses include duplicates, non-regular files and (on Un*x) + files without executable access. +""" + +__revision__ = "$Id: which.py 430 2005-08-20 03:11:58Z trentm $" +__version_info__ = (1, 1, 0) +__version__ = '.'.join(map(str, __version_info__)) + +import os +import sys +import getopt +import stat + + +#---- exceptions + +class WhichError(Exception): + pass + + + +#---- internal support stuff + +def _getRegisteredExecutable(exeName): + """Windows allow application paths to be registered in the registry.""" + registered = None + if sys.platform.startswith('win'): + if os.path.splitext(exeName)[1].lower() != '.exe': + exeName += '.exe' + import _winreg + try: + key = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\" +\ + exeName + value = _winreg.QueryValue(_winreg.HKEY_LOCAL_MACHINE, key) + registered = (value, "from HKLM\\"+key) + except _winreg.error: + pass + if registered and not os.path.exists(registered[0]): + registered = None + return registered + +def _samefile(fname1, fname2): + if sys.platform.startswith('win'): + return ( os.path.normpath(os.path.normcase(fname1)) ==\ + os.path.normpath(os.path.normcase(fname2)) ) + else: + return os.path.samefile(fname1, fname2) + +def _cull(potential, matches, verbose=0): + """Cull inappropriate matches. Possible reasons: + - a duplicate of a previous match + - not a disk file + - not executable (non-Windows) + If 'potential' is approved it is returned and added to 'matches'. + Otherwise, None is returned. + """ + for match in matches: # don't yield duplicates + if _samefile(potential[0], match[0]): + if verbose: + sys.stderr.write("duplicate: %s (%s)\n" % potential) + return None + else: + if not stat.S_ISREG(os.stat(potential[0]).st_mode): + if verbose: + sys.stderr.write("not a regular file: %s (%s)\n" % potential) + elif not os.access(potential[0], os.X_OK): + if verbose: + sys.stderr.write("no executable access: %s (%s)\n"\ + % potential) + else: + matches.append(potential) + return potential + + +#---- module API + +def whichgen(command, path=None, verbose=0, exts=None): + """Return a generator of full paths to the given command. + + "command" is a the name of the executable to search for. + "path" is an optional alternate path list to search. The default it + to use the PATH environment variable. + "verbose", if true, will cause a 2-tuple to be returned for each + match. The second element is a textual description of where the + match was found. + "exts" optionally allows one to specify a list of extensions to use + instead of the standard list for this system. This can + effectively be used as an optimization to, for example, avoid + stat's of "foo.vbs" when searching for "foo" and you know it is + not a VisualBasic script but ".vbs" is on PATHEXT. This option + is only supported on Windows. + + This method returns a generator which yields either full paths to + the given command or, if verbose, tuples of the form (<path to + command>, <where path found>). + """ + matches = [] + if path is None: + usingGivenPath = 0 + path = os.environ.get("PATH", "").split(os.pathsep) + if sys.platform.startswith("win"): + path.insert(0, os.curdir) # implied by Windows shell + else: + usingGivenPath = 1 + + # Windows has the concept of a list of extensions (PATHEXT env var). + if sys.platform.startswith("win"): + if exts is None: + exts = os.environ.get("PATHEXT", "").split(os.pathsep) + # If '.exe' is not in exts then obviously this is Win9x and + # or a bogus PATHEXT, then use a reasonable default. + for ext in exts: + if ext.lower() == ".exe": + break + else: + exts = ['.COM', '.EXE', '.BAT'] + elif not isinstance(exts, list): + raise TypeError("'exts' argument must be a list or None") + else: + if exts is not None: + raise WhichError("'exts' argument is not supported on "\ + "platform '%s'" % sys.platform) + exts = [] + + # File name cannot have path separators because PATH lookup does not + # work that way. + if os.sep in command or os.altsep and os.altsep in command: + pass + else: + for i in range(len(path)): + dirName = path[i] + # On windows the dirName *could* be quoted, drop the quotes + if sys.platform.startswith("win") and len(dirName) >= 2\ + and dirName[0] == '"' and dirName[-1] == '"': + dirName = dirName[1:-1] + for ext in ['']+exts: + absName = os.path.abspath( + os.path.normpath(os.path.join(dirName, command+ext))) + if os.path.isfile(absName): + if usingGivenPath: + fromWhere = "from given path element %d" % i + elif not sys.platform.startswith("win"): + fromWhere = "from PATH element %d" % i + elif i == 0: + fromWhere = "from current directory" + else: + fromWhere = "from PATH element %d" % (i-1) + match = _cull((absName, fromWhere), matches, verbose) + if match: + if verbose: + yield match + else: + yield match[0] + match = _getRegisteredExecutable(command) + if match is not None: + match = _cull(match, matches, verbose) + if match: + if verbose: + yield match + else: + yield match[0] + + +def which(command, path=None, verbose=0, exts=None): + """Return the full path to the first match of the given command on + the path. + + "command" is a the name of the executable to search for. + "path" is an optional alternate path list to search. The default it + to use the PATH environment variable. + "verbose", if true, will cause a 2-tuple to be returned. The second + element is a textual description of where the match was found. + "exts" optionally allows one to specify a list of extensions to use + instead of the standard list for this system. This can + effectively be used as an optimization to, for example, avoid + stat's of "foo.vbs" when searching for "foo" and you know it is + not a VisualBasic script but ".vbs" is on PATHEXT. This option + is only supported on Windows. + + If no match is found for the command, a WhichError is raised. + """ + try: + match = whichgen(command, path, verbose, exts).next() + except StopIteration: + raise WhichError("Could not find '%s' on the path." % command) + return match + + +def whichall(command, path=None, verbose=0, exts=None): + """Return a list of full paths to all matches of the given command + on the path. + + "command" is a the name of the executable to search for. + "path" is an optional alternate path list to search. The default it + to use the PATH environment variable. + "verbose", if true, will cause a 2-tuple to be returned for each + match. The second element is a textual description of where the + match was found. + "exts" optionally allows one to specify a list of extensions to use + instead of the standard list for this system. This can + effectively be used as an optimization to, for example, avoid + stat's of "foo.vbs" when searching for "foo" and you know it is + not a VisualBasic script but ".vbs" is on PATHEXT. This option + is only supported on Windows. + """ + return list( whichgen(command, path, verbose, exts) ) + + + +#---- mainline + +def main(argv): + all = 0 + verbose = 0 + altpath = None + exts = None + try: + optlist, args = getopt.getopt(argv[1:], 'haVvqp:e:', + ['help', 'all', 'version', 'verbose', 'quiet', 'path=', 'exts=']) + except getopt.GetoptError, msg: + sys.stderr.write("which: error: %s. Your invocation was: %s\n"\ + % (msg, argv)) + sys.stderr.write("Try 'which --help'.\n") + return 1 + for opt, optarg in optlist: + if opt in ('-h', '--help'): + print _cmdlnUsage + return 0 + elif opt in ('-V', '--version'): + print "which %s" % __version__ + return 0 + elif opt in ('-a', '--all'): + all = 1 + elif opt in ('-v', '--verbose'): + verbose = 1 + elif opt in ('-q', '--quiet'): + verbose = 0 + elif opt in ('-p', '--path'): + if optarg: + altpath = optarg.split(os.pathsep) + else: + altpath = [] + elif opt in ('-e', '--exts'): + if optarg: + exts = optarg.split(os.pathsep) + else: + exts = [] + + if len(args) == 0: + return -1 + + failures = 0 + for arg in args: + #print "debug: search for %r" % arg + nmatches = 0 + for match in whichgen(arg, path=altpath, verbose=verbose, exts=exts): + if verbose: + print "%s (%s)" % match + else: + print match + nmatches += 1 + if not all: + break + if not nmatches: + failures += 1 + return failures + + +if __name__ == "__main__": + sys.exit( main(sys.argv) ) + + |