mirror of
https://github.com/python/cpython.git
synced 2026-01-26 04:48:57 +00:00
GH-142950: Process format specifiers before colourization in argparse help (#142960)
This commit is contained in:
parent
d04394929b
commit
9a3263ff8f
@ -688,11 +688,41 @@ class HelpFormatter(object):
|
||||
params[name] = value.__name__
|
||||
if params.get('choices') is not None:
|
||||
params['choices'] = ', '.join(map(str, params['choices']))
|
||||
# Before interpolating, wrap the values with color codes
|
||||
|
||||
t = self._theme
|
||||
for name, value in params.items():
|
||||
params[name] = f"{t.interpolated_value}{value}{t.reset}"
|
||||
return help_string % params
|
||||
|
||||
result = help_string % params
|
||||
|
||||
if not t.reset:
|
||||
return result
|
||||
|
||||
# Match format specifiers like: %s, %d, %(key)s, etc.
|
||||
fmt_spec = r'''
|
||||
%
|
||||
(?:
|
||||
% # %% escape
|
||||
|
|
||||
(?:\((?P<key>[^)]*)\))? # key
|
||||
[-#0\ +]* # flags
|
||||
(?:\*|\d+)? # width
|
||||
(?:\.(?:\*|\d+))? # precision
|
||||
[hlL]? # length modifier
|
||||
[diouxXeEfFgGcrsa] # conversion type
|
||||
)
|
||||
'''
|
||||
|
||||
def colorize(match):
|
||||
spec, key = match.group(0, 'key')
|
||||
if spec == '%%':
|
||||
return '%'
|
||||
if key is not None:
|
||||
# %(key)... - format and colorize
|
||||
formatted = spec % {key: params[key]}
|
||||
return f'{t.interpolated_value}{formatted}{t.reset}'
|
||||
# bare %s etc. - format with full params dict, no colorization
|
||||
return spec % params
|
||||
|
||||
return _re.sub(fmt_spec, colorize, help_string, flags=_re.VERBOSE)
|
||||
|
||||
def _iter_indented_subactions(self, action):
|
||||
try:
|
||||
|
||||
@ -7663,6 +7663,38 @@ class TestColorized(TestCase):
|
||||
help_text = parser.format_help()
|
||||
self.assertIn(f'{prog_extra}grep "foo.*bar" | sort{reset}', help_text)
|
||||
|
||||
def test_help_with_format_specifiers(self):
|
||||
# GH-142950: format specifiers like %x should work with color=True
|
||||
parser = argparse.ArgumentParser(prog='PROG', color=True)
|
||||
parser.add_argument('--hex', type=int, default=255,
|
||||
help='hex: %(default)x, alt: %(default)#x')
|
||||
parser.add_argument('--zero', type=int, default=7,
|
||||
help='zero: %(default)05d')
|
||||
parser.add_argument('--str', default='test',
|
||||
help='str: %(default)s')
|
||||
parser.add_argument('--pct', type=int, default=50,
|
||||
help='pct: %(default)d%%')
|
||||
parser.add_argument('--literal', help='literal: 100%%')
|
||||
parser.add_argument('--prog', help='prog: %(prog)s')
|
||||
parser.add_argument('--type', type=int, help='type: %(type)s')
|
||||
parser.add_argument('--choices', choices=['a', 'b'],
|
||||
help='choices: %(choices)s')
|
||||
|
||||
help_text = parser.format_help()
|
||||
|
||||
interp = self.theme.interpolated_value
|
||||
reset = self.theme.reset
|
||||
|
||||
self.assertIn(f'hex: {interp}ff{reset}', help_text)
|
||||
self.assertIn(f'alt: {interp}0xff{reset}', help_text)
|
||||
self.assertIn(f'zero: {interp}00007{reset}', help_text)
|
||||
self.assertIn(f'str: {interp}test{reset}', help_text)
|
||||
self.assertIn(f'pct: {interp}50{reset}%', help_text)
|
||||
self.assertIn('literal: 100%', help_text)
|
||||
self.assertIn(f'prog: {interp}PROG{reset}', help_text)
|
||||
self.assertIn(f'type: {interp}int{reset}', help_text)
|
||||
self.assertIn(f'choices: {interp}a, b{reset}', help_text)
|
||||
|
||||
def test_print_help_uses_target_file_for_color_decision(self):
|
||||
parser = argparse.ArgumentParser(prog='PROG', color=True)
|
||||
parser.add_argument('--opt')
|
||||
|
||||
@ -0,0 +1 @@
|
||||
Fix regression in :mod:`argparse` where format specifiers in help strings raised :exc:`ValueError`.
|
||||
Loading…
x
Reference in New Issue
Block a user