Skip to content

Commit bf152cf

Browse files
committed
Simplify color state management
1 parent b32d6dd commit bf152cf

File tree

3 files changed

+21
-50
lines changed

3 files changed

+21
-50
lines changed

click_extra/colorize.py

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
from cloup._util import identity
3535
from cloup.styling import Color
3636

37-
from . import ParameterSource, Style, get_current_context
37+
from . import ParameterSource, Style
3838
from .parameters import ExtraOption
3939

4040
TYPE_CHECKING = False
@@ -258,19 +258,10 @@ def disable_colors(
258258
# One env var is enough to activate colorization.
259259
value = True in colorize_from_env
260260

261-
# There is an undocumented color flag in context:
262-
# https://github.com/pallets/click/blob/65eceb0/src/click/globals.py#L56-L69
261+
# Set the official context color flag. This is used by Click's
262+
# ``resolve_color_default()`` → ``should_strip_ansi()`` chain in ``echo()``.
263263
ctx.color = value
264264

265-
if not value:
266-
267-
def restore_original_styling():
268-
"""Reset color flag in context."""
269-
ctx = get_current_context()
270-
ctx.color = None
271-
272-
ctx.call_on_close(restore_original_styling)
273-
274265
def __init__(
275266
self,
276267
param_decls: Sequence[str] | None = None,

click_extra/commands.py

Lines changed: 15 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,12 @@ class ExtraContext(cloup.Context):
5353
"""Like ``cloup._context.Context``, but with the ability to populate the context's
5454
``meta`` property at instantiation.
5555
56-
Also inherits ``color`` property from parent context. And sets it to `True` for
57-
parentless contexts at instantiatiom, so we can always have colorized output.
56+
Also defaults ``color`` to ``True`` for root contexts (i.e. without a parent), so
57+
help screens are always colorized — even when piped. Click's own default is ``None``
58+
(auto-detect via TTY), which strips colors in non-interactive contexts.
59+
60+
Parent-to-child color inheritance is handled by Click itself at ``Context.__init__``
61+
time, so no property override is needed.
5862
5963
.. todo::
6064
Propose addition of ``meta`` keyword upstream to Click.
@@ -66,45 +70,22 @@ class ExtraContext(cloup.Context):
6670
def __init__(self, *args, meta: dict[str, Any] | None = None, **kwargs) -> None:
6771
"""Like parent's context but with an extra ``meta`` keyword-argument.
6872
69-
Also force ``color`` property default to `True` if not provided by user and
70-
this context has no parent.
73+
Also force ``color`` default to ``True`` if not provided by user and this
74+
context has no parent.
7175
"""
7276
super().__init__(*args, **kwargs)
7377

78+
# Click defaults root ``ctx.color`` to ``None`` (auto-detect via TTY), which
79+
# strips colors when piped. Override to ``True`` for parentless contexts so
80+
# help screens are always colorized by default. The ``ColorOption`` callback
81+
# will set the final value later, respecting ``--no-color`` and env vars.
82+
if not self.parent and self.color is None:
83+
self.color = True
84+
7485
# Update the context's meta property with the one provided by user.
7586
if meta:
7687
self._meta.update(meta)
7788

78-
# Transfer user color setting to our internally managed value.
79-
self._color: bool | None = kwargs.get("color", None)
80-
81-
# A Context created from scratch, i.e. without a parent, and whose color
82-
# setting is set to auto-detect (i.e. is None), will defaults to forced
83-
# colorized output.
84-
if not self.parent and self._color is None:
85-
self._color = True
86-
87-
@property
88-
def color(self) -> bool | None:
89-
"""Overrides ``Context.color`` to allow inheritance from parent context.
90-
91-
Returns the color setting of the parent context if it exists and the color is
92-
not set on the current context.
93-
"""
94-
if self._color is None and self.parent:
95-
return self.parent.color
96-
return self._color
97-
98-
@color.setter
99-
def color(self, value: bool | None) -> None:
100-
"""Set the color value of the current context."""
101-
self._color = value
102-
103-
@color.deleter
104-
def color(self) -> None:
105-
"""Reset the color value so it defaults to inheritance from parent's."""
106-
self._color = None
107-
10889

10990
def default_extra_params() -> list[click.Option]:
11091
"""Default additional options added to ``@command`` and ``@group``.

click_extra/testing.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -246,11 +246,10 @@ def invoke( # type: ignore[override]
246246

247247
if color == "forced":
248248
# Pass the color argument as an extra parameter to the invoked CLI.
249+
# This works around Click issue #2110: ``CliRunner.invoke(color=True)``
250+
# controls the test "terminal" but cannot simultaneously pass ``color``
251+
# through to ``Context``.
249252
extra["color"] = True
250-
# TODO: investigate the possibility of forcing coloring on ``echo`` too,
251-
# because by default, Windows is rendered colorless:
252-
# https://github.com/pallets/click/blob/0c85d80/src/click/utils.py#L295-L296
253-
# echo_extra["color"] = True
254253

255254
# The class attribute ``force_color`` overrides the ``color`` parameter.
256255
if self.force_color:

0 commit comments

Comments
 (0)