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

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

mercurial