summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xcmd2.py26
-rwxr-xr-xexamples/tab_completion.py8
-rw-r--r--tests/test_completion.py65
3 files changed, 82 insertions, 17 deletions
diff --git a/cmd2.py b/cmd2.py
index 7e3a3dff..fd2fd498 100755
--- a/cmd2.py
+++ b/cmd2.py
@@ -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']