diff --git a/Doc/library/argparse.rst b/Doc/library/argparse.rst index d50ec34e54d710..70dd342ab66a47 100644 --- a/Doc/library/argparse.rst +++ b/Doc/library/argparse.rst @@ -645,6 +645,27 @@ are set. .. versionadded:: 3.14 +To highlight inline code in your description or epilog text, you can use +backticks:: + + >>> parser = argparse.ArgumentParser( + ... formatter_class=argparse.RawDescriptionHelpFormatter, + ... epilog='''Examples: + ... `python -m myapp --verbose` + ... `python -m myapp --config settings.json` + ... ''') + +When colors are enabled, the text inside backticks will be displayed in a +distinct color to help examples stand out. When colors are disabled, backticks +are preserved as-is, which is readable in plain text. + +.. note:: + + Backtick markup only applies to description and epilog text. It does not + apply to individual argument ``help`` strings. + +.. versionadded:: 3.15 + The add_argument() method ------------------------- diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index 1d8b838cd2e7f0..afefcc15ab2d55 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -423,6 +423,10 @@ argparse default to ``True``. This enables suggestions for mistyped arguments by default. (Contributed by Jakob Schluse in :gh:`140450`.) +* Added backtick markup support in description and epilog text to highlight + inline code when color output is enabled. + (Contributed by Savannah Ostrowski in :gh:`142390`.) + calendar -------- diff --git a/Lib/argparse.py b/Lib/argparse.py index ee7ebc4696a0f7..633fec69ea4615 100644 --- a/Lib/argparse.py +++ b/Lib/argparse.py @@ -517,7 +517,27 @@ def _format_text(self, text): text = text % dict(prog=self._prog) text_width = max(self._width - self._current_indent, 11) indent = ' ' * self._current_indent - return self._fill_text(text, text_width, indent) + '\n\n' + text = self._fill_text(text, text_width, indent) + text = self._apply_text_markup(text) + return text + '\n\n' + + def _apply_text_markup(self, text): + """Apply color markup to text. + + Supported markup: + `...` - inline code (rendered with prog_extra color) + + When colors are disabled, backticks are preserved as-is. + """ + t = self._theme + if not t.reset: + return text + text = _re.sub( + r'`([^`]+)`', + rf'{t.prog_extra}\1{t.reset}', + text, + ) + return text def _format_action(self, action): # determine the required width and the entry label diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index 0f93e8ea740770..758af98d5cb046 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -7568,6 +7568,101 @@ def test_error_and_warning_not_colorized_when_disabled(self): self.assertNotIn('\x1b[', warn) self.assertIn('warning:', warn) + def test_backtick_markup_in_epilog(self): + parser = argparse.ArgumentParser( + prog='PROG', + color=True, + epilog='Example: `python -m myapp --verbose`', + ) + + prog_extra = self.theme.prog_extra + reset = self.theme.reset + + help_text = parser.format_help() + self.assertIn(f'Example: {prog_extra}python -m myapp --verbose{reset}', + help_text) + self.assertNotIn('`', help_text) + + def test_backtick_markup_in_description(self): + parser = argparse.ArgumentParser( + prog='PROG', + color=True, + description='Run `python -m myapp` to start.', + ) + + prog_extra = self.theme.prog_extra + reset = self.theme.reset + + help_text = parser.format_help() + self.assertIn(f'Run {prog_extra}python -m myapp{reset} to start.', + help_text) + + def test_backtick_markup_multiple(self): + parser = argparse.ArgumentParser( + prog='PROG', + color=True, + epilog='Try `app run` or `app test`.', + ) + + prog_extra = self.theme.prog_extra + reset = self.theme.reset + + help_text = parser.format_help() + self.assertIn(f'{prog_extra}app run{reset}', help_text) + self.assertIn(f'{prog_extra}app test{reset}', help_text) + + def test_backtick_markup_not_applied_when_color_disabled(self): + # When color is disabled, backticks are preserved as-is + parser = argparse.ArgumentParser( + prog='PROG', + color=False, + epilog='Example: `python -m myapp`', + ) + + help_text = parser.format_help() + self.assertIn('`python -m myapp`', help_text) + self.assertNotIn('\x1b[', help_text) + + def test_backtick_markup_with_format_string(self): + parser = argparse.ArgumentParser( + prog='myapp', + color=True, + epilog='Run `%(prog)s --help` for more info.', + ) + + prog_extra = self.theme.prog_extra + reset = self.theme.reset + + help_text = parser.format_help() + self.assertIn(f'{prog_extra}myapp --help{reset}', help_text) + + def test_backtick_markup_in_subparser(self): + parser = argparse.ArgumentParser(prog='PROG', color=True) + subparsers = parser.add_subparsers() + sub = subparsers.add_parser( + 'sub', + description='Run `PROG sub --foo` to start.', + ) + + prog_extra = self.theme.prog_extra + reset = self.theme.reset + + help_text = sub.format_help() + self.assertIn(f'{prog_extra}PROG sub --foo{reset}', help_text) + + def test_backtick_markup_special_regex_chars(self): + parser = argparse.ArgumentParser( + prog='PROG', + color=True, + epilog='`grep "foo.*bar" | sort`', + ) + + prog_extra = self.theme.prog_extra + reset = self.theme.reset + + help_text = parser.format_help() + self.assertIn(f'{prog_extra}grep "foo.*bar" | sort{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') diff --git a/Misc/NEWS.d/next/Library/2025-12-07-22-13-28.gh-issue-142389.J9v904.rst b/Misc/NEWS.d/next/Library/2025-12-07-22-13-28.gh-issue-142389.J9v904.rst new file mode 100644 index 00000000000000..44e9cea7b54a8f --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-12-07-22-13-28.gh-issue-142389.J9v904.rst @@ -0,0 +1 @@ +Add backtick markup support in :mod:`argparse` description and epilog text to highlight inline code when color output is enabled.