BASIS  version 1.2.3 (revision 2104)
which.py
00001 # Copyright (c) 2002-2007 ActiveState Software Inc.
00002 #
00003 # Permission is hereby granted, free of charge, to any person obtaining a
00004 # copy of this software and associated documentation files (the
00005 # "Software"), to deal in the Software without restriction, including
00006 # without limitation the rights to use, copy, modify, merge, publish,
00007 # distribute, sublicense, and/or sell copies of the Software, and to
00008 # permit persons to whom the Software is furnished to do so, subject to
00009 # the following conditions:
00010 #
00011 # The above copyright notice and this permission notice shall be included
00012 # in all copies or substantial portions of the Software.
00013 #
00014 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
00015 # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
00016 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
00017 # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
00018 # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
00019 # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
00020 # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
00021 #
00022 # Author:
00023 #   Trent Mick (TrentM@ActiveState.com)
00024 # Home:
00025 #   http://trentm.com/projects/which/
00026 
00027 r"""Find the full path to commands.
00028 
00029 which(command, path=None, verbose=0, exts=None)
00030     Return the full path to the first match of the given command on the
00031     path.
00032 
00033 whichall(command, path=None, verbose=0, exts=None)
00034     Return a list of full paths to all matches of the given command on
00035     the path.
00036 
00037 whichgen(command, path=None, verbose=0, exts=None)
00038     Return a generator which will yield full paths to all matches of the
00039     given command on the path.
00040     
00041 By default the PATH environment variable is searched (as well as, on
00042 Windows, the AppPaths key in the registry), but a specific 'path' list
00043 to search may be specified as well.  On Windows, the PATHEXT environment
00044 variable is applied as appropriate.
00045 
00046 If "verbose" is true then a tuple of the form
00047     (<fullpath>, <matched-where-description>)
00048 is returned for each match. The latter element is a textual description
00049 of where the match was found. For example:
00050     from PATH element 0
00051     from HKLM\SOFTWARE\...\perl.exe
00052 """
00053 
00054 _cmdlnUsage = """
00055     Show the full path of commands.
00056 
00057     Usage:
00058         which [<options>...] [<command-name>...]
00059 
00060     Options:
00061         -h, --help      Print this help and exit.
00062         -V, --version   Print the version info and exit.
00063 
00064         -a, --all       Print *all* matching paths.
00065         -v, --verbose   Print out how matches were located and
00066                         show near misses on stderr.
00067         -q, --quiet     Just print out matches. I.e., do not print out
00068                         near misses.
00069 
00070         -p <altpath>, --path=<altpath>
00071                         An alternative path (list of directories) may
00072                         be specified for searching.
00073         -e <exts>, --exts=<exts>
00074                         Specify a list of extensions to consider instead
00075                         of the usual list (';'-separate list, Windows
00076                         only).
00077 
00078     Show the full path to the program that would be run for each given
00079     command name, if any. Which, like GNU's which, returns the number of
00080     failed arguments, or -1 when no <command-name> was given.
00081 
00082     Near misses include duplicates, non-regular files and (on Un*x)
00083     files without executable access.
00084 """
00085 
00086 __revision__ = "Id: which.py 1448 2007-02-28 19:13:06Z trentm"
00087 __version_info__ = (1, 1, 3)
00088 __version__ = '.'.join(map(str, __version_info__))
00089 __all__ = ["which", "whichall", "whichgen", "WhichError"]
00090 
00091 import os
00092 import sys
00093 import getopt
00094 import stat
00095 
00096 
00097 #---- exceptions
00098 
00099 class WhichError(Exception):
00100     pass
00101 
00102 
00103 
00104 #---- internal support stuff
00105 
00106 def _getRegisteredExecutable(exeName):
00107     """Windows allow application paths to be registered in the registry."""
00108     registered = None
00109     if sys.platform.startswith('win'):
00110         if os.path.splitext(exeName)[1].lower() != '.exe':
00111             exeName += '.exe'
00112         import _winreg
00113         try:
00114             key = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\" +\
00115                   exeName
00116             value = _winreg.QueryValue(_winreg.HKEY_LOCAL_MACHINE, key)
00117             registered = (value, "from HKLM\\"+key)
00118         except _winreg.error:
00119             pass
00120         if registered and not os.path.exists(registered[0]):
00121             registered = None
00122     return registered
00123 
00124 def _samefile(fname1, fname2):
00125     if sys.platform.startswith('win'):
00126         return ( os.path.normpath(os.path.normcase(fname1)) ==\
00127             os.path.normpath(os.path.normcase(fname2)) )
00128     else:
00129         return os.path.samefile(fname1, fname2)
00130 
00131 def _cull(potential, matches, verbose=0):
00132     """Cull inappropriate matches. Possible reasons:
00133         - a duplicate of a previous match
00134         - not a disk file
00135         - not executable (non-Windows)
00136     If 'potential' is approved it is returned and added to 'matches'.
00137     Otherwise, None is returned.
00138     """
00139     for match in matches:  # don't yield duplicates
00140         if _samefile(potential[0], match[0]):
00141             if verbose:
00142                 sys.stderr.write("duplicate: %s (%s)\n" % potential)
00143             return None
00144     else:
00145         if not stat.S_ISREG(os.stat(potential[0]).st_mode):
00146             if verbose:
00147                 sys.stderr.write("not a regular file: %s (%s)\n" % potential)
00148         elif sys.platform != "win32" \
00149              and not os.access(potential[0], os.X_OK):
00150             if verbose:
00151                 sys.stderr.write("no executable access: %s (%s)\n"\
00152                                  % potential)
00153         else:
00154             matches.append(potential)
00155             return potential
00156 
00157         
00158 #---- module API
00159 
00160 def whichgen(command, path=None, verbose=0, exts=None):
00161     """Return a generator of full paths to the given command.
00162     
00163     "command" is a the name of the executable to search for.
00164     "path" is an optional alternate path list to search. The default it
00165         to use the PATH environment variable.
00166     "verbose", if true, will cause a 2-tuple to be returned for each
00167         match. The second element is a textual description of where the
00168         match was found.
00169     "exts" optionally allows one to specify a list of extensions to use
00170         instead of the standard list for this system. This can
00171         effectively be used as an optimization to, for example, avoid
00172         stat's of "foo.vbs" when searching for "foo" and you know it is
00173         not a VisualBasic script but ".vbs" is on PATHEXT. This option
00174         is only supported on Windows.
00175 
00176     This method returns a generator which yields either full paths to
00177     the given command or, if verbose, tuples of the form (<path to
00178     command>, <where path found>).
00179     """
00180     matches = []
00181     if path is None:
00182         usingGivenPath = 0
00183         path = os.environ.get("PATH", "").split(os.pathsep)
00184         if sys.platform.startswith("win"):
00185             path.insert(0, os.curdir)  # implied by Windows shell
00186     else:
00187         usingGivenPath = 1
00188 
00189     # Windows has the concept of a list of extensions (PATHEXT env var).
00190     if sys.platform.startswith("win"):
00191         if exts is None:
00192             exts = os.environ.get("PATHEXT", "").split(os.pathsep)
00193             # If '.exe' is not in exts then obviously this is Win9x and
00194             # or a bogus PATHEXT, then use a reasonable default.
00195             for ext in exts:
00196                 if ext.lower() == ".exe":
00197                     break
00198             else:
00199                 exts = ['.CMD', '.COM', '.EXE', '.BAT']
00200         elif not isinstance(exts, list):
00201             raise TypeError("'exts' argument must be a list or None")
00202     else:
00203         if exts is not None:
00204             raise WhichError("'exts' argument is not supported on "\
00205                              "platform '%s'" % sys.platform)
00206         exts = []
00207 
00208     # File name cannot have path separators because PATH lookup does not
00209     # work that way.
00210     if os.sep in command or os.altsep and os.altsep in command:
00211         if os.path.exists(command):
00212             match = _cull((command, "explicit path given"), matches, verbose)
00213             if verbose:
00214                 yield match
00215             else:
00216                 yield match[0]
00217     else:
00218         for i in range(len(path)):
00219             dirName = path[i]
00220             # On windows the dirName *could* be quoted, drop the quotes
00221             if sys.platform.startswith("win") and len(dirName) >= 2\
00222                and dirName[0] == '"' and dirName[-1] == '"':
00223                 dirName = dirName[1:-1]
00224             for ext in ['']+exts:
00225                 absName = os.path.abspath(
00226                     os.path.normpath(os.path.join(dirName, command+ext)))
00227                 if os.path.isfile(absName):
00228                     if usingGivenPath:
00229                         fromWhere = "from given path element %d" % i
00230                     elif not sys.platform.startswith("win"):
00231                         fromWhere = "from PATH element %d" % i
00232                     elif i == 0:
00233                         fromWhere = "from current directory"
00234                     else:
00235                         fromWhere = "from PATH element %d" % (i-1)
00236                     match = _cull((absName, fromWhere), matches, verbose)
00237                     if match:
00238                         if verbose:
00239                             yield match
00240                         else:
00241                             yield match[0]
00242         match = _getRegisteredExecutable(command)
00243         if match is not None:
00244             match = _cull(match, matches, verbose)
00245             if match:
00246                 if verbose:
00247                     yield match
00248                 else:
00249                     yield match[0]
00250 
00251 
00252 def which(command, path=None, verbose=0, exts=None):
00253     """Return the full path to the first match of the given command on
00254     the path.
00255     
00256     "command" is a the name of the executable to search for.
00257     "path" is an optional alternate path list to search. The default it
00258         to use the PATH environment variable.
00259     "verbose", if true, will cause a 2-tuple to be returned. The second
00260         element is a textual description of where the match was found.
00261     "exts" optionally allows one to specify a list of extensions to use
00262         instead of the standard list for this system. This can
00263         effectively be used as an optimization to, for example, avoid
00264         stat's of "foo.vbs" when searching for "foo" and you know it is
00265         not a VisualBasic script but ".vbs" is on PATHEXT. This option
00266         is only supported on Windows.
00267 
00268     If no match is found for the command, a WhichError is raised.
00269     """
00270     try:
00271         match = whichgen(command, path, verbose, exts).next()
00272     except StopIteration:
00273         raise WhichError("Could not find '%s' on the path." % command)
00274     return match
00275 
00276 
00277 def whichall(command, path=None, verbose=0, exts=None):
00278     """Return a list of full paths to all matches of the given command
00279     on the path.  
00280 
00281     "command" is a the name of the executable to search for.
00282     "path" is an optional alternate path list to search. The default it
00283         to use the PATH environment variable.
00284     "verbose", if true, will cause a 2-tuple to be returned for each
00285         match. The second element is a textual description of where the
00286         match was found.
00287     "exts" optionally allows one to specify a list of extensions to use
00288         instead of the standard list for this system. This can
00289         effectively be used as an optimization to, for example, avoid
00290         stat's of "foo.vbs" when searching for "foo" and you know it is
00291         not a VisualBasic script but ".vbs" is on PATHEXT. This option
00292         is only supported on Windows.
00293     """
00294     return list( whichgen(command, path, verbose, exts) )
00295 
00296 
00297 
00298 #---- mainline
00299 
00300 def main(argv):
00301     all = 0
00302     verbose = 0
00303     altpath = None
00304     exts = None
00305     try:
00306         optlist, args = getopt.getopt(argv[1:], 'haVvqp:e:',
00307             ['help', 'all', 'version', 'verbose', 'quiet', 'path=', 'exts='])
00308     except getopt.GetoptError, msg:
00309         sys.stderr.write("which: error: %s. Your invocation was: %s\n"\
00310                          % (msg, argv))
00311         sys.stderr.write("Try 'which --help'.\n")
00312         return 1
00313     for opt, optarg in optlist:
00314         if opt in ('-h', '--help'):
00315             print _cmdlnUsage
00316             return 0
00317         elif opt in ('-V', '--version'):
00318             print "which %s" % __version__
00319             return 0
00320         elif opt in ('-a', '--all'):
00321             all = 1
00322         elif opt in ('-v', '--verbose'):
00323             verbose = 1
00324         elif opt in ('-q', '--quiet'):
00325             verbose = 0
00326         elif opt in ('-p', '--path'):
00327             if optarg:
00328                 altpath = optarg.split(os.pathsep)
00329             else:
00330                 altpath = []
00331         elif opt in ('-e', '--exts'):
00332             if optarg:
00333                 exts = optarg.split(os.pathsep)
00334             else:
00335                 exts = []
00336 
00337     if len(args) == 0:
00338         return -1
00339 
00340     failures = 0
00341     for arg in args:
00342         #print "debug: search for %r" % arg
00343         nmatches = 0
00344         for match in whichgen(arg, path=altpath, verbose=verbose, exts=exts):
00345             if verbose:
00346                 print "%s (%s)" % match
00347             else:
00348                 print match
00349             nmatches += 1
00350             if not all:
00351                 break
00352         if not nmatches:
00353             failures += 1
00354     return failures
00355 
00356 
00357 if __name__ == "__main__":
00358     sys.exit( main(sys.argv) )
00359 
00360