1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/python/which/which.py Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,335 @@ 1.4 +#!/usr/bin/env python 1.5 +# Copyright (c) 2002-2005 ActiveState Corp. 1.6 +# See LICENSE.txt for license details. 1.7 +# Author: 1.8 +# Trent Mick (TrentM@ActiveState.com) 1.9 +# Home: 1.10 +# http://trentm.com/projects/which/ 1.11 + 1.12 +r"""Find the full path to commands. 1.13 + 1.14 +which(command, path=None, verbose=0, exts=None) 1.15 + Return the full path to the first match of the given command on the 1.16 + path. 1.17 + 1.18 +whichall(command, path=None, verbose=0, exts=None) 1.19 + Return a list of full paths to all matches of the given command on 1.20 + the path. 1.21 + 1.22 +whichgen(command, path=None, verbose=0, exts=None) 1.23 + Return a generator which will yield full paths to all matches of the 1.24 + given command on the path. 1.25 + 1.26 +By default the PATH environment variable is searched (as well as, on 1.27 +Windows, the AppPaths key in the registry), but a specific 'path' list 1.28 +to search may be specified as well. On Windows, the PATHEXT environment 1.29 +variable is applied as appropriate. 1.30 + 1.31 +If "verbose" is true then a tuple of the form 1.32 + (<fullpath>, <matched-where-description>) 1.33 +is returned for each match. The latter element is a textual description 1.34 +of where the match was found. For example: 1.35 + from PATH element 0 1.36 + from HKLM\SOFTWARE\...\perl.exe 1.37 +""" 1.38 + 1.39 +_cmdlnUsage = """ 1.40 + Show the full path of commands. 1.41 + 1.42 + Usage: 1.43 + which [<options>...] [<command-name>...] 1.44 + 1.45 + Options: 1.46 + -h, --help Print this help and exit. 1.47 + -V, --version Print the version info and exit. 1.48 + 1.49 + -a, --all Print *all* matching paths. 1.50 + -v, --verbose Print out how matches were located and 1.51 + show near misses on stderr. 1.52 + -q, --quiet Just print out matches. I.e., do not print out 1.53 + near misses. 1.54 + 1.55 + -p <altpath>, --path=<altpath> 1.56 + An alternative path (list of directories) may 1.57 + be specified for searching. 1.58 + -e <exts>, --exts=<exts> 1.59 + Specify a list of extensions to consider instead 1.60 + of the usual list (';'-separate list, Windows 1.61 + only). 1.62 + 1.63 + Show the full path to the program that would be run for each given 1.64 + command name, if any. Which, like GNU's which, returns the number of 1.65 + failed arguments, or -1 when no <command-name> was given. 1.66 + 1.67 + Near misses include duplicates, non-regular files and (on Un*x) 1.68 + files without executable access. 1.69 +""" 1.70 + 1.71 +__revision__ = "$Id: which.py 430 2005-08-20 03:11:58Z trentm $" 1.72 +__version_info__ = (1, 1, 0) 1.73 +__version__ = '.'.join(map(str, __version_info__)) 1.74 + 1.75 +import os 1.76 +import sys 1.77 +import getopt 1.78 +import stat 1.79 + 1.80 + 1.81 +#---- exceptions 1.82 + 1.83 +class WhichError(Exception): 1.84 + pass 1.85 + 1.86 + 1.87 + 1.88 +#---- internal support stuff 1.89 + 1.90 +def _getRegisteredExecutable(exeName): 1.91 + """Windows allow application paths to be registered in the registry.""" 1.92 + registered = None 1.93 + if sys.platform.startswith('win'): 1.94 + if os.path.splitext(exeName)[1].lower() != '.exe': 1.95 + exeName += '.exe' 1.96 + import _winreg 1.97 + try: 1.98 + key = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\" +\ 1.99 + exeName 1.100 + value = _winreg.QueryValue(_winreg.HKEY_LOCAL_MACHINE, key) 1.101 + registered = (value, "from HKLM\\"+key) 1.102 + except _winreg.error: 1.103 + pass 1.104 + if registered and not os.path.exists(registered[0]): 1.105 + registered = None 1.106 + return registered 1.107 + 1.108 +def _samefile(fname1, fname2): 1.109 + if sys.platform.startswith('win'): 1.110 + return ( os.path.normpath(os.path.normcase(fname1)) ==\ 1.111 + os.path.normpath(os.path.normcase(fname2)) ) 1.112 + else: 1.113 + return os.path.samefile(fname1, fname2) 1.114 + 1.115 +def _cull(potential, matches, verbose=0): 1.116 + """Cull inappropriate matches. Possible reasons: 1.117 + - a duplicate of a previous match 1.118 + - not a disk file 1.119 + - not executable (non-Windows) 1.120 + If 'potential' is approved it is returned and added to 'matches'. 1.121 + Otherwise, None is returned. 1.122 + """ 1.123 + for match in matches: # don't yield duplicates 1.124 + if _samefile(potential[0], match[0]): 1.125 + if verbose: 1.126 + sys.stderr.write("duplicate: %s (%s)\n" % potential) 1.127 + return None 1.128 + else: 1.129 + if not stat.S_ISREG(os.stat(potential[0]).st_mode): 1.130 + if verbose: 1.131 + sys.stderr.write("not a regular file: %s (%s)\n" % potential) 1.132 + elif not os.access(potential[0], os.X_OK): 1.133 + if verbose: 1.134 + sys.stderr.write("no executable access: %s (%s)\n"\ 1.135 + % potential) 1.136 + else: 1.137 + matches.append(potential) 1.138 + return potential 1.139 + 1.140 + 1.141 +#---- module API 1.142 + 1.143 +def whichgen(command, path=None, verbose=0, exts=None): 1.144 + """Return a generator of full paths to the given command. 1.145 + 1.146 + "command" is a the name of the executable to search for. 1.147 + "path" is an optional alternate path list to search. The default it 1.148 + to use the PATH environment variable. 1.149 + "verbose", if true, will cause a 2-tuple to be returned for each 1.150 + match. The second element is a textual description of where the 1.151 + match was found. 1.152 + "exts" optionally allows one to specify a list of extensions to use 1.153 + instead of the standard list for this system. This can 1.154 + effectively be used as an optimization to, for example, avoid 1.155 + stat's of "foo.vbs" when searching for "foo" and you know it is 1.156 + not a VisualBasic script but ".vbs" is on PATHEXT. This option 1.157 + is only supported on Windows. 1.158 + 1.159 + This method returns a generator which yields either full paths to 1.160 + the given command or, if verbose, tuples of the form (<path to 1.161 + command>, <where path found>). 1.162 + """ 1.163 + matches = [] 1.164 + if path is None: 1.165 + usingGivenPath = 0 1.166 + path = os.environ.get("PATH", "").split(os.pathsep) 1.167 + if sys.platform.startswith("win"): 1.168 + path.insert(0, os.curdir) # implied by Windows shell 1.169 + else: 1.170 + usingGivenPath = 1 1.171 + 1.172 + # Windows has the concept of a list of extensions (PATHEXT env var). 1.173 + if sys.platform.startswith("win"): 1.174 + if exts is None: 1.175 + exts = os.environ.get("PATHEXT", "").split(os.pathsep) 1.176 + # If '.exe' is not in exts then obviously this is Win9x and 1.177 + # or a bogus PATHEXT, then use a reasonable default. 1.178 + for ext in exts: 1.179 + if ext.lower() == ".exe": 1.180 + break 1.181 + else: 1.182 + exts = ['.COM', '.EXE', '.BAT'] 1.183 + elif not isinstance(exts, list): 1.184 + raise TypeError("'exts' argument must be a list or None") 1.185 + else: 1.186 + if exts is not None: 1.187 + raise WhichError("'exts' argument is not supported on "\ 1.188 + "platform '%s'" % sys.platform) 1.189 + exts = [] 1.190 + 1.191 + # File name cannot have path separators because PATH lookup does not 1.192 + # work that way. 1.193 + if os.sep in command or os.altsep and os.altsep in command: 1.194 + pass 1.195 + else: 1.196 + for i in range(len(path)): 1.197 + dirName = path[i] 1.198 + # On windows the dirName *could* be quoted, drop the quotes 1.199 + if sys.platform.startswith("win") and len(dirName) >= 2\ 1.200 + and dirName[0] == '"' and dirName[-1] == '"': 1.201 + dirName = dirName[1:-1] 1.202 + for ext in ['']+exts: 1.203 + absName = os.path.abspath( 1.204 + os.path.normpath(os.path.join(dirName, command+ext))) 1.205 + if os.path.isfile(absName): 1.206 + if usingGivenPath: 1.207 + fromWhere = "from given path element %d" % i 1.208 + elif not sys.platform.startswith("win"): 1.209 + fromWhere = "from PATH element %d" % i 1.210 + elif i == 0: 1.211 + fromWhere = "from current directory" 1.212 + else: 1.213 + fromWhere = "from PATH element %d" % (i-1) 1.214 + match = _cull((absName, fromWhere), matches, verbose) 1.215 + if match: 1.216 + if verbose: 1.217 + yield match 1.218 + else: 1.219 + yield match[0] 1.220 + match = _getRegisteredExecutable(command) 1.221 + if match is not None: 1.222 + match = _cull(match, matches, verbose) 1.223 + if match: 1.224 + if verbose: 1.225 + yield match 1.226 + else: 1.227 + yield match[0] 1.228 + 1.229 + 1.230 +def which(command, path=None, verbose=0, exts=None): 1.231 + """Return the full path to the first match of the given command on 1.232 + the path. 1.233 + 1.234 + "command" is a the name of the executable to search for. 1.235 + "path" is an optional alternate path list to search. The default it 1.236 + to use the PATH environment variable. 1.237 + "verbose", if true, will cause a 2-tuple to be returned. The second 1.238 + element is a textual description of where the match was found. 1.239 + "exts" optionally allows one to specify a list of extensions to use 1.240 + instead of the standard list for this system. This can 1.241 + effectively be used as an optimization to, for example, avoid 1.242 + stat's of "foo.vbs" when searching for "foo" and you know it is 1.243 + not a VisualBasic script but ".vbs" is on PATHEXT. This option 1.244 + is only supported on Windows. 1.245 + 1.246 + If no match is found for the command, a WhichError is raised. 1.247 + """ 1.248 + try: 1.249 + match = whichgen(command, path, verbose, exts).next() 1.250 + except StopIteration: 1.251 + raise WhichError("Could not find '%s' on the path." % command) 1.252 + return match 1.253 + 1.254 + 1.255 +def whichall(command, path=None, verbose=0, exts=None): 1.256 + """Return a list of full paths to all matches of the given command 1.257 + on the path. 1.258 + 1.259 + "command" is a the name of the executable to search for. 1.260 + "path" is an optional alternate path list to search. The default it 1.261 + to use the PATH environment variable. 1.262 + "verbose", if true, will cause a 2-tuple to be returned for each 1.263 + match. The second element is a textual description of where the 1.264 + match was found. 1.265 + "exts" optionally allows one to specify a list of extensions to use 1.266 + instead of the standard list for this system. This can 1.267 + effectively be used as an optimization to, for example, avoid 1.268 + stat's of "foo.vbs" when searching for "foo" and you know it is 1.269 + not a VisualBasic script but ".vbs" is on PATHEXT. This option 1.270 + is only supported on Windows. 1.271 + """ 1.272 + return list( whichgen(command, path, verbose, exts) ) 1.273 + 1.274 + 1.275 + 1.276 +#---- mainline 1.277 + 1.278 +def main(argv): 1.279 + all = 0 1.280 + verbose = 0 1.281 + altpath = None 1.282 + exts = None 1.283 + try: 1.284 + optlist, args = getopt.getopt(argv[1:], 'haVvqp:e:', 1.285 + ['help', 'all', 'version', 'verbose', 'quiet', 'path=', 'exts=']) 1.286 + except getopt.GetoptError, msg: 1.287 + sys.stderr.write("which: error: %s. Your invocation was: %s\n"\ 1.288 + % (msg, argv)) 1.289 + sys.stderr.write("Try 'which --help'.\n") 1.290 + return 1 1.291 + for opt, optarg in optlist: 1.292 + if opt in ('-h', '--help'): 1.293 + print _cmdlnUsage 1.294 + return 0 1.295 + elif opt in ('-V', '--version'): 1.296 + print "which %s" % __version__ 1.297 + return 0 1.298 + elif opt in ('-a', '--all'): 1.299 + all = 1 1.300 + elif opt in ('-v', '--verbose'): 1.301 + verbose = 1 1.302 + elif opt in ('-q', '--quiet'): 1.303 + verbose = 0 1.304 + elif opt in ('-p', '--path'): 1.305 + if optarg: 1.306 + altpath = optarg.split(os.pathsep) 1.307 + else: 1.308 + altpath = [] 1.309 + elif opt in ('-e', '--exts'): 1.310 + if optarg: 1.311 + exts = optarg.split(os.pathsep) 1.312 + else: 1.313 + exts = [] 1.314 + 1.315 + if len(args) == 0: 1.316 + return -1 1.317 + 1.318 + failures = 0 1.319 + for arg in args: 1.320 + #print "debug: search for %r" % arg 1.321 + nmatches = 0 1.322 + for match in whichgen(arg, path=altpath, verbose=verbose, exts=exts): 1.323 + if verbose: 1.324 + print "%s (%s)" % match 1.325 + else: 1.326 + print match 1.327 + nmatches += 1 1.328 + if not all: 1.329 + break 1.330 + if not nmatches: 1.331 + failures += 1 1.332 + return failures 1.333 + 1.334 + 1.335 +if __name__ == "__main__": 1.336 + sys.exit( main(sys.argv) ) 1.337 + 1.338 +