diff options
author | Kevin Van Brunt <kmvanbrunt@gmail.com> | 2020-02-18 12:59:46 -0500 |
---|---|---|
committer | Kevin Van Brunt <kmvanbrunt@gmail.com> | 2020-02-18 12:59:46 -0500 |
commit | d3deca3c99c299149a30aa3ec760dc17f8abc456 (patch) | |
tree | 58ac6df4330780cec94e854782a7bfae23c16589 | |
parent | 065536a484bb705e1e6b7971fc4c8efdb637185e (diff) | |
download | cmd2-git-d3deca3c99c299149a30aa3ec760dc17f8abc456.tar.gz |
Added apply_style to CompletionError
Simplified error class structure in argparse_completer.py
-rw-r--r-- | cmd2/argparse_completer.py | 56 | ||||
-rw-r--r-- | cmd2/cmd2.py | 8 | ||||
-rw-r--r-- | cmd2/utils.py | 16 | ||||
-rwxr-xr-x | examples/basic_completion.py | 3 | ||||
-rw-r--r-- | tests/test_argparse_completer.py | 39 |
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']), |