Skip to content

Commit d3deca3

Browse files
committed
Added apply_style to CompletionError
Simplified error class structure in argparse_completer.py
1 parent 065536a commit d3deca3

File tree

5 files changed

+33
-89
lines changed

5 files changed

+33
-89
lines changed

cmd2/argparse_completer.py

Lines changed: 12 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
import inspect
1111
import numbers
1212
import shutil
13-
import textwrap
1413
from collections import deque
1514
from typing import Dict, List, Optional, Union
1615

@@ -95,30 +94,8 @@ def __init__(self, arg_action: argparse.Action) -> None:
9594
self.max = self.action.nargs
9695

9796

98-
class _ArgparseCompletionError(CompletionError):
99-
"""CompletionError specific to argparse-based tab completion"""
100-
pass
101-
102-
103-
# noinspection PyProtectedMember
104-
class _ActionCompletionError(_ArgparseCompletionError):
105-
def __init__(self, arg_action: argparse.Action, completion_error: CompletionError) -> None:
106-
"""
107-
Adds action-specific information to a CompletionError. These are raised when
108-
non-argparse related errors occur during tab completion.
109-
:param arg_action: action being tab completed
110-
:param completion_error: error that occurred
111-
"""
112-
# Indent all lines of completion_error
113-
indented_error = textwrap.indent(str(completion_error), ' ')
114-
115-
error = ("Error tab completing {}:\n"
116-
"{}".format(argparse._get_action_name(arg_action), indented_error))
117-
super().__init__(ansi.style_error(error))
118-
119-
12097
# noinspection PyProtectedMember
121-
class _UnfinishedFlagError(_ArgparseCompletionError):
98+
class _UnfinishedFlagError(CompletionError):
12299
def __init__(self, flag_arg_state: _ArgumentState) -> None:
123100
"""
124101
CompletionError which occurs when the user has not finished the current flag
@@ -128,11 +105,11 @@ def __init__(self, flag_arg_state: _ArgumentState) -> None:
128105
format(argparse._get_action_name(flag_arg_state.action),
129106
generate_range_error(flag_arg_state.min, flag_arg_state.max),
130107
flag_arg_state.count)
131-
super().__init__(ansi.style_error(error))
108+
super().__init__(error)
132109

133110

134111
# noinspection PyProtectedMember
135-
class _NoResultsError(_ArgparseCompletionError):
112+
class _NoResultsError(CompletionError):
136113
def __init__(self, parser: argparse.ArgumentParser, arg_action: argparse.Action) -> None:
137114
"""
138115
CompletionError which occurs when there are no results. If hinting is allowed, then its message will
@@ -151,7 +128,8 @@ def __init__(self, parser: argparse.ArgumentParser, arg_action: argparse.Action)
151128
formatter.add_argument(arg_action)
152129
formatter.end_section()
153130
hint_str = formatter.format_help()
154-
super().__init__(hint_str)
131+
# Set apply_style to False because we don't want hints to look like errors
132+
super().__init__(hint_str, apply_style=False)
155133

156134

157135
# noinspection PyProtectedMember
@@ -253,9 +231,9 @@ def update_mutex_groups(arg_action: argparse.Action) -> None:
253231
if arg_action == completer_action:
254232
return
255233

256-
error = ansi.style_error("\nError: argument {}: not allowed with argument {}\n".
257-
format(argparse._get_action_name(arg_action),
258-
argparse._get_action_name(completer_action)))
234+
error = ("Error: argument {}: not allowed with argument {}\n".
235+
format(argparse._get_action_name(arg_action),
236+
argparse._get_action_name(completer_action)))
259237
raise CompletionError(error)
260238

261239
# Mark that this action completed the group
@@ -418,13 +396,8 @@ def update_mutex_groups(arg_action: argparse.Action) -> None:
418396

419397
# Check if we are completing a flag's argument
420398
if flag_arg_state is not None:
421-
try:
422-
completion_results = self._complete_for_arg(flag_arg_state.action, text, line,
423-
begidx, endidx, consumed_arg_values)
424-
except _ArgparseCompletionError as ex:
425-
raise ex
426-
except CompletionError as ex:
427-
raise _ActionCompletionError(flag_arg_state.action, ex)
399+
completion_results = self._complete_for_arg(flag_arg_state.action, text, line,
400+
begidx, endidx, consumed_arg_values)
428401

429402
# If we have results, then return them
430403
if completion_results:
@@ -443,13 +416,8 @@ def update_mutex_groups(arg_action: argparse.Action) -> None:
443416
action = remaining_positionals.popleft()
444417
pos_arg_state = _ArgumentState(action)
445418

446-
try:
447-
completion_results = self._complete_for_arg(pos_arg_state.action, text, line,
448-
begidx, endidx, consumed_arg_values)
449-
except _ArgparseCompletionError as ex:
450-
raise ex
451-
except CompletionError as ex:
452-
raise _ActionCompletionError(pos_arg_state.action, ex)
419+
completion_results = self._complete_for_arg(pos_arg_state.action, text, line,
420+
begidx, endidx, consumed_arg_values)
453421

454422
# If we have results, then return them
455423
if completion_results:

cmd2/cmd2.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1416,10 +1416,12 @@ def complete(self, text: str, state: int) -> Optional[str]:
14161416
except IndexError:
14171417
return None
14181418

1419-
except CompletionError as e:
1420-
err_str = str(e)
1419+
except CompletionError as ex:
1420+
err_str = str(ex)
1421+
# Don't print error and redraw the prompt unless the error has length
14211422
if err_str:
1422-
# Don't print error and redraw the prompt unless the error has length
1423+
if ex.apply_style:
1424+
err_str = ansi.style_error(err_str)
14231425
ansi.style_aware_write(sys.stdout, '\n' + err_str + '\n')
14241426
rl_force_redisplay()
14251427
return None

cmd2/utils.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,12 +75,26 @@ def str_to_bool(val: str) -> bool:
7575
class CompletionError(Exception):
7676
"""
7777
Raised during tab completion operations to report any sort of error you want printed by the ArgparseCompleter
78+
This can also be used just to display a message, even if it's not an error. ArgparseCompleter raises
79+
CompletionErrors to display tab completion hints and sets apply_style to False so hints aren't colored
80+
like error text.
7881
7982
Example use cases
8083
- Reading a database to retrieve a tab completion data set failed
8184
- A previous command line argument that determines the data set being completed is invalid
85+
- Tab completion hints
8286
"""
83-
pass
87+
def __init__(self, *args, apply_style: bool = True, **kwargs):
88+
"""
89+
Initializer for CompletionError
90+
:param apply_style: If True, then ansi.style_error will be applied to the message text when printed.
91+
Set to False in cases where the message text already has the desired style.
92+
Defaults to True.
93+
"""
94+
self.apply_style = apply_style
95+
96+
# noinspection PyArgumentList
97+
super().__init__(*args, **kwargs)
8498

8599

86100
class Settable:

examples/basic_completion.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
from typing import List
1717

1818
import cmd2
19-
from cmd2 import ansi
2019

2120
# List of strings used with completion functions
2221
food_item_strs = ['Pizza', 'Ham', 'Ham Sandwich', 'Potato']
@@ -99,7 +98,7 @@ def complete_raise_error(self, text, line, begidx, endidx) -> List[str]:
9998
- Reading a database to retrieve a tab completion data set failed
10099
- A previous command line argument that determines the data set being completed is invalid
101100
"""
102-
raise cmd2.CompletionError(ansi.style_error("This is how a CompletionError behaves"))
101+
raise cmd2.CompletionError("This is how a CompletionError behaves")
103102

104103

105104
if __name__ == '__main__':

tests/test_argparse_completer.py

Lines changed: 0 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,6 @@
3030
completions_from_function = ['completions', 'function', 'fairly', 'complete']
3131
completions_from_method = ['completions', 'method', 'missed', 'spot']
3232

33-
AP_COMP_ERROR_TEXT = "SHOULD ONLY BE THIS TEXT"
34-
3533

3634
def choices_function() -> List[str]:
3735
"""Function that provides choices"""
@@ -234,24 +232,6 @@ def choice_raise_error(self) -> List[str]:
234232
def do_raise_completion_error(self, args: argparse.Namespace) -> None:
235233
pass
236234

237-
############################################################################################################
238-
# Begin code related to _ArgparseCompletionError
239-
############################################################################################################
240-
def raise_argparse_completion_error(self):
241-
"""Raises ArgparseCompletionError to make sure it gets raised as is"""
242-
from cmd2.argparse_completer import _ArgparseCompletionError
243-
raise _ArgparseCompletionError(AP_COMP_ERROR_TEXT)
244-
245-
ap_comp_error_parser = Cmd2ArgumentParser()
246-
ap_comp_error_parser.add_argument('pos_ap_comp_err', help='pos ap completion error',
247-
choices_method=raise_argparse_completion_error)
248-
ap_comp_error_parser.add_argument('--flag_ap_comp_err', help='flag ap completion error',
249-
choices_method=raise_argparse_completion_error)
250-
251-
@with_argparser(ap_comp_error_parser)
252-
def do_raise_ap_completion_error(self, args: argparse.Namespace) -> None:
253-
pass
254-
255235
############################################################################################################
256236
# Begin code related to receiving arg_tokens
257237
############################################################################################################
@@ -793,25 +773,6 @@ def test_completion_error(ac_app, capsys, args, text):
793773
assert "{} broke something".format(text) in out
794774

795775

796-
@pytest.mark.parametrize('arg', [
797-
# Exercise positional arg that raises _ArgparseCompletionError
798-
'',
799-
800-
# Exercise flag arg that raises _ArgparseCompletionError
801-
'--flag_ap_comp_err'
802-
])
803-
def test_argparse_completion_error(ac_app, capsys, arg):
804-
text = ''
805-
line = 'raise_ap_completion_error {} {}'.format(arg, text)
806-
endidx = len(line)
807-
begidx = endidx - len(text)
808-
809-
first_match = complete_tester(text, line, begidx, endidx, ac_app)
810-
assert first_match is None
811-
out, err = capsys.readouterr()
812-
assert out.strip() == AP_COMP_ERROR_TEXT
813-
814-
815776
@pytest.mark.parametrize('command_and_args, completions', [
816777
# Exercise a choices function that receives arg_tokens dictionary
817778
('arg_tokens choice subcmd', ['choice', 'subcmd']),

0 commit comments

Comments
 (0)