diff options
Diffstat (limited to 'cmd2.py')
-rwxr-xr-x | cmd2.py | 72 |
1 files changed, 40 insertions, 32 deletions
@@ -165,7 +165,7 @@ def set_use_arg_list(val): def flag_based_complete(text, line, begidx, endidx, flag_dict, default_completer=None): """ - Tab completes based on a particular flag preceding the text being completed + Tab completes based on a particular flag preceding the token being completed :param text: str - the string prefix we are attempting to match (all returned matches must begin with it) :param line: str - the current input line with leading whitespace removed :param begidx: int - the beginning index of the prefix text @@ -181,7 +181,7 @@ def flag_based_complete(text, line, begidx, endidx, flag_dict, default_completer :return: List[str] - a list of possible tab completions """ - # Get all tokens prior to text being completed + # Get all tokens prior to token being completed try: prev_space_index = max(line.rfind(' ', 0, begidx), 0) tokens = shlex.split(line[:prev_space_index], posix=POSIX_SHLEX) @@ -189,7 +189,6 @@ def flag_based_complete(text, line, begidx, endidx, flag_dict, default_completer # Invalid syntax for shlex (Probably due to missing closing quote) return [] - # Nothing to do if len(tokens) == 0: return [] @@ -199,7 +198,7 @@ def flag_based_complete(text, line, begidx, endidx, flag_dict, default_completer # Must have at least the command and one argument for a flag to be present if len(tokens) > 1: - # Get the argument that precedes the text being completed + # Get the argument that precedes the token being completed flag = tokens[-1] # Check if the flag is in the dictionary @@ -242,12 +241,12 @@ def index_based_complete(text, line, begidx, endidx, index_dict, default_complet values - there are two types of values 1. iterable list of strings to match against (dictionaries, lists, etc.) 2. function that performs tab completion (ex: path_complete) - :param default_completer: callable - an optional completer to use if the text being completed is not at + :param default_completer: callable - an optional completer to use if the token being completed is not at any index in index_dict :return: List[str] - a list of possible tab completions """ - # Get all tokens prior to text being completed + # Get all tokens prior to token being completed try: prev_space_index = max(line.rfind(' ', 0, begidx), 0) tokens = shlex.split(line[:prev_space_index], posix=POSIX_SHLEX) @@ -260,7 +259,7 @@ def index_based_complete(text, line, begidx, endidx, index_dict, default_complet # Must have at least the command if len(tokens) > 0: - # Get the index of the text being completed + # Get the index of the token being completed index = len(tokens) # Check if the index is in the dictionary @@ -300,20 +299,32 @@ def path_complete(text, line, begidx, endidx, dir_exe_only=False, dir_only=False :return: List[str] - a list of possible tab completions """ + # Get all tokens prior to token being completed + try: + prev_space_index = max(line.rfind(' ', 0, begidx), 0) + tokens = shlex.split(line[:prev_space_index], posix=POSIX_SHLEX) + except ValueError: + # Invalid syntax for shlex (Probably due to missing closing quote) + return [] + + if len(tokens) == 0: + return [] + # Determine if a trailing separator should be appended to directory completions add_trailing_sep_if_dir = False if endidx == len(line) or (endidx < len(line) and line[endidx] != os.path.sep): add_trailing_sep_if_dir = True add_sep_after_tilde = False - # If no path and no search text has been entered, then search in the CWD for * - if not text and line[begidx - 1] == ' ' and (begidx >= len(line) or line[begidx] == ' '): + + # Readline places begidx after ~ and path separators (/) so we need to extract any directory + # path that appears before the search text + dirname = line[prev_space_index + 1:begidx] + + # If no directory path and no search text has been entered, then search in the CWD for * + if not dirname and not text: search_str = os.path.join(os.getcwd(), '*') else: - # Parse out the path being searched - prev_space_index = line.rfind(' ', 0, begidx) - dirname = line[prev_space_index + 1:begidx] - # Purposely don't match any path containing wildcards - what we are doing is complicated enough! wildcards = ['*', '?'] for wildcard in wildcards: @@ -354,7 +365,7 @@ def path_complete(text, line, begidx, endidx, dir_exe_only=False, dir_only=False # If there is a single completion if len(completions) == 1: - # If it is a file and we are at the end of the line, then add a space for convenience + # If it is a file and we are at the end of the line, then add a space if os.path.isfile(path_completions[0]) and endidx == len(line): completions[0] += ' ' # If tilde was expanded without a separator, prepend one @@ -1340,7 +1351,7 @@ class Cmd(cmd.Cmd): Override of parent class method to handle tab completing subcommands """ - # Get all tokens prior to text being completed + # Get all tokens prior to token being completed try: prev_space_index = max(line.rfind(' ', 0, begidx), 0) tokens = shlex.split(line[:prev_space_index], posix=POSIX_SHLEX) @@ -2030,22 +2041,20 @@ class Cmd(cmd.Cmd): proc = subprocess.Popen(command, stdout=self.stdout, shell=True) proc.communicate() - # noinspection PyUnusedLocal @staticmethod - def _shell_command_complete(text, line, begidx, endidx): - """Method called to complete an input line by environment PATH executable completion. + def _get_exes_in_path(starts_with, at_eol): + """ + Called by complete_shell to get names of executables in a user's path - :param text: str - the string prefix we are attempting to match (all returned matches must begin with it) - :param line: str - the current input line with leading whitespace removed - :param begidx: int - the beginning index of the prefix text - :param endidx: int - the ending index of the prefix text + :param starts_with: str - what the exes should start with + :param at_eol: bool - tells if the user's cursor is at the end of the command line :return: List[str] - a list of possible tab completions """ # Purposely don't match any executable containing wildcards wildcards = ['*', '?'] for wildcard in wildcards: - if wildcard in text: + if wildcard in starts_with: return [] # Get a list of every directory in the PATH environment variable and ignore symbolic links @@ -2054,9 +2063,9 @@ class Cmd(cmd.Cmd): # Use a set to store exe names since there can be duplicates exes = set() - # Find every executable file in the PATH that matches the pattern + # Find every executable file in the user's path that matches the pattern for path in paths: - full_path = os.path.join(path, text) + full_path = os.path.join(path, starts_with) matches = [f for f in glob.glob(full_path + '*') if os.path.isfile(f) and os.access(f, os.X_OK)] for match in matches: @@ -2067,13 +2076,13 @@ class Cmd(cmd.Cmd): results.sort() # If there is a single completion and we are at end of the line, then add a space at the end for convenience - if len(results) == 1 and endidx == len(line): + if len(results) == 1 and at_eol: results[0] += ' ' return results def complete_shell(self, text, line, begidx, endidx): - """Handles tab completion of executable commands and local file system paths. + """Handles tab completion of executable commands and local file system paths for the shell command :param text: str - the string prefix we are attempting to match (all returned matches must begin with it) :param line: str - the current input line with leading whitespace removed @@ -2082,7 +2091,7 @@ class Cmd(cmd.Cmd): :return: List[str] - a list of possible tab completions """ - # Get all tokens prior to text being completed + # Get all tokens prior to token being completed try: prev_space_index = max(line.rfind(' ', 0, begidx), 0) tokens = shlex.split(line[:prev_space_index], posix=POSIX_SHLEX) @@ -2090,7 +2099,6 @@ class Cmd(cmd.Cmd): # Invalid syntax for shlex (Probably due to missing closing quote) return [] - # Nothing to do if len(tokens) == 0: return [] @@ -2106,9 +2114,10 @@ class Cmd(cmd.Cmd): if len(cmd_token) == 0: return [] + # Look for path characters in the token if not (cmd_token.startswith('~') or os.path.sep in cmd_token): # No path characters are in this token, it is OK to try shell command completion. - command_completions = self._shell_command_complete(text, line, begidx, endidx) + command_completions = self._get_exes_in_path(text, endidx == len(line)) if command_completions: return command_completions @@ -2116,9 +2125,8 @@ class Cmd(cmd.Cmd): # If we have no results, try path completion to find the shell commands return path_complete(text, line, begidx, endidx, dir_exe_only=True) - # Shell command has been completed + # We are past the shell command, so do path completion else: - # Do path completion return path_complete(text, line, begidx, endidx) # noinspection PyBroadException |