summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTodd Leonhardt <todd.leonhardt@gmail.com>2019-06-30 23:55:32 -0400
committerGitHub <noreply@github.com>2019-06-30 23:55:32 -0400
commitab64843f638be59f252197f24e38819821b02490 (patch)
tree5865112ef335010ec6fcec2be7998ca8700606e3
parent6787a3d412a1fba8783dc393d8d4af124220f4b9 (diff)
parentd932357389015c1e01a99d0f4b3faad62bbd88f3 (diff)
downloadcmd2-git-ab64843f638be59f252197f24e38819821b02490.tar.gz
Merge pull request #707 from python-cmd2/redirect_complete
Redirect complete
-rw-r--r--cmd2/cmd2.py52
-rw-r--r--tests/test_completion.py75
2 files changed, 115 insertions, 12 deletions
diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py
index d41a631d..97fb8cd0 100644
--- a/cmd2/cmd2.py
+++ b/cmd2/cmd2.py
@@ -1200,7 +1200,7 @@ class Cmd(cmd.Cmd):
def _redirect_complete(self, text: str, line: str, begidx: int, endidx: int, compfunc: Callable) -> List[str]:
"""Called by complete() as the first tab completion function for all commands
- It determines if it should tab complete for redirection (|, <, >, >>) or use the
+ It determines if it should tab complete for redirection (|, >, >>) or use the
completer function for the current command
:param text: the string prefix we are attempting to match (all returned matches must begin with it)
@@ -1219,28 +1219,58 @@ class Cmd(cmd.Cmd):
if not raw_tokens:
return []
+ # Must at least have the command
if len(raw_tokens) > 1:
- # Check if there are redirection strings prior to the token being completed
- seen_pipe = False
+ # True when command line contains any redirection tokens
has_redirection = False
- for cur_token in raw_tokens[:-1]:
+ # Keep track of state while examining tokens
+ in_pipe = False
+ in_file_redir = False
+ do_shell_completion = False
+ do_path_completion = False
+ prior_token = None
+
+ for cur_token in raw_tokens:
+ # Process redirection tokens
if cur_token in constants.REDIRECTION_TOKENS:
has_redirection = True
+ # Check if we are at a pipe
if cur_token == constants.REDIRECTION_PIPE:
- seen_pipe = True
+ # Do not complete bad syntax (e.g cmd | |)
+ if prior_token == constants.REDIRECTION_PIPE:
+ return []
+
+ in_pipe = True
+ in_file_redir = False
+
+ # Otherwise this is a file redirection token
+ else:
+ if prior_token in constants.REDIRECTION_TOKENS or in_file_redir:
+ # Do not complete bad syntax (e.g cmd | >) (e.g cmd > blah >)
+ return []
+
+ in_pipe = False
+ in_file_redir = True
+
+ # Not a redirection token
+ else:
+ do_shell_completion = False
+ do_path_completion = False
+
+ if prior_token == constants.REDIRECTION_PIPE:
+ do_shell_completion = True
+ elif in_pipe or prior_token in (constants.REDIRECTION_OUTPUT, constants.REDIRECTION_APPEND):
+ do_path_completion = True
- # Get token prior to the one being completed
- prior_token = raw_tokens[-2]
+ prior_token = cur_token
- # If a pipe is right before the token being completed, complete a shell command as the piped process
- if prior_token == constants.REDIRECTION_PIPE:
+ if do_shell_completion:
return self.shell_cmd_complete(text, line, begidx, endidx)
- # Otherwise do path completion either as files to redirectors or arguments to the piped process
- elif prior_token in constants.REDIRECTION_TOKENS or seen_pipe:
+ elif do_path_completion:
return self.path_complete(text, line, begidx, endidx)
# If there were redirection strings anywhere on the command line, then we
diff --git a/tests/test_completion.py b/tests/test_completion.py
index 9157ce84..eea34ba6 100644
--- a/tests/test_completion.py
+++ b/tests/test_completion.py
@@ -6,15 +6,23 @@ Unit/functional testing for readline tab-completion functions in the cmd2.py mod
These are primarily tests related to readline completer functions which handle tab-completion of cmd2/cmd commands,
file system paths, and shell commands.
"""
+# Python 3.5 had some regressions in the unitest.mock module, so use 3rd party mock if available
+try:
+ import mock
+except ImportError:
+ from unittest import mock
+
import argparse
+import enum
import os
import sys
import pytest
+
import cmd2
from cmd2 import utils
-from .conftest import base_app, complete_tester, normalize, run_cmd
from examples.subcommands import SubcommandsExample
+from .conftest import complete_tester, normalize, run_cmd
# List of strings used with completion functions
food_item_strs = ['Pizza', 'Ham', 'Ham Sandwich', 'Potato', 'Cheese "Pizza"']
@@ -854,6 +862,71 @@ def test_quote_as_command(cmd2_app):
first_match = complete_tester(text, line, begidx, endidx, cmd2_app)
assert first_match is None and not cmd2_app.completion_matches
+
+# Used by redirect_complete tests
+class RedirCompType(enum.Enum):
+ SHELL_CMD = 1,
+ PATH = 2,
+ DEFAULT = 3,
+ NONE = 4
+
+ """
+ fake > >
+ fake | grep > file
+ fake | grep > file >
+ """
+
+@pytest.mark.parametrize('line, comp_type', [
+ ('fake', RedirCompType.DEFAULT),
+ ('fake arg', RedirCompType.DEFAULT),
+ ('fake |', RedirCompType.SHELL_CMD),
+ ('fake | grep', RedirCompType.PATH),
+ ('fake | grep arg', RedirCompType.PATH),
+ ('fake | grep >', RedirCompType.PATH),
+ ('fake | grep > >', RedirCompType.NONE),
+ ('fake | grep > file', RedirCompType.NONE),
+ ('fake | grep > file >', RedirCompType.NONE),
+ ('fake | grep > file |', RedirCompType.SHELL_CMD),
+ ('fake | grep > file | grep', RedirCompType.PATH),
+ ('fake | |', RedirCompType.NONE),
+ ('fake | >', RedirCompType.NONE),
+ ('fake >', RedirCompType.PATH),
+ ('fake >>', RedirCompType.PATH),
+ ('fake > >', RedirCompType.NONE),
+ ('fake > |', RedirCompType.SHELL_CMD),
+ ('fake >> file |', RedirCompType.SHELL_CMD),
+ ('fake >> file | grep', RedirCompType.PATH),
+ ('fake > file', RedirCompType.NONE),
+ ('fake > file >', RedirCompType.NONE),
+ ('fake > file >>', RedirCompType.NONE),
+])
+def test_redirect_complete(cmd2_app, monkeypatch, line, comp_type):
+ shell_cmd_complete_mock = mock.MagicMock(name='shell_cmd_complete')
+ monkeypatch.setattr("cmd2.Cmd.shell_cmd_complete", shell_cmd_complete_mock)
+
+ path_complete_mock = mock.MagicMock(name='path_complete')
+ monkeypatch.setattr("cmd2.Cmd.path_complete", path_complete_mock)
+
+ default_complete_mock = mock.MagicMock(name='fake_completer')
+
+ text = ''
+ line = '{} {}'.format(line, text)
+ endidx = len(line)
+ begidx = endidx - len(text)
+
+ cmd2_app._redirect_complete(text, line, begidx, endidx, default_complete_mock)
+
+ if comp_type == RedirCompType.SHELL_CMD:
+ shell_cmd_complete_mock.assert_called_once()
+ elif comp_type == RedirCompType.PATH:
+ path_complete_mock.assert_called_once()
+ elif comp_type == RedirCompType.DEFAULT:
+ default_complete_mock.assert_called_once()
+ else:
+ shell_cmd_complete_mock.assert_not_called()
+ path_complete_mock.assert_not_called()
+ default_complete_mock.assert_not_called()
+
@pytest.fixture
def sc_app():
c = SubcommandsExample()