summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKevin Van Brunt <kmvanbrunt@gmail.com>2019-05-06 00:36:39 -0400
committerKevin Van Brunt <kmvanbrunt@gmail.com>2019-05-06 00:36:39 -0400
commitfc41d86b275ba4bbd2dbd76c766dbc2abc138021 (patch)
tree70db8b01eb2a113ab28468d6de29a4ed3c99c744
parent4ebcf42f7fe51216019699b1a9edf2af4f3cb7b6 (diff)
downloadcmd2-git-fc41d86b275ba4bbd2dbd76c766dbc2abc138021.tar.gz
Exceptions occurring in tab completion functions are now printed to stderr before returning control back to readline
-rw-r--r--CHANGELOG.md2
-rw-r--r--cmd2/cmd2.py27
-rw-r--r--tests/test_completion.py18
3 files changed, 41 insertions, 6 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e5142c9b..bead9ce1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,8 @@
* `StdSim.buffer.write()` now flushes when the wrapped stream uses line buffering and the bytes being written
contain a newline or carriage return. This helps when `pyscript` is echoing the output of a shell command
since the output will print at the same frequency as when the command is run in a terminal.
+ * Exceptions occurring in tab completion functions are now printed to stderr before returning control back to
+ readline. This makes debugging a lot easier since readline suppresses these exceptions.
* **Python 3.4 EOL notice**
* Python 3.4 reached its [end of life](https://www.python.org/dev/peps/pep-0429/) on March 18, 2019
* This is the last release of `cmd2` which will support Python 3.4
diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py
index a7b60b1a..3c1c8d2c 100644
--- a/cmd2/cmd2.py
+++ b/cmd2/cmd2.py
@@ -1362,16 +1362,13 @@ class Cmd(cmd.Cmd):
# Display matches using actual display function. This also redraws the prompt and line.
orig_pyreadline_display(matches_to_display)
- # ----- Methods which override stuff in cmd -----
-
- def complete(self, text: str, state: int) -> Optional[str]:
- """Override of command method which returns the next possible completion for 'text'.
+ def _complete_worker(self, text: str, state: int) -> Optional[str]:
+ """The actual worker function for tab completion which is called by complete() and returns
+ the next possible completion for 'text'.
If a command has not been entered, then complete against command list.
Otherwise try to call complete_<command> to get list of completions.
- This method gets called directly by readline because it is set as the tab-completion function.
-
This completer function is called as complete(text, state), for state in 0, 1, 2, …, until it returns a
non-string value. It should return the next possible completion starting with text.
@@ -1581,6 +1578,24 @@ class Cmd(cmd.Cmd):
except IndexError:
return None
+ def complete(self, text: str, state: int) -> Optional[str]:
+ """Override of cmd2's complete method which returns the next possible completion for 'text'
+
+ This method gets called directly by readline. Since readline suppresses any exception raised
+ in completer functions, they can be difficult to debug. Therefore this function wraps the
+ actual tab completion logic and prints to stderr any exception that occurs before returning
+ control to readline.
+
+ :param text: the current word that user is typing
+ :param state: non-negative integer
+ """
+ # noinspection PyBroadException
+ try:
+ return self._complete_worker(text, state)
+ except Exception as e:
+ self.perror(e)
+ return None
+
def _autocomplete_default(self, text: str, line: str, begidx: int, endidx: int,
argparser: argparse.ArgumentParser) -> List[str]:
"""Default completion function for argparse commands."""
diff --git a/tests/test_completion.py b/tests/test_completion.py
index 23843012..158856ec 100644
--- a/tests/test_completion.py
+++ b/tests/test_completion.py
@@ -77,6 +77,12 @@ class CompletionsExample(cmd2.Cmd):
num_strs = ['2', '11', '1']
return self.basic_complete(text, line, begidx, endidx, num_strs)
+ def do_test_raise_exception(self, args):
+ pass
+
+ def complete_test_raise_exception(self, text, line, begidx, endidx):
+ raise IndexError("You are out of bounds!!")
+
@pytest.fixture
def cmd2_app():
@@ -120,6 +126,18 @@ def test_complete_bogus_command(cmd2_app):
first_match = complete_tester(text, line, begidx, endidx, cmd2_app)
assert first_match is None
+def test_complete_exception(cmd2_app, capsys):
+ text = ''
+ line = 'test_raise_exception {}'.format(text)
+ endidx = len(line)
+ begidx = endidx - len(text)
+
+ first_match = complete_tester(text, line, begidx, endidx, cmd2_app)
+ out, err = capsys.readouterr()
+
+ assert first_match is None
+ assert "IndexError" in err
+
def test_complete_macro(base_app, request):
# Create the macro
out, err = run_cmd(base_app, 'macro create fake pyscript {1}')