summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKevin Van Brunt <kmvanbrunt@gmail.com>2020-02-18 12:10:34 -0500
committerKevin Van Brunt <kmvanbrunt@gmail.com>2020-02-18 12:10:34 -0500
commitd214709eecf2208b5edb6c52af69a0d76973e595 (patch)
tree3d5096aba973b3eab8d9defed51d0964e472157c
parente1dc7637ab99bedaafa421b7fd499ed6302008f1 (diff)
downloadcmd2-git-d214709eecf2208b5edb6c52af69a0d76973e595.tar.gz
Fixed issue where argparse completion errors were being rewrapped as _ActionCompletionError in some cases
-rw-r--r--cmd2/argparse_completer.py23
-rw-r--r--cmd2/cmd2.py2
-rw-r--r--tests/test_argparse_completer.py42
3 files changed, 58 insertions, 9 deletions
diff --git a/cmd2/argparse_completer.py b/cmd2/argparse_completer.py
index a0c19959..add8868c 100644
--- a/cmd2/argparse_completer.py
+++ b/cmd2/argparse_completer.py
@@ -95,8 +95,13 @@ class _ArgumentState:
self.max = self.action.nargs
+class _ArgparseCompletionError(CompletionError):
+ """CompletionError specific to argparse-based tab completion"""
+ pass
+
+
# noinspection PyProtectedMember
-class _ActionCompletionError(CompletionError):
+class _ActionCompletionError(_ArgparseCompletionError):
def __init__(self, arg_action: argparse.Action, completion_error: CompletionError) -> None:
"""
Adds action-specific information to a CompletionError. These are raised when
@@ -107,19 +112,19 @@ class _ActionCompletionError(CompletionError):
# Indent all lines of completion_error
indented_error = textwrap.indent(str(completion_error), ' ')
- error = ("\nError tab completing {}:\n"
- "{}\n".format(argparse._get_action_name(arg_action), indented_error))
+ error = ("Error tab completing {}:\n"
+ "{}".format(argparse._get_action_name(arg_action), indented_error))
super().__init__(ansi.style_error(error))
# noinspection PyProtectedMember
-class _UnfinishedFlagError(CompletionError):
+class _UnfinishedFlagError(_ArgparseCompletionError):
def __init__(self, flag_arg_state: _ArgumentState) -> None:
"""
CompletionError which occurs when the user has not finished the current flag
:param flag_arg_state: information about the unfinished flag action
"""
- error = "\nError: argument {}: {} ({} entered)\n".\
+ error = "Error: argument {}: {} ({} entered)".\
format(argparse._get_action_name(flag_arg_state.action),
generate_range_error(flag_arg_state.min, flag_arg_state.max),
flag_arg_state.count)
@@ -127,7 +132,7 @@ class _UnfinishedFlagError(CompletionError):
# noinspection PyProtectedMember
-class _NoResultsError(CompletionError):
+class _NoResultsError(_ArgparseCompletionError):
def __init__(self, parser: argparse.ArgumentParser, arg_action: argparse.Action) -> None:
"""
CompletionError which occurs when there are no results. If hinting is allowed, then its message will
@@ -145,7 +150,7 @@ class _NoResultsError(CompletionError):
formatter.start_section("Hint")
formatter.add_argument(arg_action)
formatter.end_section()
- hint_str = '\n' + formatter.format_help()
+ hint_str = formatter.format_help()
super().__init__(hint_str)
@@ -416,6 +421,8 @@ class ArgparseCompleter:
try:
completion_results = self._complete_for_arg(flag_arg_state.action, text, line,
begidx, endidx, consumed_arg_values)
+ except _ArgparseCompletionError as ex:
+ raise ex
except CompletionError as ex:
raise _ActionCompletionError(flag_arg_state.action, ex)
@@ -439,6 +446,8 @@ class ArgparseCompleter:
try:
completion_results = self._complete_for_arg(pos_arg_state.action, text, line,
begidx, endidx, consumed_arg_values)
+ except _ArgparseCompletionError as ex:
+ raise ex
except CompletionError as ex:
raise _ActionCompletionError(pos_arg_state.action, ex)
diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py
index 49273b51..60d5463a 100644
--- a/cmd2/cmd2.py
+++ b/cmd2/cmd2.py
@@ -1420,7 +1420,7 @@ class Cmd(cmd.Cmd):
err_str = str(e)
if err_str:
# Don't print error and redraw the prompt unless the error has length
- ansi.style_aware_write(sys.stdout, err_str + '\n')
+ ansi.style_aware_write(sys.stdout, '\n' + err_str + '\n')
rl_force_redisplay()
return None
except Exception as e:
diff --git a/tests/test_argparse_completer.py b/tests/test_argparse_completer.py
index 2619d053..83cee30f 100644
--- a/tests/test_argparse_completer.py
+++ b/tests/test_argparse_completer.py
@@ -30,6 +30,8 @@ positional_choices = ['the', 'positional', 'choices']
completions_from_function = ['completions', 'function', 'fairly', 'complete']
completions_from_method = ['completions', 'method', 'missed', 'spot']
+AP_COMP_ERROR_TEXT = "SHOULD ONLY BE THIS TEXT"
+
def choices_function() -> List[str]:
"""Function that provides choices"""
@@ -53,7 +55,7 @@ def completer_takes_arg_tokens(text: str, line: str, begidx: int, endidx: int,
return basic_complete(text, line, begidx, endidx, match_against)
-# noinspection PyMethodMayBeStatic,PyUnusedLocal
+# noinspection PyMethodMayBeStatic,PyUnusedLocal,PyProtectedMember
class AutoCompleteTester(cmd2.Cmd):
"""Cmd2 app that exercises ArgparseCompleter class"""
def __init__(self, *args, **kwargs):
@@ -181,6 +183,7 @@ class AutoCompleteTester(cmd2.Cmd):
choices=one_or_more_choices)
nargs_parser.add_argument("--optional", help="a flag with an optional value", nargs=argparse.OPTIONAL,
choices=optional_choices)
+ # noinspection PyTypeChecker
nargs_parser.add_argument("--range", help="a flag with nargs range", nargs=(1, 2),
choices=range_choices)
nargs_parser.add_argument("--remainder", help="a flag wanting remaining", nargs=argparse.REMAINDER,
@@ -232,6 +235,24 @@ class AutoCompleteTester(cmd2.Cmd):
pass
############################################################################################################
+ # Begin code related to _ArgparseCompletionError
+ ############################################################################################################
+ def raise_argparse_completion_error(self):
+ """Raises ArgparseCompletionError to make sure it gets raised as is"""
+ from cmd2.argparse_completer import _ArgparseCompletionError
+ raise _ArgparseCompletionError(AP_COMP_ERROR_TEXT)
+
+ ap_comp_error_parser = Cmd2ArgumentParser()
+ ap_comp_error_parser.add_argument('pos_ap_comp_err', help='pos ap completion error',
+ choices_method=raise_argparse_completion_error)
+ ap_comp_error_parser.add_argument('--flag_ap_comp_err', help='flag ap completion error',
+ choices_method=raise_argparse_completion_error)
+
+ @with_argparser(ap_comp_error_parser)
+ def do_raise_ap_completion_error(self, args: argparse.Namespace) -> None:
+ pass
+
+ ############################################################################################################
# Begin code related to receiving arg_tokens
############################################################################################################
arg_tokens_parser = Cmd2ArgumentParser()
@@ -772,6 +793,25 @@ def test_completion_error(ac_app, capsys, args, text):
assert "{} broke something".format(text) in out
+@pytest.mark.parametrize('arg', [
+ # Exercise positional arg that raises _ArgparseCompletionError
+ '',
+
+ # Exercise flag arg that raises _ArgparseCompletionError
+ '--flag_ap_comp_err'
+])
+def test_argparse_completion_error(ac_app, capsys, arg):
+ text = ''
+ line = 'raise_ap_completion_error {} {}'.format(arg, text)
+ endidx = len(line)
+ begidx = endidx - len(text)
+
+ first_match = complete_tester(text, line, begidx, endidx, ac_app)
+ assert first_match is None
+ out, err = capsys.readouterr()
+ assert out.strip() == AP_COMP_ERROR_TEXT
+
+
@pytest.mark.parametrize('command_and_args, completions', [
# Exercise a choices function that receives arg_tokens dictionary
('arg_tokens choice subcmd', ['choice', 'subcmd']),