summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKevin Van Brunt <kmvanbrunt@gmail.com>2020-02-18 12:59:46 -0500
committerKevin Van Brunt <kmvanbrunt@gmail.com>2020-02-18 12:59:46 -0500
commitd3deca3c99c299149a30aa3ec760dc17f8abc456 (patch)
tree58ac6df4330780cec94e854782a7bfae23c16589
parent065536a484bb705e1e6b7971fc4c8efdb637185e (diff)
downloadcmd2-git-d3deca3c99c299149a30aa3ec760dc17f8abc456.tar.gz
Added apply_style to CompletionError
Simplified error class structure in argparse_completer.py
-rw-r--r--cmd2/argparse_completer.py56
-rw-r--r--cmd2/cmd2.py8
-rw-r--r--cmd2/utils.py16
-rwxr-xr-xexamples/basic_completion.py3
-rw-r--r--tests/test_argparse_completer.py39
5 files changed, 33 insertions, 89 deletions
diff --git a/cmd2/argparse_completer.py b/cmd2/argparse_completer.py
index add8868c..d75c04d0 100644
--- a/cmd2/argparse_completer.py
+++ b/cmd2/argparse_completer.py
@@ -10,7 +10,6 @@ import argparse
import inspect
import numbers
import shutil
-import textwrap
from collections import deque
from typing import Dict, List, Optional, Union
@@ -95,30 +94,8 @@ class _ArgumentState:
self.max = self.action.nargs
-class _ArgparseCompletionError(CompletionError):
- """CompletionError specific to argparse-based tab completion"""
- pass
-
-
-# noinspection PyProtectedMember
-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
- non-argparse related errors occur during tab completion.
- :param arg_action: action being tab completed
- :param completion_error: error that occurred
- """
- # Indent all lines of completion_error
- indented_error = textwrap.indent(str(completion_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(_ArgparseCompletionError):
+class _UnfinishedFlagError(CompletionError):
def __init__(self, flag_arg_state: _ArgumentState) -> None:
"""
CompletionError which occurs when the user has not finished the current flag
@@ -128,11 +105,11 @@ class _UnfinishedFlagError(_ArgparseCompletionError):
format(argparse._get_action_name(flag_arg_state.action),
generate_range_error(flag_arg_state.min, flag_arg_state.max),
flag_arg_state.count)
- super().__init__(ansi.style_error(error))
+ super().__init__(error)
# noinspection PyProtectedMember
-class _NoResultsError(_ArgparseCompletionError):
+class _NoResultsError(CompletionError):
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
@@ -151,7 +128,8 @@ class _NoResultsError(_ArgparseCompletionError):
formatter.add_argument(arg_action)
formatter.end_section()
hint_str = formatter.format_help()
- super().__init__(hint_str)
+ # Set apply_style to False because we don't want hints to look like errors
+ super().__init__(hint_str, apply_style=False)
# noinspection PyProtectedMember
@@ -253,9 +231,9 @@ class ArgparseCompleter:
if arg_action == completer_action:
return
- error = ansi.style_error("\nError: argument {}: not allowed with argument {}\n".
- format(argparse._get_action_name(arg_action),
- argparse._get_action_name(completer_action)))
+ error = ("Error: argument {}: not allowed with argument {}\n".
+ format(argparse._get_action_name(arg_action),
+ argparse._get_action_name(completer_action)))
raise CompletionError(error)
# Mark that this action completed the group
@@ -418,13 +396,8 @@ class ArgparseCompleter:
# Check if we are completing a flag's argument
if flag_arg_state is not None:
- 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)
+ completion_results = self._complete_for_arg(flag_arg_state.action, text, line,
+ begidx, endidx, consumed_arg_values)
# If we have results, then return them
if completion_results:
@@ -443,13 +416,8 @@ class ArgparseCompleter:
action = remaining_positionals.popleft()
pos_arg_state = _ArgumentState(action)
- 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)
+ completion_results = self._complete_for_arg(pos_arg_state.action, text, line,
+ begidx, endidx, consumed_arg_values)
# If we have results, then return them
if completion_results:
diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py
index 60d5463a..67304636 100644
--- a/cmd2/cmd2.py
+++ b/cmd2/cmd2.py
@@ -1416,10 +1416,12 @@ class Cmd(cmd.Cmd):
except IndexError:
return None
- except CompletionError as e:
- err_str = str(e)
+ except CompletionError as ex:
+ err_str = str(ex)
+ # Don't print error and redraw the prompt unless the error has length
if err_str:
- # Don't print error and redraw the prompt unless the error has length
+ if ex.apply_style:
+ err_str = ansi.style_error(err_str)
ansi.style_aware_write(sys.stdout, '\n' + err_str + '\n')
rl_force_redisplay()
return None
diff --git a/cmd2/utils.py b/cmd2/utils.py
index b6b45891..6a67c43f 100644
--- a/cmd2/utils.py
+++ b/cmd2/utils.py
@@ -75,12 +75,26 @@ def str_to_bool(val: str) -> bool:
class CompletionError(Exception):
"""
Raised during tab completion operations to report any sort of error you want printed by the ArgparseCompleter
+ This can also be used just to display a message, even if it's not an error. ArgparseCompleter raises
+ CompletionErrors to display tab completion hints and sets apply_style to False so hints aren't colored
+ like error text.
Example use cases
- Reading a database to retrieve a tab completion data set failed
- A previous command line argument that determines the data set being completed is invalid
+ - Tab completion hints
"""
- pass
+ def __init__(self, *args, apply_style: bool = True, **kwargs):
+ """
+ Initializer for CompletionError
+ :param apply_style: If True, then ansi.style_error will be applied to the message text when printed.
+ Set to False in cases where the message text already has the desired style.
+ Defaults to True.
+ """
+ self.apply_style = apply_style
+
+ # noinspection PyArgumentList
+ super().__init__(*args, **kwargs)
class Settable:
diff --git a/examples/basic_completion.py b/examples/basic_completion.py
index b043e157..9523ac67 100755
--- a/examples/basic_completion.py
+++ b/examples/basic_completion.py
@@ -16,7 +16,6 @@ import functools
from typing import List
import cmd2
-from cmd2 import ansi
# List of strings used with completion functions
food_item_strs = ['Pizza', 'Ham', 'Ham Sandwich', 'Potato']
@@ -99,7 +98,7 @@ class BasicCompletion(cmd2.Cmd):
- Reading a database to retrieve a tab completion data set failed
- A previous command line argument that determines the data set being completed is invalid
"""
- raise cmd2.CompletionError(ansi.style_error("This is how a CompletionError behaves"))
+ raise cmd2.CompletionError("This is how a CompletionError behaves")
if __name__ == '__main__':
diff --git a/tests/test_argparse_completer.py b/tests/test_argparse_completer.py
index 83cee30f..9e635a42 100644
--- a/tests/test_argparse_completer.py
+++ b/tests/test_argparse_completer.py
@@ -30,8 +30,6 @@ 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"""
@@ -235,24 +233,6 @@ 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()
@@ -793,25 +773,6 @@ 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']),