mirror of
https://github.com/python/cpython.git
synced 2025-10-29 20:51:26 +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