Skip to content

Commit 0917e2c

Browse files
Colorize error and warning messages in argparse
1 parent f5394c2 commit 0917e2c

File tree

3 files changed

+58
-3
lines changed

3 files changed

+58
-3
lines changed

Lib/_colorize.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,8 @@ class Argparse(ThemeSection):
170170
label: str = ANSIColors.BOLD_YELLOW
171171
action: str = ANSIColors.BOLD_GREEN
172172
reset: str = ANSIColors.RESET
173+
error: str = ANSIColors.BOLD_RED
174+
warning: str = ANSIColors.BOLD_YELLOW
173175

174176

175177
@dataclass(frozen=True, kw_only=True)

Lib/argparse.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2749,6 +2749,14 @@ def _print_message(self, message, file=None):
27492749
except (AttributeError, OSError):
27502750
pass
27512751

2752+
def _get_theme(self, file=None):
2753+
from _colorize import can_colorize, get_theme
2754+
2755+
if self.color and can_colorize(file=file):
2756+
return get_theme(force_color=True).argparse
2757+
else:
2758+
return get_theme(force_no_color=True).argparse
2759+
27522760
# ===============
27532761
# Exiting methods
27542762
# ===============
@@ -2768,13 +2776,19 @@ def error(self, message):
27682776
should either exit or raise an exception.
27692777
"""
27702778
self.print_usage(_sys.stderr)
2779+
theme = self._get_theme(file=_sys.stderr)
2780+
fmt = _('%(prog)s: error: %(message)s\n')
2781+
fmt = fmt.replace('error:', f'{theme.error}error:{theme.reset}')
2782+
27712783
args = {'prog': self.prog, 'message': message}
2772-
self.exit(2, _('%(prog)s: error: %(message)s\n') % args)
2784+
self.exit(2, fmt % args)
27732785

27742786
def _warning(self, message):
2787+
theme = self._get_theme(file=_sys.stderr)
2788+
fmt = _('%(prog)s: warning: %(message)s\n')
2789+
fmt = fmt.replace('warning:', f'{theme.warning}warning:{theme.reset}')
27752790
args = {'prog': self.prog, 'message': message}
2776-
self._print_message(_('%(prog)s: warning: %(message)s\n') % args, _sys.stderr)
2777-
2791+
self._print_message(fmt % args, _sys.stderr)
27782792

27792793
def __getattr__(name):
27802794
if name == "__version__":

Lib/test/test_argparse.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7370,6 +7370,45 @@ def test_subparser_prog_is_stored_without_color(self):
73707370
help_text = demo_parser.format_help()
73717371
self.assertNotIn('\x1b[', help_text)
73727372

7373+
def test_error_and_warning_keywords_colorized(self):
7374+
parser = argparse.ArgumentParser(prog='PROG')
7375+
parser.add_argument('foo')
7376+
7377+
with self.assertRaises(SystemExit):
7378+
with captured_stderr() as stderr:
7379+
parser.parse_args([])
7380+
7381+
err = stderr.getvalue()
7382+
error_color = self.theme.error
7383+
reset = self.theme.reset
7384+
self.assertIn(f'{error_color}error:{reset}', err)
7385+
7386+
with captured_stderr() as stderr:
7387+
parser._warning('test warning')
7388+
7389+
warn = stderr.getvalue()
7390+
warning_color = self.theme.warning
7391+
self.assertIn(f'{warning_color}warning:{reset}', warn)
7392+
7393+
def test_error_and_warning_not_colorized_when_disabled(self):
7394+
parser = argparse.ArgumentParser(prog='PROG', color=False)
7395+
parser.add_argument('foo')
7396+
7397+
with self.assertRaises(SystemExit):
7398+
with captured_stderr() as stderr:
7399+
parser.parse_args([])
7400+
7401+
err = stderr.getvalue()
7402+
self.assertNotIn('\x1b[', err)
7403+
self.assertIn('error:', err)
7404+
7405+
with captured_stderr() as stderr:
7406+
parser._warning('test warning')
7407+
7408+
warn = stderr.getvalue()
7409+
self.assertNotIn('\x1b[', warn)
7410+
self.assertIn('warning:', warn)
7411+
73737412

73747413
class TestModule(unittest.TestCase):
73757414
def test_deprecated__version__(self):

0 commit comments

Comments
 (0)