mirror of
				https://github.com/python/cpython.git
				synced 2025-10-30 21:21:22 +00:00 
			
		
		
		
	[3.14] GH-130645: Default to color help in argparse (GH-136809) (#136886)
GH-130645: Default to color help in argparse (GH-136809)
(cherry picked from commit acbe896cb1)
Co-authored-by: Pablo Galindo Salgado <Pablogsal@gmail.com>
Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com>
Co-authored-by: Łukasz Langa <lukasz@langa.pl>
Co-authored-by: Adam Turner <9087854+aa-turner@users.noreply.github.com>
			
			
This commit is contained in:
		
							parent
							
								
									2f7684ceee
								
							
						
					
					
						commit
						17c5959aa3
					
				
					 6 changed files with 39 additions and 25 deletions
				
			
		|  | @ -74,7 +74,7 @@ ArgumentParser objects | ||||||
|                           prefix_chars='-', fromfile_prefix_chars=None, \ |                           prefix_chars='-', fromfile_prefix_chars=None, \ | ||||||
|                           argument_default=None, conflict_handler='error', \ |                           argument_default=None, conflict_handler='error', \ | ||||||
|                           add_help=True, allow_abbrev=True, exit_on_error=True, \ |                           add_help=True, allow_abbrev=True, exit_on_error=True, \ | ||||||
|                           *, suggest_on_error=False, color=False) |                           *, suggest_on_error=False, color=True) | ||||||
| 
 | 
 | ||||||
|    Create a new :class:`ArgumentParser` object. All parameters should be passed |    Create a new :class:`ArgumentParser` object. All parameters should be passed | ||||||
|    as keyword arguments. Each parameter has its own more detailed description |    as keyword arguments. Each parameter has its own more detailed description | ||||||
|  | @ -119,7 +119,7 @@ ArgumentParser objects | ||||||
|    * suggest_on_error_ - Enables suggestions for mistyped argument choices |    * suggest_on_error_ - Enables suggestions for mistyped argument choices | ||||||
|      and subparser names (default: ``False``) |      and subparser names (default: ``False``) | ||||||
| 
 | 
 | ||||||
|    * color_ - Allow color output (default: ``False``) |    * color_ - Allow color output (default: ``True``) | ||||||
| 
 | 
 | ||||||
|    .. versionchanged:: 3.5 |    .. versionchanged:: 3.5 | ||||||
|       *allow_abbrev* parameter was added. |       *allow_abbrev* parameter was added. | ||||||
|  | @ -626,27 +626,19 @@ keyword argument:: | ||||||
| color | color | ||||||
| ^^^^^ | ^^^^^ | ||||||
| 
 | 
 | ||||||
| By default, the help message is printed in plain text. If you want to allow | By default, the help message is printed in color using `ANSI escape sequences | ||||||
| color in help messages, you can enable it by setting ``color`` to ``True``:: | <https://en.wikipedia.org/wiki/ANSI_escape_code>`__. | ||||||
|  | If you want plain text help messages, you can disable this :ref:`in your local | ||||||
|  | environment <using-on-controlling-color>`, or in the argument parser itself | ||||||
|  | by setting ``color`` to ``False``:: | ||||||
| 
 | 
 | ||||||
|    >>> parser = argparse.ArgumentParser(description='Process some integers.', |    >>> parser = argparse.ArgumentParser(description='Process some integers.', | ||||||
|    ...                                  color=True) |    ...                                  color=False) | ||||||
|    >>> parser.add_argument('--action', choices=['sum', 'max']) |    >>> parser.add_argument('--action', choices=['sum', 'max']) | ||||||
|    >>> parser.add_argument('integers', metavar='N', type=int, nargs='+', |    >>> parser.add_argument('integers', metavar='N', type=int, nargs='+', | ||||||
|    ...                     help='an integer for the accumulator') |    ...                     help='an integer for the accumulator') | ||||||
|    >>> parser.parse_args(['--help']) |    >>> parser.parse_args(['--help']) | ||||||
| 
 | 
 | ||||||
| Even if a CLI author has enabled color, it can be |  | ||||||
| :ref:`controlled using environment variables <using-on-controlling-color>`. |  | ||||||
| 
 |  | ||||||
| If you're writing code that needs to be compatible with older Python versions |  | ||||||
| and want to opportunistically use ``color`` when it's available, you |  | ||||||
| can set it as an attribute after initializing the parser instead of using the |  | ||||||
| keyword argument:: |  | ||||||
| 
 |  | ||||||
|    >>> parser = argparse.ArgumentParser(description='Process some integers.') |  | ||||||
|    >>> parser.color = True |  | ||||||
| 
 |  | ||||||
| .. versionadded:: 3.14 | .. versionadded:: 3.14 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1228,11 +1228,10 @@ argparse | ||||||
| 
 | 
 | ||||||
|   .. _whatsnew314-color-argparse: |   .. _whatsnew314-color-argparse: | ||||||
| 
 | 
 | ||||||
| * Introduced the optional *color* parameter to | * Enable color for help text, which can be disabled with the optional *color* | ||||||
|   :class:`argparse.ArgumentParser`, enabling color for help text. |   parameter to :class:`argparse.ArgumentParser`. | ||||||
|   This can be controlled by :ref:`environment variables |   This can also be controlled by :ref:`environment variables | ||||||
|   <using-on-controlling-color>`. Color has also been enabled for help in the |   <using-on-controlling-color>`. | ||||||
|   :ref:`stdlib CLIs <library-cmdline>` which use :mod:`!argparse`. |  | ||||||
|   (Contributed by Hugo van Kemenade in :gh:`130645`.) |   (Contributed by Hugo van Kemenade in :gh:`130645`.) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -167,7 +167,7 @@ def __init__( | ||||||
|         indent_increment=2, |         indent_increment=2, | ||||||
|         max_help_position=24, |         max_help_position=24, | ||||||
|         width=None, |         width=None, | ||||||
|         color=False, |         color=True, | ||||||
|     ): |     ): | ||||||
|         # default setting for width |         # default setting for width | ||||||
|         if width is None: |         if width is None: | ||||||
|  | @ -1231,7 +1231,7 @@ def __init__(self, | ||||||
|         self._name_parser_map = {} |         self._name_parser_map = {} | ||||||
|         self._choices_actions = [] |         self._choices_actions = [] | ||||||
|         self._deprecated = set() |         self._deprecated = set() | ||||||
|         self._color = False |         self._color = True | ||||||
| 
 | 
 | ||||||
|         super(_SubParsersAction, self).__init__( |         super(_SubParsersAction, self).__init__( | ||||||
|             option_strings=option_strings, |             option_strings=option_strings, | ||||||
|  | @ -1878,7 +1878,7 @@ def __init__(self, | ||||||
|                  exit_on_error=True, |                  exit_on_error=True, | ||||||
|                  *, |                  *, | ||||||
|                  suggest_on_error=False, |                  suggest_on_error=False, | ||||||
|                  color=False, |                  color=True, | ||||||
|                  ): |                  ): | ||||||
|         superinit = super(ArgumentParser, self).__init__ |         superinit = super(ArgumentParser, self).__init__ | ||||||
|         superinit(description=description, |         superinit(description=description, | ||||||
|  |  | ||||||
|  | @ -18,7 +18,11 @@ | ||||||
| import warnings | import warnings | ||||||
| 
 | 
 | ||||||
| from enum import StrEnum | from enum import StrEnum | ||||||
| from test.support import captured_stderr | from test.support import ( | ||||||
|  |     captured_stderr, | ||||||
|  |     force_not_colorized, | ||||||
|  |     force_not_colorized_test_class, | ||||||
|  | ) | ||||||
| from test.support import import_helper | from test.support import import_helper | ||||||
| from test.support import os_helper | from test.support import os_helper | ||||||
| from test.support import script_helper | from test.support import script_helper | ||||||
|  | @ -1007,6 +1011,7 @@ def test_parse_enum_value(self): | ||||||
|         args = parser.parse_args(['--color', 'red']) |         args = parser.parse_args(['--color', 'red']) | ||||||
|         self.assertEqual(args.color, self.Color.RED) |         self.assertEqual(args.color, self.Color.RED) | ||||||
| 
 | 
 | ||||||
|  |     @force_not_colorized | ||||||
|     def test_help_message_contains_enum_choices(self): |     def test_help_message_contains_enum_choices(self): | ||||||
|         parser = argparse.ArgumentParser() |         parser = argparse.ArgumentParser() | ||||||
|         parser.add_argument('--color', choices=self.Color, help='Choose a color') |         parser.add_argument('--color', choices=self.Color, help='Choose a color') | ||||||
|  | @ -2403,6 +2408,7 @@ def test_modified_invalid_action(self): | ||||||
| # Subparsers tests | # Subparsers tests | ||||||
| # ================ | # ================ | ||||||
| 
 | 
 | ||||||
|  | @force_not_colorized_test_class | ||||||
| class TestAddSubparsers(TestCase): | class TestAddSubparsers(TestCase): | ||||||
|     """Test the add_subparsers method""" |     """Test the add_subparsers method""" | ||||||
| 
 | 
 | ||||||
|  | @ -3009,6 +3015,7 @@ def test_nested_argument_group(self): | ||||||
| # Parent parser tests | # Parent parser tests | ||||||
| # =================== | # =================== | ||||||
| 
 | 
 | ||||||
|  | @force_not_colorized_test_class | ||||||
| class TestParentParsers(TestCase): | class TestParentParsers(TestCase): | ||||||
|     """Tests that parsers can be created with parent parsers""" |     """Tests that parsers can be created with parent parsers""" | ||||||
| 
 | 
 | ||||||
|  | @ -3216,6 +3223,7 @@ def test_mutex_groups_parents(self): | ||||||
| # Mutually exclusive group tests | # Mutually exclusive group tests | ||||||
| # ============================== | # ============================== | ||||||
| 
 | 
 | ||||||
|  | @force_not_colorized_test_class | ||||||
| class TestMutuallyExclusiveGroupErrors(TestCase): | class TestMutuallyExclusiveGroupErrors(TestCase): | ||||||
| 
 | 
 | ||||||
|     def test_invalid_add_argument_group(self): |     def test_invalid_add_argument_group(self): | ||||||
|  | @ -3344,21 +3352,25 @@ def test_successes_when_required(self): | ||||||
|                 actual_ns = parse_args(args_string.split()) |                 actual_ns = parse_args(args_string.split()) | ||||||
|                 self.assertEqual(actual_ns, expected_ns) |                 self.assertEqual(actual_ns, expected_ns) | ||||||
| 
 | 
 | ||||||
|  |     @force_not_colorized | ||||||
|     def test_usage_when_not_required(self): |     def test_usage_when_not_required(self): | ||||||
|         format_usage = self.get_parser(required=False).format_usage |         format_usage = self.get_parser(required=False).format_usage | ||||||
|         expected_usage = self.usage_when_not_required |         expected_usage = self.usage_when_not_required | ||||||
|         self.assertEqual(format_usage(), textwrap.dedent(expected_usage)) |         self.assertEqual(format_usage(), textwrap.dedent(expected_usage)) | ||||||
| 
 | 
 | ||||||
|  |     @force_not_colorized | ||||||
|     def test_usage_when_required(self): |     def test_usage_when_required(self): | ||||||
|         format_usage = self.get_parser(required=True).format_usage |         format_usage = self.get_parser(required=True).format_usage | ||||||
|         expected_usage = self.usage_when_required |         expected_usage = self.usage_when_required | ||||||
|         self.assertEqual(format_usage(), textwrap.dedent(expected_usage)) |         self.assertEqual(format_usage(), textwrap.dedent(expected_usage)) | ||||||
| 
 | 
 | ||||||
|  |     @force_not_colorized | ||||||
|     def test_help_when_not_required(self): |     def test_help_when_not_required(self): | ||||||
|         format_help = self.get_parser(required=False).format_help |         format_help = self.get_parser(required=False).format_help | ||||||
|         help = self.usage_when_not_required + self.help |         help = self.usage_when_not_required + self.help | ||||||
|         self.assertEqual(format_help(), textwrap.dedent(help)) |         self.assertEqual(format_help(), textwrap.dedent(help)) | ||||||
| 
 | 
 | ||||||
|  |     @force_not_colorized | ||||||
|     def test_help_when_required(self): |     def test_help_when_required(self): | ||||||
|         format_help = self.get_parser(required=True).format_help |         format_help = self.get_parser(required=True).format_help | ||||||
|         help = self.usage_when_required + self.help |         help = self.usage_when_required + self.help | ||||||
|  | @ -4030,11 +4042,13 @@ def _test(self, tester, parser_text): | ||||||
|                 tester.maxDiff = None |                 tester.maxDiff = None | ||||||
|                 tester.assertEqual(expected_text, parser_text) |                 tester.assertEqual(expected_text, parser_text) | ||||||
| 
 | 
 | ||||||
|  |             @force_not_colorized | ||||||
|             def test_format(self, tester): |             def test_format(self, tester): | ||||||
|                 parser = self._get_parser(tester) |                 parser = self._get_parser(tester) | ||||||
|                 format = getattr(parser, 'format_%s' % self.func_suffix) |                 format = getattr(parser, 'format_%s' % self.func_suffix) | ||||||
|                 self._test(tester, format()) |                 self._test(tester, format()) | ||||||
| 
 | 
 | ||||||
|  |             @force_not_colorized | ||||||
|             def test_print(self, tester): |             def test_print(self, tester): | ||||||
|                 parser = self._get_parser(tester) |                 parser = self._get_parser(tester) | ||||||
|                 print_ = getattr(parser, 'print_%s' % self.func_suffix) |                 print_ = getattr(parser, 'print_%s' % self.func_suffix) | ||||||
|  | @ -4047,6 +4061,7 @@ def test_print(self, tester): | ||||||
|                     setattr(sys, self.std_name, old_stream) |                     setattr(sys, self.std_name, old_stream) | ||||||
|                 self._test(tester, parser_text) |                 self._test(tester, parser_text) | ||||||
| 
 | 
 | ||||||
|  |             @force_not_colorized | ||||||
|             def test_print_file(self, tester): |             def test_print_file(self, tester): | ||||||
|                 parser = self._get_parser(tester) |                 parser = self._get_parser(tester) | ||||||
|                 print_ = getattr(parser, 'print_%s' % self.func_suffix) |                 print_ = getattr(parser, 'print_%s' % self.func_suffix) | ||||||
|  | @ -4788,6 +4803,7 @@ class TestHelpUsageMetavarsSpacesParentheses(HelpTestCase): | ||||||
|     version = '' |     version = '' | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @force_not_colorized_test_class | ||||||
| class TestHelpUsageNoWhitespaceCrash(TestCase): | class TestHelpUsageNoWhitespaceCrash(TestCase): | ||||||
| 
 | 
 | ||||||
|     def test_all_suppressed_mutex_followed_by_long_arg(self): |     def test_all_suppressed_mutex_followed_by_long_arg(self): | ||||||
|  | @ -5469,6 +5485,7 @@ def custom_type(string): | ||||||
|     version = '' |     version = '' | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @force_not_colorized_test_class | ||||||
| class TestHelpCustomHelpFormatter(TestCase): | class TestHelpCustomHelpFormatter(TestCase): | ||||||
|     maxDiff = None |     maxDiff = None | ||||||
| 
 | 
 | ||||||
|  | @ -5765,6 +5782,7 @@ def test_conflict_error(self): | ||||||
|         self.assertRaises(argparse.ArgumentError, |         self.assertRaises(argparse.ArgumentError, | ||||||
|                           parser.add_argument, '--spam') |                           parser.add_argument, '--spam') | ||||||
| 
 | 
 | ||||||
|  |     @force_not_colorized | ||||||
|     def test_resolve_error(self): |     def test_resolve_error(self): | ||||||
|         get_parser = argparse.ArgumentParser |         get_parser = argparse.ArgumentParser | ||||||
|         parser = get_parser(prog='PROG', conflict_handler='resolve') |         parser = get_parser(prog='PROG', conflict_handler='resolve') | ||||||
|  | @ -6031,6 +6049,7 @@ def test_argument_error(self): | ||||||
| 
 | 
 | ||||||
| class TestArgumentTypeError(TestCase): | class TestArgumentTypeError(TestCase): | ||||||
| 
 | 
 | ||||||
|  |     @force_not_colorized | ||||||
|     def test_argument_type_error(self): |     def test_argument_type_error(self): | ||||||
| 
 | 
 | ||||||
|         def spam(string): |         def spam(string): | ||||||
|  | @ -6829,6 +6848,7 @@ def setUp(self): | ||||||
|         metavar = '<http[s]://example:1234>' |         metavar = '<http[s]://example:1234>' | ||||||
|         self.parser.add_argument('--proxy', metavar=metavar) |         self.parser.add_argument('--proxy', metavar=metavar) | ||||||
| 
 | 
 | ||||||
|  |     @force_not_colorized | ||||||
|     def test_help_with_metavar(self): |     def test_help_with_metavar(self): | ||||||
|         help_text = self.parser.format_help() |         help_text = self.parser.format_help() | ||||||
|         self.assertEqual(help_text, textwrap.dedent('''\ |         self.assertEqual(help_text, textwrap.dedent('''\ | ||||||
|  | @ -6994,6 +7014,7 @@ def test_os_error(self): | ||||||
|                                self.parser.parse_args, ['@no-such-file']) |                                self.parser.parse_args, ['@no-such-file']) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @force_not_colorized_test_class | ||||||
| class TestProgName(TestCase): | class TestProgName(TestCase): | ||||||
|     source = textwrap.dedent('''\ |     source = textwrap.dedent('''\ | ||||||
|         import argparse |         import argparse | ||||||
|  |  | ||||||
|  | @ -2792,6 +2792,7 @@ def test_cli_verbose(self): | ||||||
|             out = self.expect_success("-v", fn) |             out = self.expect_success("-v", fn) | ||||||
|             self.assertEqual(out.strip(), fn) |             self.assertEqual(out.strip(), fn) | ||||||
| 
 | 
 | ||||||
|  |     @support.force_not_colorized | ||||||
|     def test_cli_help(self): |     def test_cli_help(self): | ||||||
|         out = self.expect_success("-h") |         out = self.expect_success("-h") | ||||||
|         self.assertIn("usage: clinic.py", out) |         self.assertIn("usage: clinic.py", out) | ||||||
|  |  | ||||||
|  | @ -0,0 +1 @@ | ||||||
|  | Enable color help by default in :mod:`argparse`. | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Miss Islington (bot)
						Miss Islington (bot)