Repository: pallets/click
Pull request #1429 (2019-11-22 17:24:32)

click.style: add support for 256/RGB colors
This adds support for true-color text (using the \033[38;2;RRR;GGG;BBB escape sequence) and 256-color palette (using the \033[38;5;NNN escape sequence). It's backwards compatible with the current string-based colors, but adds a type-checking helper to handle integers or tuples/lists of 3 integers.

Zethson: Very nice!

Patch:
diff --git a/CHANGES.rst b/CHANGES.rst
index 8f58527..b4a8b1c 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -77,6 +77,8 @@ Unreleased
     to ``--name``. :pr:`1355`
 -   A context's ``show_default`` parameter defaults to the value from
     the parent context. :issue:`1565`
+-   ``click.style()`` can output 256 and RGB color codes. Most modern
+    terminals support these codes. :pr:`1429`
 
 
 Version 7.1.2
diff --git a/src/click/termui.py b/src/click/termui.py
index 24de36e..44e0b80 100644
--- a/src/click/termui.py
+++ b/src/click/termui.py
@@ -441,6 +441,17 @@ def clear():
         sys.stdout.write("\033[2J\033[1;1H")
 
 
+def _interpret_color(color, offset=0):
+    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)
+
+
 def style(
     text,
     fg=None,
@@ -462,6 +473,7 @@ def style(
         click.echo(click.style('Hello World!', fg='green'))
         click.echo(click.style('ATTENTION!', blink=True))
         click.echo(click.style('Some things', reverse=True, fg='cyan'))
+        click.echo(click.style('More colors', fg=(255, 12, 128), bg=117))
 
     Supported color names:
 
@@ -483,6 +495,16 @@ def style(
     * ``bright_white``
     * ``reset`` (reset the color code only)
 
+    If the terminal supports it, color may also be specified as:
+
+    -   An integer in the interval [0, 255]. The terminal must support
+        8-bit/256-color mode.
+    -   An RGB tuple of three integers in [0, 255]. The terminal must
+        support 24-bit/true-color mode.
+
+    See https://en.wikipedia.org/wiki/ANSI_color and
+    https://gist.github.com/XVilka/8346728 for more information.
+
     :param text: the string to style with ansi codes.
     :param fg: if provided this will become the foreground color.
     :param bg: if provided this will become the background color.
@@ -501,6 +523,9 @@ def style(
     .. versionchanged:: 8.0
         A non-string ``message`` is converted to a string.
 
+    .. versionchanged:: 8.0
+       Added support for 256 and RGB color codes.
+
     .. versionchanged:: 7.0
         Added support for bright colors.
 
@@ -510,16 +535,19 @@ def style(
         text = str(text)
 
     bits = []
+
     if fg:
         try:
-            bits.append(f"\033[{_ansi_colors[fg]}m")
+            bits.append(f"\033[{_interpret_color(fg)}m")
         except KeyError:
             raise TypeError(f"Unknown color {fg!r}")
+
     if bg:
         try:
-            bits.append(f"\033[{_ansi_colors[bg] + 10}m")
+            bits.append(f"\033[{_interpret_color(bg, 10)}m")
         except KeyError:
             raise TypeError(f"Unknown color {bg!r}")
+
     if bold is not None:
         bits.append(f"\033[{1 if bold else 22}m")
     if dim is not None:


Test patch:
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 8b1277b..a588904 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -62,6 +62,8 @@ def test_echo_custom_file():
         ({"bg": "magenta"}, "\x1b[45mx y\x1b[0m"),
         ({"bg": "cyan"}, "\x1b[46mx y\x1b[0m"),
         ({"bg": "white"}, "\x1b[47mx y\x1b[0m"),
+        ({"bg": 91}, "\x1b[48;5;91mx y\x1b[0m"),
+        ({"bg": (135, 0, 175)}, "\x1b[48;2;135;0;175mx y\x1b[0m"),
         ({"blink": True}, "\x1b[5mx y\x1b[0m"),
         ({"underline": True}, "\x1b[4mx y\x1b[0m"),
         ({"bold": True}, "\x1b[1mx y\x1b[0m"),