Skip to content

Commit 6ce08e1

Browse files
committed
Merge pull request #4 from tony/new-which
Update which() to use os.environ['PATH']
2 parents 6d74847 + 9cfe892 commit 6ce08e1

File tree

1 file changed

+60
-17
lines changed

1 file changed

+60
-17
lines changed

libtmux/common.py

Lines changed: 60 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import re
1212
import subprocess
1313
from distutils.version import StrictVersion
14+
from functools import wraps
1415

1516
from . import exc
1617
from ._compat import console_to_str
@@ -312,6 +313,21 @@ def get_by_id(self, id):
312313
return None
313314

314315

316+
def real_memoize(func):
317+
'''
318+
Memoize aka cache the return output of a function
319+
given a specific set of arguments
320+
'''
321+
cache = {}
322+
323+
@wraps(func)
324+
def _memoize(*args):
325+
if args not in cache:
326+
cache[args] = func(*args)
327+
return cache[args]
328+
return _memoize
329+
330+
315331
def which(exe=None):
316332
"""Return path of bin. Python clone of /usr/bin/which.
317333
@@ -322,24 +338,51 @@ def which(exe=None):
322338
:rtype: string
323339
324340
"""
325-
if exe:
326-
if os.access(exe, os.X_OK):
327-
return exe
328-
329-
# default path based on busybox's default
330-
search_path = '/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin'
341+
def _is_executable_file_or_link(exe):
342+
# check for os.X_OK doesn't suffice because directory may executable
343+
return (os.access(exe, os.X_OK) and
344+
(os.path.isfile(exe) or os.path.islink(exe)))
345+
346+
if _is_executable_file_or_link(exe):
347+
# executable in cwd or fullpath
348+
return exe
349+
350+
ext_list = os.environ.get('PATHEXT', '.EXE').split(';')
351+
352+
@real_memoize
353+
def _exe_has_ext():
354+
'''
355+
Do a case insensitive test if exe has a file extension match in
356+
PATHEXT
357+
'''
358+
for ext in ext_list:
359+
try:
360+
pattern = r'.*\.' + ext.lstrip('.') + r'$'
361+
re.match(pattern, exe, re.I).groups()
362+
return True
363+
except AttributeError:
364+
continue
365+
return False
366+
367+
# Enhance POSIX path for the reliability at some environments, when
368+
# $PATH is changing. This also keeps order, where 'first came, first
369+
# win' for cases to find optional alternatives
370+
search_path = os.environ.get('PATH') and \
371+
os.environ['PATH'].split(os.pathsep) or list()
372+
for default_path in [
373+
'/bin', '/sbin', '/usr/bin', '/usr/sbin', '/usr/local/bin'
374+
]:
375+
if default_path not in search_path:
376+
search_path.append(default_path)
377+
os.environ['PATH'] = os.pathsep.join(search_path)
378+
for path in search_path:
379+
full_path = os.path.join(path, exe)
380+
if _is_executable_file_or_link(full_path):
381+
return full_path
382+
logger.trace(
383+
'\'{0}\' could not be found in the following search path: '
384+
'\'{1}\''.format(exe, search_path))
331385

332-
for path in search_path.split(os.pathsep):
333-
full_path = os.path.join(path, exe)
334-
if os.access(full_path, os.X_OK):
335-
return full_path
336-
raise exc.LibTmuxException(
337-
'{0!r} could not be found in the following search '
338-
'path: {1!r}'.format(
339-
exe, search_path
340-
)
341-
)
342-
logger.error('No executable was passed to be searched by which')
343386
return None
344387

345388

0 commit comments

Comments
 (0)