diff options
author | Kevin Van Brunt <kmvanbrunt@gmail.com> | 2019-05-06 00:36:39 -0400 |
---|---|---|
committer | Kevin Van Brunt <kmvanbrunt@gmail.com> | 2019-05-06 00:36:39 -0400 |
commit | fc41d86b275ba4bbd2dbd76c766dbc2abc138021 (patch) | |
tree | 70db8b01eb2a113ab28468d6de29a4ed3c99c744 | |
parent | 4ebcf42f7fe51216019699b1a9edf2af4f3cb7b6 (diff) | |
download | cmd2-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.md | 2 | ||||
-rw-r--r-- | cmd2/cmd2.py | 27 | ||||
-rw-r--r-- | tests/test_completion.py | 18 |
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}') |