3737
3838# noinspection PyProtectedMember
3939from .shcommon import _STASH_ROOT , _STASH_HISTORY_FILE , _SYS_STDOUT , _SYS_STDERR
40- from .shcommon import is_binary_file , _STASH_EXTENSION_BIN_PATH
40+ from .shcommon import (
41+ is_binary_file ,
42+ is_true_python_file ,
43+ has_py_extension ,
44+ _STASH_EXTENSION_BIN_PATH ,
45+ )
4146from .shparsers import ShPipeSequence
4247from .shthreads import (
4348 ShBaseThread ,
4853)
4954from .shhistory import ShHistory
5055
56+ _HOME2 = os .path .join (os .path .expanduser ("~" ), "Documents" )
57+ _SITE_PACKAGES = os .path .join (_HOME2 , "site-packages" )
58+ _SITE_PACKAGES_BIN = os .path .join (_SITE_PACKAGES , "bin" )
59+ _SITE_PACKAGES_COMPLETER_MAX_FILES_LIMIT = 100
60+
5161# Default .stashrc file
5262_DEFAULT_RC = r"""BIN_PATH=~/Documents/bin:{bin_ext}:$BIN_PATH
5363SELFUPDATE_TARGET=master
@@ -79,14 +89,16 @@ def __init__(self, stash, parser, expander, no_historyfile=False, debug=False):
7989 self .state = ShState (
8090 environ = dict (
8191 os .environ ,
82- HOME2 = os . path . join ( os . environ [ "HOME" ], "Documents" ) ,
92+ HOME2 = _HOME2 ,
8393 STASH_ROOT = _STASH_ROOT ,
8494 STASH_PY_VERSION = platform .python_version (),
8595 BIN_PATH = os .path .join (_STASH_ROOT , "bin" ),
8696 # Must have a placeholder because it is needed before _DEFAULT_RC is loaded
8797 PROMPT = r"[\W]$ " ,
8898 PYTHONISTA_ROOT = os .path .dirname (sys .executable ),
8999 TMPDIR = os .environ .get ("TMPDIR" , tempfile .gettempdir ()),
100+ # site-packages/bin (console scripts added via pip
101+ SITE_PACKAGES_BIN = _SITE_PACKAGES_BIN ,
90102 ),
91103 sys_stdin = self .stash .io ,
92104 sys_stdout = self .stash .io ,
@@ -170,30 +182,46 @@ def write_error_message(self, stream, msg, prefix=None, log=True):
170182 def find_script_file (self , filename ):
171183 _ , current_state = self .get_current_worker_and_state ()
172184
173- dir_match_found = False
174185 # direct match of the filename, e.g. full path, relative path etc.
175186 for fname in (filename , filename + ".py" , filename + ".sh" ):
176187 if os .path .exists (fname ):
177188 if os .path .isdir (fname ):
178- dir_match_found = True
189+ raise ShIsDirectory ( "%s: is a directory" % filename )
179190 else :
180191 return fname
181192
182193 # Match for commands in current dir and BIN_PATH
183194 # Effectively, current dir is always the first in BIN_PATH
184195 for path in ["." ] + current_state .environ_get ("BIN_PATH" ).split (":" ):
185196 path = os .path .abspath (os .path .expanduser (path ))
186- if os .path .exists (path ):
187- for f in os .listdir (path ):
188- if f == filename or f == filename + ".py" or f == filename + ".sh" :
189- if os .path .isdir (f ):
190- dir_match_found = True
191- else :
192- return os .path .join (path , f )
193- if dir_match_found :
194- raise ShIsDirectory ("%s: is a directory" % filename )
195- else :
196- raise ShFileNotFound ("%s: command not found" % filename )
197+ file_match_found = self ._find_matching_executable (filename , path )
198+ if file_match_found :
199+ return file_match_found
200+
201+ # match for commands in site-packages/bin
202+ # is nothing was found in BIN_PATH
203+ # assume all files in site-packages/bin is executable
204+ path = current_state .environ_get ("SITE_PACKAGES_BIN" )
205+ path = os .path .abspath (path )
206+ file_match_found = self ._find_matching_executable (filename , path )
207+ if file_match_found :
208+ return file_match_found
209+
210+ raise ShFileNotFound ("%s: command not found" % filename )
211+
212+ @staticmethod
213+ def _find_matching_executable (filename , path ):
214+ if not os .path .exists (path ):
215+ return None
216+
217+ for f in os .listdir (path ):
218+ if f in (filename , filename + ".py" , filename + ".sh" ):
219+ found = os .path .join (path , f )
220+ if os .path .isdir (found ):
221+ raise ShIsDirectory ("%s: is a directory" % filename )
222+ return found
223+
224+ return None
197225
198226 def get_all_script_names (self ):
199227 """This function used for completer, whitespaces in names are escaped"""
@@ -207,6 +235,22 @@ def get_all_script_names(self):
207235 f .endswith (".py" ) or f .endswith (".sh" )
208236 ):
209237 all_names .append (f .replace (" " , "\\ " ))
238+
239+ # find executables in site-packages/bin
240+ path = current_state .environ_get ("SITE_PACKAGES_BIN" )
241+ if os .path .exists (path ):
242+ count_unknown = 0
243+ for f in os .listdir (path ):
244+ if not os .path .isdir (f ):
245+ if f .endswith (".py" ) or f .endswith (".sh" ):
246+ all_names .append (f .replace (" " , "\\ " ))
247+ else :
248+ # assume we check only some range of .undefined
249+ if count_unknown < _SITE_PACKAGES_COMPLETER_MAX_FILES_LIMIT :
250+ if is_true_python_file (f ):
251+ all_names .append (f .replace (" " , "\\ " ))
252+ count_unknown += 1
253+
210254 return all_names
211255
212256 def run (
@@ -503,7 +547,8 @@ def run_pipe_sequence(
503547 else :
504548 simple_command_args = simple_command .args
505549
506- if script_file .endswith (".py" ):
550+ # check file extension or if syntax match
551+ if has_py_extension (script_file ) or is_true_python_file (script_file ):
507552 self .exec_py_file (
508553 script_file , simple_command_args , ins , outs , errs
509554 )
0 commit comments