Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 60 additions & 17 deletions libtmux/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import re
import subprocess
from distutils.version import StrictVersion
from functools import wraps

from . import exc
from ._compat import console_to_str
Expand Down Expand Up @@ -312,6 +313,21 @@ def get_by_id(self, id):
return None


def real_memoize(func):
'''
Memoize aka cache the return output of a function
given a specific set of arguments
'''
cache = {}

@wraps(func)
def _memoize(*args):
if args not in cache:
cache[args] = func(*args)
return cache[args]
return _memoize


def which(exe=None):
"""Return path of bin. Python clone of /usr/bin/which.

Expand All @@ -322,24 +338,51 @@ def which(exe=None):
:rtype: string

"""
if exe:
if os.access(exe, os.X_OK):
return exe

# default path based on busybox's default
search_path = '/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin'
def _is_executable_file_or_link(exe):
# check for os.X_OK doesn't suffice because directory may executable
return (os.access(exe, os.X_OK) and
(os.path.isfile(exe) or os.path.islink(exe)))

if _is_executable_file_or_link(exe):
# executable in cwd or fullpath
return exe

ext_list = os.environ.get('PATHEXT', '.EXE').split(';')

@real_memoize
def _exe_has_ext():
'''
Do a case insensitive test if exe has a file extension match in
PATHEXT
'''
for ext in ext_list:
try:
pattern = r'.*\.' + ext.lstrip('.') + r'$'
re.match(pattern, exe, re.I).groups()
return True
except AttributeError:
continue
return False

# Enhance POSIX path for the reliability at some environments, when
# $PATH is changing. This also keeps order, where 'first came, first
# win' for cases to find optional alternatives
search_path = os.environ.get('PATH') and \
os.environ['PATH'].split(os.pathsep) or list()
for default_path in [
'/bin', '/sbin', '/usr/bin', '/usr/sbin', '/usr/local/bin'
]:
if default_path not in search_path:
search_path.append(default_path)
os.environ['PATH'] = os.pathsep.join(search_path)
for path in search_path:
full_path = os.path.join(path, exe)
if _is_executable_file_or_link(full_path):
return full_path
logger.trace(
'\'{0}\' could not be found in the following search path: '
'\'{1}\''.format(exe, search_path))

for path in search_path.split(os.pathsep):
full_path = os.path.join(path, exe)
if os.access(full_path, os.X_OK):
return full_path
raise exc.LibTmuxException(
'{0!r} could not be found in the following search '
'path: {1!r}'.format(
exe, search_path
)
)
logger.error('No executable was passed to be searched by which')
return None


Expand Down