Skip to content
Closed

AI junk #3666

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ Unreleased
This stripping was lost in `8.4.0` when {pr}`2969` began writing the
prompt with `input()` directly. {issue}`3572` {pr}`3653`
- Fix test failures when using pytest >= 9.1. {pr}`3656`
- `style()` and `secho()` no longer silently drop the 256-color index `0`
(black) passed as `fg` or `bg`, and now validate color arguments: unknown
names, `bool` values, indices or RGB components outside `[0, 255]`, and
malformed tuples all raise `TypeError`. Previously, falsy values (`""`,
`()`, `False`) were silently ignored, and malformed truthy values raised
inconsistent errors or emitted invalid escape codes. {pr}`3666`

## Version 8.4.2

Expand Down
44 changes: 26 additions & 18 deletions src/click/termui.py
Original file line number Diff line number Diff line change
Expand Up @@ -570,14 +570,22 @@ def clear() -> None:


def _interpret_color(color: int | tuple[int, int, int] | str, offset: int = 0) -> str:
if isinstance(color, int):
return f"{38 + offset};5;{color:d}"

if isinstance(color, (tuple, list)):
r, g, b = color
return f"{38 + offset};2;{r:d};{g:d};{b:d}"

return str(_ansi_colors[color] + offset)
# bool is an int subclass: without the exclusion, True and False would
# silently render as the palette indices 1 and 0.
if isinstance(color, int) and not isinstance(color, bool):
if 0 <= color <= 255:
return f"{38 + offset};5;{color:d}"
elif isinstance(color, (tuple, list)):
if len(color) == 3 and all(
isinstance(c, int) and not isinstance(c, bool) and 0 <= c <= 255
for c in color
):
r, g, b = color
return f"{38 + offset};2;{r:d};{g:d};{b:d}"
elif isinstance(color, str) and color in _ansi_colors:
return str(_ansi_colors[color] + offset)

raise TypeError(_("Unknown color {colour!r}").format(colour=color))


def style(
Expand Down Expand Up @@ -655,6 +663,12 @@ def style(
string which means that styles do not carry over. This
can be disabled to compose styles.

.. versionchanged:: 8.5
The 256-color index ``0`` is no longer ignored. All other invalid
``fg`` and ``bg`` values now raise :exc:`TypeError`: previously,
falsy values were silently ignored and malformed truthy values
raised inconsistent errors or emitted invalid escape codes.

.. versionchanged:: 8.0
A non-string ``message`` is converted to a string.

Expand All @@ -675,17 +689,11 @@ def style(

bits = []

if fg:
try:
bits.append(f"\033[{_interpret_color(fg)}m")
except KeyError:
raise TypeError(_("Unknown color {colour!r}").format(colour=fg)) from None
if fg is not None:
bits.append(f"\033[{_interpret_color(fg)}m")

if bg:
try:
bits.append(f"\033[{_interpret_color(bg, 10)}m")
except KeyError:
raise TypeError(_("Unknown color {colour!r}").format(colour=bg)) from None
if bg is not None:
bits.append(f"\033[{_interpret_color(bg, 10)}m")

if bold is not None:
bits.append(f"\033[{1 if bold else 22}m")
Expand Down
59 changes: 59 additions & 0 deletions tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ def test_echo_no_streams(monkeypatch, runner):
@pytest.mark.parametrize(
("styles", "ref"),
[
({}, "x y\x1b[0m"),
({"fg": "black"}, "\x1b[30mx y\x1b[0m"),
({"fg": "red"}, "\x1b[31mx y\x1b[0m"),
({"fg": "green"}, "\x1b[32mx y\x1b[0m"),
Expand All @@ -122,6 +123,15 @@ def test_echo_no_streams(monkeypatch, runner):
({"fg": "magenta"}, "\x1b[35mx y\x1b[0m"),
({"fg": "cyan"}, "\x1b[36mx y\x1b[0m"),
({"fg": "white"}, "\x1b[37mx y\x1b[0m"),
({"fg": "bright_black"}, "\x1b[90mx y\x1b[0m"),
({"fg": "bright_red"}, "\x1b[91mx y\x1b[0m"),
({"fg": "bright_green"}, "\x1b[92mx y\x1b[0m"),
({"fg": "bright_yellow"}, "\x1b[93mx y\x1b[0m"),
({"fg": "bright_blue"}, "\x1b[94mx y\x1b[0m"),
({"fg": "bright_magenta"}, "\x1b[95mx y\x1b[0m"),
({"fg": "bright_cyan"}, "\x1b[96mx y\x1b[0m"),
({"fg": "bright_white"}, "\x1b[97mx y\x1b[0m"),
({"fg": "reset"}, "\x1b[39mx y\x1b[0m"),
({"bg": "black"}, "\x1b[40mx y\x1b[0m"),
({"bg": "red"}, "\x1b[41mx y\x1b[0m"),
({"bg": "green"}, "\x1b[42mx y\x1b[0m"),
Expand All @@ -130,8 +140,28 @@ def test_echo_no_streams(monkeypatch, runner):
({"bg": "magenta"}, "\x1b[45mx y\x1b[0m"),
({"bg": "cyan"}, "\x1b[46mx y\x1b[0m"),
({"bg": "white"}, "\x1b[47mx y\x1b[0m"),
({"bg": "bright_black"}, "\x1b[100mx y\x1b[0m"),
({"bg": "bright_red"}, "\x1b[101mx y\x1b[0m"),
({"bg": "bright_green"}, "\x1b[102mx y\x1b[0m"),
({"bg": "bright_yellow"}, "\x1b[103mx y\x1b[0m"),
({"bg": "bright_blue"}, "\x1b[104mx y\x1b[0m"),
({"bg": "bright_magenta"}, "\x1b[105mx y\x1b[0m"),
({"bg": "bright_cyan"}, "\x1b[106mx y\x1b[0m"),
({"bg": "bright_white"}, "\x1b[107mx y\x1b[0m"),
({"bg": "reset"}, "\x1b[49mx y\x1b[0m"),
({"fg": 91}, "\x1b[38;5;91mx y\x1b[0m"),
({"bg": 91}, "\x1b[48;5;91mx y\x1b[0m"),
({"fg": 255}, "\x1b[38;5;255mx y\x1b[0m"),
({"bg": 255}, "\x1b[48;5;255mx y\x1b[0m"),
({"fg": (135, 0, 175)}, "\x1b[38;2;135;0;175mx y\x1b[0m"),
({"bg": (135, 0, 175)}, "\x1b[48;2;135;0;175mx y\x1b[0m"),
({"bg": [135, 0, 175]}, "\x1b[48;2;135;0;175mx y\x1b[0m"),
({"fg": (0, 0, 0)}, "\x1b[38;2;0;0;0mx y\x1b[0m"),
({"fg": (255, 255, 255)}, "\x1b[38;2;255;255;255mx y\x1b[0m"),
# 256-color index 0 (black) is valid and must not be dropped by a
# truthiness check on fg/bg.
({"fg": 0}, "\x1b[38;5;0mx y\x1b[0m"),
({"bg": 0}, "\x1b[48;5;0mx y\x1b[0m"),
({"bold": True}, "\x1b[1mx y\x1b[0m"),
({"dim": True}, "\x1b[2mx y\x1b[0m"),
({"underline": True}, "\x1b[4mx y\x1b[0m"),
Expand All @@ -156,6 +186,35 @@ def test_styling(styles, ref):
assert click.unstyle(ref) == "x y"


@pytest.mark.parametrize("param", ["fg", "bg"])
@pytest.mark.parametrize(
"value",
[
"",
"banana",
"BLACK",
b"red",
True,
False,
-1,
256,
0.0,
(),
[],
(0, 0),
(0, 0, 0, 0),
("0", "0", "0"),
(True, False, True),
(-1, 0, 0),
(0, 256, 0),
(0.0, 0.0, 0.0),
],
)
def test_styling_invalid_color(param, value):
with pytest.raises(TypeError, match="Unknown color"):
click.style("x y", **{param: value})


@pytest.mark.parametrize(("text", "expect"), [("\x1b[?25lx y\x1b[?25h", "x y")])
def test_unstyle_other_ansi(text, expect):
assert click.unstyle(text) == expect
Expand Down
Loading