From d3deca3c99c299149a30aa3ec760dc17f8abc456 Mon Sep 17 00:00:00 2001 From: Kevin Van Brunt Date: Tue, 18 Feb 2020 12:59:46 -0500 Subject: Added apply_style to CompletionError Simplified error class structure in argparse_completer.py --- cmd2/argparse_completer.py | 56 ++++++++++------------------------------------ cmd2/cmd2.py | 8 ++++--- cmd2/utils.py | 16 ++++++++++++- 3 files changed, 32 insertions(+), 48 deletions(-) (limited to 'cmd2') 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: -- cgit v1.2.1