comparison corebio/utils/_which.py @ 7:8d676bbd1f2d

Uploaded
author davidmurphy
date Mon, 16 Jan 2012 07:03:36 -0500
parents c55bdc2fb9fa
children
comparison
equal deleted inserted replaced
6:4a4aca3d57c9 7:8d676bbd1f2d
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/
8
9 r"""Find the full path to commands.
10
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.
14
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.
18
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.
22
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.
27
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 """
35
36 _cmdlnUsage = """
37 Show the full path of commands.
38
39 Usage:
40 which [<options>...] [<command-name>...]
41
42 Options:
43 -h, --help Print this help and exit.
44 -V, --version Print the version info and exit.
45
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.
51
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).
59
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.
63
64 Near misses include duplicates, non-regular files and (on Un*x)
65 files without executable access.
66 """
67
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__))
71
72 import os
73 import sys
74 import getopt
75 import stat
76
77
78 #---- exceptions
79
80 class WhichError(Exception):
81 pass
82
83
84
85 #---- internal support stuff
86
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
104
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)
111
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
136
137
138 #---- module API
139
140 def whichgen(command, path=None, verbose=0, exts=None):
141 """Return a generator of full paths to the given command.
142
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.
155
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
168
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 = []
187
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]
225
226
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.
230
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.
242
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
250
251
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.
255
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) )
270
271
272
273 #---- mainline
274
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 = []
311
312 if len(args) == 0:
313 return -1
314
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
330
331
332 if __name__ == "__main__":
333 sys.exit( main(sys.argv) )
334
335