Skip to content

Commit e73661c

Browse files
committed
Added validation when setting allow_ansi
1 parent c1812a9 commit e73661c

File tree

2 files changed

+32
-8
lines changed

2 files changed

+32
-8
lines changed

cmd2/cmd2.py

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -387,7 +387,10 @@ def __init__(self, completekey: str = 'tab', stdin=None, stdout=None, *,
387387
self.settable = \
388388
{
389389
# allow_ansi is a special case in which it's an application-wide setting defined in ansi.py
390-
'allow_ansi': 'Allow ANSI escape sequences in output (valid values: Terminal, Always, Never)',
390+
'allow_ansi': ('Allow ANSI escape sequences in output '
391+
'(valid values: {}, {}, {})'.format(ansi.ANSI_TERMINAL,
392+
ansi.ANSI_ALWAYS,
393+
ansi.ANSI_NEVER)),
391394
'continuation_prompt': 'On 2nd+ line of input',
392395
'debug': 'Show full error stack on error',
393396
'echo': 'Echo command issued into output',
@@ -561,7 +564,11 @@ def allow_ansi(self) -> str:
561564
@allow_ansi.setter
562565
def allow_ansi(self, new_val: str) -> None:
563566
"""Read-only property needed to support do_set when it sets allow_ansi"""
564-
ansi.allow_ansi = new_val
567+
if new_val not in (ansi.ANSI_TERMINAL, ansi.ANSI_ALWAYS, ansi.ANSI_NEVER):
568+
self.perror('Invalid value: {} (valid values: {}, {}, {})'.format(new_val, ansi.ANSI_TERMINAL,
569+
ansi.ANSI_ALWAYS, ansi.ANSI_NEVER))
570+
else:
571+
ansi.allow_ansi = new_val
565572

566573
@property
567574
def visible_prompt(self) -> str:
@@ -2953,17 +2960,20 @@ def do_set(self, args: argparse.Namespace) -> None:
29532960
return self._show(args, param)
29542961

29552962
# Update the settable's value
2956-
current_value = getattr(self, param)
2957-
value = utils.cast(current_value, value)
2958-
setattr(self, param, value)
2963+
orig_value = getattr(self, param)
2964+
setattr(self, param, utils.cast(orig_value, value))
2965+
2966+
# In cases where a Python property is used to validate and update a settable's value, its value will not
2967+
# change if the passed in one is invalid. Therefore we should read its actual value back and not assume.
2968+
new_value = getattr(self, param)
29592969

2960-
self.poutput('{} - was: {}\nnow: {}'.format(param, current_value, value))
2970+
self.poutput('{} - was: {}\nnow: {}'.format(param, orig_value, new_value))
29612971

29622972
# See if we need to call a change hook for this settable
2963-
if current_value != value:
2973+
if orig_value != new_value:
29642974
onchange_hook = getattr(self, '_onchange_{}'.format(param), None)
29652975
if onchange_hook is not None:
2966-
onchange_hook(old=current_value, new=value)
2976+
onchange_hook(old=orig_value, new=new_value)
29672977

29682978
shell_parser = ACArgumentParser()
29692979
setattr(shell_parser.add_argument('command', help='the command to run'),

tests/test_cmd2.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,20 @@ def test_set_quiet(base_app):
181181
out, err = run_cmd(base_app, 'set quiet')
182182
assert out == ['quiet: True']
183183

184+
@pytest.mark.parametrize('new_val, is_valid', [
185+
(ansi.ANSI_NEVER, False),
186+
(ansi.ANSI_TERMINAL, False),
187+
(ansi.ANSI_ALWAYS, False),
188+
('invalid', True),
189+
])
190+
def test_set_allow_ansi(base_app, new_val, is_valid):
191+
out, err = run_cmd(base_app, 'set allow_ansi {}'.format(new_val))
192+
assert bool(err) == is_valid
193+
194+
# Reload ansi module to reset allow_ansi to its default since it's an
195+
# application-wide setting that can affect other unit tests.
196+
import importlib
197+
importlib.reload(ansi)
184198

185199
class OnChangeHookApp(cmd2.Cmd):
186200
def __init__(self, *args, **kwargs):

0 commit comments

Comments
 (0)