diff options
-rwxr-xr-x | cmd2.py | 26 | ||||
-rwxr-xr-x | examples/tab_completion.py | 8 | ||||
-rw-r--r-- | tests/test_completion.py | 65 |
3 files changed, 82 insertions, 17 deletions
@@ -185,24 +185,27 @@ def flag_based_complete(text, line, begidx, endidx, flag_dict, default_completer # Get all tokens prior to text being completed try: - tokens = shlex.split(line[:begidx], posix=POSIX_SHLEX) + prev_space_index = line.rfind(' ', 0, begidx) + tokens = shlex.split(line[:prev_space_index], posix=POSIX_SHLEX) except ValueError: # Invalid syntax for shlex (Probably due to missing closing quote) return [] completions = [] + flag_processed = False - # Must have at least the command and one argument + # 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 - flag = tokens[len(tokens) - 1] + flag = tokens[-1] # Check if the flag is in the dictionary if flag in flag_dict: # Check if this flag does completions using an Iterable if isinstance(flag_dict[flag], collections.Iterable): + flag_processed = True strs_to_match = flag_dict[flag] completions = [cur_str for cur_str in strs_to_match if cur_str.startswith(text)] @@ -212,12 +215,13 @@ def flag_based_complete(text, line, begidx, endidx, flag_dict, default_completer # Otherwise check if this flag does completions with a function elif callable(flag_dict[flag]): + flag_processed = True completer_func = flag_dict[flag] completions = completer_func(text, line, begidx, endidx) - # Otherwise check if there is a default completer - elif default_completer is not None: - completions = default_completer(text, line, begidx, endidx) + # Check if we need to run the default completer + if default_completer is not None and not flag_processed: + completions = default_completer(text, line, begidx, endidx) completions.sort() return completions @@ -243,7 +247,8 @@ def index_based_complete(text, line, begidx, endidx, index_dict, default_complet # Get all tokens prior to text being completed try: - tokens = shlex.split(line[:begidx], posix=POSIX_SHLEX) + prev_space_index = line.rfind(' ', 0, begidx) + tokens = shlex.split(line[:prev_space_index], posix=POSIX_SHLEX) except ValueError: # Invalid syntax for shlex (Probably due to missing closing quote) return [] @@ -268,7 +273,7 @@ def index_based_complete(text, line, begidx, endidx, index_dict, default_complet if len(completions) == 1 and endidx == len(line): completions[0] += ' ' - # Otherwise check if this flag does completions with a function + # Otherwise check if this index does completions with a function elif callable(index_dict[index]): completer_func = index_dict[index] completions = completer_func(text, line, begidx, endidx) @@ -1343,7 +1348,8 @@ class Cmd(cmd.Cmd): # Get all tokens prior to text being completed try: - tokens = shlex.split(line[:begidx], posix=POSIX_SHLEX) + prev_space_index = line.rfind(' ', 0, begidx) + tokens = shlex.split(line[:prev_space_index], posix=POSIX_SHLEX) except ValueError: # Invalid syntax for shlex (Probably due to missing closing quote) return [] @@ -2103,7 +2109,7 @@ class Cmd(cmd.Cmd): # Readline places begidx after ~ and path separators (/) so we need to get the whole token # and see if it begins with a possible path in case we need to do path completion # to find the shell command executables - cur_token = tokens[len(tokens) - 1] + cur_token = tokens[-1] if not (cur_token.startswith('~') or os.path.sep in cur_token): # No path characters are in this token, it is OK to try shell command completion. diff --git a/examples/tab_completion.py b/examples/tab_completion.py index 400775a8..6c16e63b 100755 --- a/examples/tab_completion.py +++ b/examples/tab_completion.py @@ -6,7 +6,7 @@ import argparse import functools import cmd2 -from cmd2 import with_argparser, with_argument_list, flag_based_complete, index_based_complete +from cmd2 import with_argparser, with_argument_list, flag_based_complete, index_based_complete, path_complete # List of strings used with flag and index based completion functions food_item_strs = ['Pizza', 'Hamburger', 'Ham', 'Potato'] @@ -19,6 +19,8 @@ flag_dict = \ '--food': food_item_strs, # Tab-complete food items after --food flag in command line '-s': sport_item_strs, # Tab-complete sport items after -s flag in command line '--sport': sport_item_strs, # Tab-complete sport items after --sport flag in command line + '-o': path_complete, # Tab-complete using path_complete function after -o flag in command line + '--other': path_complete, # Tab-complete using path_complete function after --other flag in command line } # Dictionary used with index based completion functions @@ -26,6 +28,7 @@ index_dict = \ { 1: food_item_strs, # Tab-complete food items at index 1 in command line 2: sport_item_strs, # Tab-complete sport items at index 2 in command line + 3: path_complete, # Tab-complete using path_complete function at index 3 in command line } @@ -39,6 +42,7 @@ class TabCompleteExample(cmd2.Cmd): add_item_group = add_item_parser.add_mutually_exclusive_group() add_item_group.add_argument('-f', '--food', help='Adds food item') add_item_group.add_argument('-s', '--sport', help='Adds sport item') + add_item_group.add_argument('-o', '--other', help='Adds other item') @with_argparser(add_item_parser) def do_add_item(self, args): @@ -47,6 +51,8 @@ class TabCompleteExample(cmd2.Cmd): add_item = args.food elif args.sport: add_item = args.sport + elif args.other: + add_item = args.other else: add_item = 'no items' diff --git a/tests/test_completion.py b/tests/test_completion.py index dcebd5f6..4a8d0eae 100644 --- a/tests/test_completion.py +++ b/tests/test_completion.py @@ -356,9 +356,9 @@ def test_path_completion_user_expansion(): # Run path with just a tilde text = '' if sys.platform.startswith('win'): - line = 'shell dir ~\{}'.format(text) + line = 'shell dir ~{}'.format(text) else: - line = 'shell ls ~/{}'.format(text) + line = 'shell ls ~{}'.format(text) endidx = len(line) begidx = endidx - len(text) completions_tilde = path_complete(text, line, begidx, endidx) @@ -394,7 +394,15 @@ food_item_strs = ['Pizza', 'Hamburger', 'Ham', 'Potato'] sport_item_strs = ['Bat', 'Basket', 'Basketball', 'Football'] # Dictionary used with flag based completion functions -flag_dict = {'-f': food_item_strs, '-s': sport_item_strs} +flag_dict = \ + { + '-f': food_item_strs, # Tab-complete food items after -f flag in command line + '--food': food_item_strs, # Tab-complete food items after --food flag in command line + '-s': sport_item_strs, # Tab-complete sport items after -s flag in command line + '--sport': sport_item_strs, # Tab-complete sport items after --sport flag in command line + '-o': path_complete, # Tab-complete using path_complete function after -o flag in command line + '--other': path_complete, # Tab-complete using path_complete function after --other flag in command line + } def test_flag_based_completion_single_end(): text = 'Pi' @@ -438,8 +446,33 @@ def test_flag_based_default_completer(request): assert flag_based_complete(text, line, begidx, endidx, flag_dict, path_complete) == ['conftest.py '] +def test_flag_based_callable_completer(request): + test_dir = os.path.dirname(request.module.__file__) + + text = 'c' + path = os.path.join(test_dir, text) + line = 'list_food -o {}'.format(path) + + endidx = len(line) + begidx = endidx - len(text) + + assert flag_based_complete(text, line, begidx, endidx, flag_dict, path_complete) == ['conftest.py '] + +def test_flag_based_completion_syntax_err(): + text = 'Pi' + line = 'list_food -f " Pi' + endidx = len(line) + begidx = endidx - len(text) + + assert flag_based_complete(text, line, begidx, endidx, flag_dict) == [] + # Dictionary used with index based completion functions -index_dict = {1: food_item_strs, 2: sport_item_strs} +index_dict = \ + { + 1: food_item_strs, # Tab-complete food items at index 1 in command line + 2: sport_item_strs, # Tab-complete sport items at index 2 in command line + 3: path_complete, # Tab-complete using path_complete function at index 3 in command line + } def test_index_based_completion_single_end(): text = 'Foo' @@ -476,12 +509,32 @@ def test_index_based_default_completer(request): text = 'c' path = os.path.join(test_dir, text) + line = 'command Pizza Bat Computer {}'.format(path) + + endidx = len(line) + begidx = endidx - len(text) + + assert index_based_complete(text, line, begidx, endidx, index_dict, path_complete) == ['conftest.py '] + +def test_index_based_callable_completer(request): + test_dir = os.path.dirname(request.module.__file__) + + text = 'c' + path = os.path.join(test_dir, text) line = 'command Pizza Bat {}'.format(path) endidx = len(line) begidx = endidx - len(text) - assert flag_based_complete(text, line, begidx, endidx, flag_dict, path_complete) == ['conftest.py '] + assert index_based_complete(text, line, begidx, endidx, index_dict) == ['conftest.py '] + +def test_index_based_completion_syntax_err(): + text = 'Foo' + line = 'command "Pizza Foo' + endidx = len(line) + begidx = endidx - len(text) + + assert index_based_complete(text, line, begidx, endidx, index_dict) == [] def test_parseline_command_and_args(cmd2_app): @@ -727,7 +780,7 @@ def test_cmd2_help_subcommand_completion_single_mid(sc_app): def test_cmd2_help_subcommand_completion_multiple(sc_app): text = '' - line = 'help base' + line = 'help base ' endidx = len(line) begidx = endidx - len(text) assert sc_app.complete_help(text, line, begidx, endidx) == ['bar', 'foo'] |