python/which/which.py

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     1 #!/usr/bin/env python
     2 # Copyright (c) 2002-2005 ActiveState Corp.
     3 # See LICENSE.txt for license details.
     4 # Author:
     5 #   Trent Mick (TrentM@ActiveState.com)
     6 # Home:
     7 #   http://trentm.com/projects/which/
     9 r"""Find the full path to commands.
    11 which(command, path=None, verbose=0, exts=None)
    12     Return the full path to the first match of the given command on the
    13     path.
    15 whichall(command, path=None, verbose=0, exts=None)
    16     Return a list of full paths to all matches of the given command on
    17     the path.
    19 whichgen(command, path=None, verbose=0, exts=None)
    20     Return a generator which will yield full paths to all matches of the
    21     given command on the path.
    23 By default the PATH environment variable is searched (as well as, on
    24 Windows, the AppPaths key in the registry), but a specific 'path' list
    25 to search may be specified as well.  On Windows, the PATHEXT environment
    26 variable is applied as appropriate.
    28 If "verbose" is true then a tuple of the form
    29     (<fullpath>, <matched-where-description>)
    30 is returned for each match. The latter element is a textual description
    31 of where the match was found. For example:
    32     from PATH element 0
    33     from HKLM\SOFTWARE\...\perl.exe
    34 """
    36 _cmdlnUsage = """
    37     Show the full path of commands.
    39     Usage:
    40         which [<options>...] [<command-name>...]
    42     Options:
    43         -h, --help      Print this help and exit.
    44         -V, --version   Print the version info and exit.
    46         -a, --all       Print *all* matching paths.
    47         -v, --verbose   Print out how matches were located and
    48                         show near misses on stderr.
    49         -q, --quiet     Just print out matches. I.e., do not print out
    50                         near misses.
    52         -p <altpath>, --path=<altpath>
    53                         An alternative path (list of directories) may
    54                         be specified for searching.
    55         -e <exts>, --exts=<exts>
    56                         Specify a list of extensions to consider instead
    57                         of the usual list (';'-separate list, Windows
    58                         only).
    60     Show the full path to the program that would be run for each given
    61     command name, if any. Which, like GNU's which, returns the number of
    62     failed arguments, or -1 when no <command-name> was given.
    64     Near misses include duplicates, non-regular files and (on Un*x)
    65     files without executable access.
    66 """
    68 __revision__ = "$Id: which.py 430 2005-08-20 03:11:58Z trentm $"
    69 __version_info__ = (1, 1, 0)
    70 __version__ = '.'.join(map(str, __version_info__))
    72 import os
    73 import sys
    74 import getopt
    75 import stat
    78 #---- exceptions
    80 class WhichError(Exception):
    81     pass
    85 #---- internal support stuff
    87 def _getRegisteredExecutable(exeName):
    88     """Windows allow application paths to be registered in the registry."""
    89     registered = None
    90     if sys.platform.startswith('win'):
    91         if os.path.splitext(exeName)[1].lower() != '.exe':
    92             exeName += '.exe'
    93         import _winreg
    94         try:
    95             key = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\" +\
    96                   exeName
    97             value = _winreg.QueryValue(_winreg.HKEY_LOCAL_MACHINE, key)
    98             registered = (value, "from HKLM\\"+key)
    99         except _winreg.error:
   100             pass
   101         if registered and not os.path.exists(registered[0]):
   102             registered = None
   103     return registered
   105 def _samefile(fname1, fname2):
   106     if sys.platform.startswith('win'):
   107         return ( os.path.normpath(os.path.normcase(fname1)) ==\
   108             os.path.normpath(os.path.normcase(fname2)) )
   109     else:
   110         return os.path.samefile(fname1, fname2)
   112 def _cull(potential, matches, verbose=0):
   113     """Cull inappropriate matches. Possible reasons:
   114         - a duplicate of a previous match
   115         - not a disk file
   116         - not executable (non-Windows)
   117     If 'potential' is approved it is returned and added to 'matches'.
   118     Otherwise, None is returned.
   119     """
   120     for match in matches:  # don't yield duplicates
   121         if _samefile(potential[0], match[0]):
   122             if verbose:
   123                 sys.stderr.write("duplicate: %s (%s)\n" % potential)
   124             return None
   125     else:
   126         if not stat.S_ISREG(os.stat(potential[0]).st_mode):
   127             if verbose:
   128                 sys.stderr.write("not a regular file: %s (%s)\n" % potential)
   129         elif not os.access(potential[0], os.X_OK):
   130             if verbose:
   131                 sys.stderr.write("no executable access: %s (%s)\n"\
   132                                  % potential)
   133         else:
   134             matches.append(potential)
   135             return potential
   138 #---- module API
   140 def whichgen(command, path=None, verbose=0, exts=None):
   141     """Return a generator of full paths to the given command.
   143     "command" is a the name of the executable to search for.
   144     "path" is an optional alternate path list to search. The default it
   145         to use the PATH environment variable.
   146     "verbose", if true, will cause a 2-tuple to be returned for each
   147         match. The second element is a textual description of where the
   148         match was found.
   149     "exts" optionally allows one to specify a list of extensions to use
   150         instead of the standard list for this system. This can
   151         effectively be used as an optimization to, for example, avoid
   152         stat's of "foo.vbs" when searching for "foo" and you know it is
   153         not a VisualBasic script but ".vbs" is on PATHEXT. This option
   154         is only supported on Windows.
   156     This method returns a generator which yields either full paths to
   157     the given command or, if verbose, tuples of the form (<path to
   158     command>, <where path found>).
   159     """
   160     matches = []
   161     if path is None:
   162         usingGivenPath = 0
   163         path = os.environ.get("PATH", "").split(os.pathsep)
   164         if sys.platform.startswith("win"):
   165             path.insert(0, os.curdir)  # implied by Windows shell
   166     else:
   167         usingGivenPath = 1
   169     # Windows has the concept of a list of extensions (PATHEXT env var).
   170     if sys.platform.startswith("win"):
   171         if exts is None:
   172             exts = os.environ.get("PATHEXT", "").split(os.pathsep)
   173             # If '.exe' is not in exts then obviously this is Win9x and
   174             # or a bogus PATHEXT, then use a reasonable default.
   175             for ext in exts:
   176                 if ext.lower() == ".exe":
   177                     break
   178             else:
   179                 exts = ['.COM', '.EXE', '.BAT']
   180         elif not isinstance(exts, list):
   181             raise TypeError("'exts' argument must be a list or None")
   182     else:
   183         if exts is not None:
   184             raise WhichError("'exts' argument is not supported on "\
   185                              "platform '%s'" % sys.platform)
   186         exts = []
   188     # File name cannot have path separators because PATH lookup does not
   189     # work that way.
   190     if os.sep in command or os.altsep and os.altsep in command:
   191         pass
   192     else:
   193         for i in range(len(path)):
   194             dirName = path[i]
   195             # On windows the dirName *could* be quoted, drop the quotes
   196             if sys.platform.startswith("win") and len(dirName) >= 2\
   197                and dirName[0] == '"' and dirName[-1] == '"':
   198                 dirName = dirName[1:-1]
   199             for ext in ['']+exts:
   200                 absName = os.path.abspath(
   201                     os.path.normpath(os.path.join(dirName, command+ext)))
   202                 if os.path.isfile(absName):
   203                     if usingGivenPath:
   204                         fromWhere = "from given path element %d" % i
   205                     elif not sys.platform.startswith("win"):
   206                         fromWhere = "from PATH element %d" % i
   207                     elif i == 0:
   208                         fromWhere = "from current directory"
   209                     else:
   210                         fromWhere = "from PATH element %d" % (i-1)
   211                     match = _cull((absName, fromWhere), matches, verbose)
   212                     if match:
   213                         if verbose:
   214                             yield match
   215                         else:
   216                             yield match[0]
   217         match = _getRegisteredExecutable(command)
   218         if match is not None:
   219             match = _cull(match, matches, verbose)
   220             if match:
   221                 if verbose:
   222                     yield match
   223                 else:
   224                     yield match[0]
   227 def which(command, path=None, verbose=0, exts=None):
   228     """Return the full path to the first match of the given command on
   229     the path.
   231     "command" is a the name of the executable to search for.
   232     "path" is an optional alternate path list to search. The default it
   233         to use the PATH environment variable.
   234     "verbose", if true, will cause a 2-tuple to be returned. The second
   235         element is a textual description of where the match was found.
   236     "exts" optionally allows one to specify a list of extensions to use
   237         instead of the standard list for this system. This can
   238         effectively be used as an optimization to, for example, avoid
   239         stat's of "foo.vbs" when searching for "foo" and you know it is
   240         not a VisualBasic script but ".vbs" is on PATHEXT. This option
   241         is only supported on Windows.
   243     If no match is found for the command, a WhichError is raised.
   244     """
   245     try:
   246         match = whichgen(command, path, verbose, exts).next()
   247     except StopIteration:
   248         raise WhichError("Could not find '%s' on the path." % command)
   249     return match
   252 def whichall(command, path=None, verbose=0, exts=None):
   253     """Return a list of full paths to all matches of the given command
   254     on the path.  
   256     "command" is a the name of the executable to search for.
   257     "path" is an optional alternate path list to search. The default it
   258         to use the PATH environment variable.
   259     "verbose", if true, will cause a 2-tuple to be returned for each
   260         match. The second element is a textual description of where the
   261         match was found.
   262     "exts" optionally allows one to specify a list of extensions to use
   263         instead of the standard list for this system. This can
   264         effectively be used as an optimization to, for example, avoid
   265         stat's of "foo.vbs" when searching for "foo" and you know it is
   266         not a VisualBasic script but ".vbs" is on PATHEXT. This option
   267         is only supported on Windows.
   268     """
   269     return list( whichgen(command, path, verbose, exts) )
   273 #---- mainline
   275 def main(argv):
   276     all = 0
   277     verbose = 0
   278     altpath = None
   279     exts = None
   280     try:
   281         optlist, args = getopt.getopt(argv[1:], 'haVvqp:e:',
   282             ['help', 'all', 'version', 'verbose', 'quiet', 'path=', 'exts='])
   283     except getopt.GetoptError, msg:
   284         sys.stderr.write("which: error: %s. Your invocation was: %s\n"\
   285                          % (msg, argv))
   286         sys.stderr.write("Try 'which --help'.\n")
   287         return 1
   288     for opt, optarg in optlist:
   289         if opt in ('-h', '--help'):
   290             print _cmdlnUsage
   291             return 0
   292         elif opt in ('-V', '--version'):
   293             print "which %s" % __version__
   294             return 0
   295         elif opt in ('-a', '--all'):
   296             all = 1
   297         elif opt in ('-v', '--verbose'):
   298             verbose = 1
   299         elif opt in ('-q', '--quiet'):
   300             verbose = 0
   301         elif opt in ('-p', '--path'):
   302             if optarg:
   303                 altpath = optarg.split(os.pathsep)
   304             else:
   305                 altpath = []
   306         elif opt in ('-e', '--exts'):
   307             if optarg:
   308                 exts = optarg.split(os.pathsep)
   309             else:
   310                 exts = []
   312     if len(args) == 0:
   313         return -1
   315     failures = 0
   316     for arg in args:
   317         #print "debug: search for %r" % arg
   318         nmatches = 0
   319         for match in whichgen(arg, path=altpath, verbose=verbose, exts=exts):
   320             if verbose:
   321                 print "%s (%s)" % match
   322             else:
   323                 print match
   324             nmatches += 1
   325             if not all:
   326                 break
   327         if not nmatches:
   328             failures += 1
   329     return failures
   332 if __name__ == "__main__":
   333     sys.exit( main(sys.argv) )

mercurial