Skip to content

Commit b3deacc

Browse files
Format before colourization
1 parent e79c391 commit b3deacc

File tree

2 files changed

+39
-4
lines changed

2 files changed

+39
-4
lines changed

Lib/argparse.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -688,11 +688,30 @@ def _expand_help(self, action):
688688
params[name] = value.__name__
689689
if params.get('choices') is not None:
690690
params['choices'] = ', '.join(map(str, params['choices']))
691-
# Before interpolating, wrap the values with color codes
691+
692692
t = self._theme
693-
for name, value in params.items():
694-
params[name] = f"{t.interpolated_value}{value}{t.reset}"
695-
return help_string % params
693+
694+
if not t.reset:
695+
return help_string % params
696+
697+
# Format first to preserve types for specifiers, like %x that require int.
698+
def colorize(match):
699+
spec, name = match.group(0, 1)
700+
if spec == '%%':
701+
return '%'
702+
if name in params:
703+
formatted = spec % {name: params[name]}
704+
return f'{t.interpolated_value}{formatted}{t.reset}'
705+
return spec
706+
707+
# Match %% (literal %) or %(name)... format specifiers
708+
result = _re.sub(r'%%|%\((\w+)\)[^a-z]*[a-z]', colorize,
709+
help_string, flags=_re.IGNORECASE)
710+
711+
# Check for invalid/unmatched % specifiers
712+
if '%' in result:
713+
raise ValueError(f"invalid format specifier in: {help_string!r}")
714+
return result
696715

697716
def _iter_indented_subactions(self, action):
698717
try:

Lib/test/test_argparse.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7663,6 +7663,22 @@ def test_backtick_markup_special_regex_chars(self):
76637663
help_text = parser.format_help()
76647664
self.assertIn(f'{prog_extra}grep "foo.*bar" | sort{reset}', help_text)
76657665

7666+
def test_help_with_format_specifiers(self):
7667+
# GH-142950: format specifiers like %x should work with color=True
7668+
parser = argparse.ArgumentParser(prog='PROG', color=True)
7669+
parser.add_argument('--hex', type=int, default=255,
7670+
help='hex: %(default)x')
7671+
parser.add_argument('--str', default='test',
7672+
help='str: %(default)s')
7673+
7674+
help_text = parser.format_help()
7675+
7676+
interp = self.theme.interpolated_value
7677+
reset = self.theme.reset
7678+
7679+
self.assertIn(f'hex: {interp}ff{reset}', help_text)
7680+
self.assertIn(f'str: {interp}test{reset}', help_text)
7681+
76667682
def test_print_help_uses_target_file_for_color_decision(self):
76677683
parser = argparse.ArgumentParser(prog='PROG', color=True)
76687684
parser.add_argument('--opt')

0 commit comments

Comments
 (0)