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:
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')