diff --git a/CHANGELOG.md b/CHANGELOG.md index c54be4f3..62ccf705 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## 3.2.1 (February TBD, 2026) + +- Bug Fixes + - The `async_alert` and `async_prompt_update` methods of `cmd2.Cmd` now respect the current + value of the `allow_style` settable + - If `allow_style` is `NEVER`, all ANSI escape codes will be stripped to ensure plain text + output + ## 3.2.0 (February 5, 2026) - Bug Fixes diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index 429596d1..33e6aa7e 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -5744,6 +5744,8 @@ def async_alert(self, alert_msg: str, new_prompt: str | None = None) -> None: # update_terminal = False if alert_msg: + if self.allow_style == ru.AllowStyle.NEVER: + alert_msg = su.strip_style(alert_msg) alert_msg += '\n' update_terminal = True diff --git a/tests/test_async_alert.py b/tests/test_async_alert.py new file mode 100644 index 00000000..878b95c9 --- /dev/null +++ b/tests/test_async_alert.py @@ -0,0 +1,50 @@ +import unittest +from unittest.mock import MagicMock, patch + +import cmd2 +import cmd2.cmd2 # to patch vt100_support +from cmd2 import rich_utils as ru + + +class TestAsyncAlert(unittest.TestCase): + def test_async_alert_strips_ansi_when_allow_style_is_never(self): + app = cmd2.Cmd() + + # Patch vt100_support to True + with patch('cmd2.cmd2.vt100_support', True): + # Patch threading functions + mock_current_thread = MagicMock() + mock_current_thread.name = "NotMainThread" + + with ( + patch('threading.current_thread', return_value=mock_current_thread), + patch('threading.main_thread', return_value=MagicMock()), + patch('cmd2.cmd2.rl_get_display_prompt', return_value='(Cmd) '), + patch('cmd2.cmd2.readline.get_line_buffer', return_value=''), + patch('cmd2.cmd2.rl_get_point', return_value=0), + patch('cmd2.cmd2.rl_force_redisplay'), + patch('sys.stdout', new_callable=MagicMock) as mock_stdout, + ): + # Set allow_style to NEVER + app.allow_style = ru.AllowStyle.NEVER + + # Styled message + msg = "033円[31mError033円[0m" + + # Call async_alert + app.async_alert(msg) + + # Capture calls to write + # mock_stdout.write.call_args_list -> [call(str), call(str)...] + # We look at all written strings + written_content = "".join([call.args[0] for call in mock_stdout.write.call_args_list]) + + # Check that ANSI codes for color are NOT present + if "033円[31m" in written_content: + raise AssertionError(f"Found ANSI color code in output: {written_content!r}") + if "Error" not in written_content: + raise AssertionError(f"Message 'Error' not found in output: {written_content!r}") + + +if __name__ == '__main__': + unittest.main()

AltStyle によって変換されたページ (->オリジナル) /