Source code for xonsh.color_tools

"""Tools for color handling in xonsh.

This includes Convert values between RGB hex codes and xterm-256
color codes. Parts of this file were originally forked from Micah Elliott
http://MicahElliott.com Copyright (C) 2011 Micah Elliott. All rights reserved.
WTFPL http://sam.zoy.org/wtfpl/
"""

import math
import re

from xonsh.lazyasd import LazyObject, lazyobject
from xonsh.tools import print_warning

_NO_COLOR_WARNING_SHOWN = False

RE_BACKGROUND = LazyObject(
    lambda: re.compile("(BG#|BGHEX|BACKGROUND)"), globals(), "RE_BACKGROUND"
)


[docs] class COLORS: """constants""" RESET = "{RESET}" RED = "{RED}" GREEN = "{GREEN}" BOLD_RED = "{BOLD_RED}" BOLD_GREEN = "{BOLD_GREEN}"
@lazyobject def KNOWN_XONSH_COLORS(): """These are the minimum number of colors that need to be implemented by any style. """ return frozenset( [ "DEFAULT", "BLACK", "RED", "GREEN", "YELLOW", "BLUE", "PURPLE", "CYAN", "WHITE", "INTENSE_BLACK", "INTENSE_RED", "INTENSE_GREEN", "INTENSE_YELLOW", "INTENSE_BLUE", "INTENSE_PURPLE", "INTENSE_CYAN", "INTENSE_WHITE", ] ) @lazyobject def BASE_XONSH_COLORS(): return { "BLACK": (0, 0, 0), "RED": (170, 0, 0), "GREEN": (0, 170, 0), "YELLOW": (170, 85, 0), "BLUE": (0, 0, 170), "PURPLE": (170, 0, 170), "CYAN": (0, 170, 170), "WHITE": (170, 170, 170), "INTENSE_BLACK": (85, 85, 85), "INTENSE_RED": (255, 85, 85), "INTENSE_GREEN": (85, 255, 85), "INTENSE_YELLOW": (255, 255, 85), "INTENSE_BLUE": (85, 85, 255), "INTENSE_PURPLE": (255, 85, 255), "INTENSE_CYAN": (85, 255, 255), "INTENSE_WHITE": (255, 255, 255), } @lazyobject def RE_XONSH_COLOR(): hex = "[0-9a-fA-F]" s = ( # background r"((?P<background>BACKGROUND_)|(?P<modifiers>(" # modifiers, only apply to foreground r"BOLD_|FAINT_|ITALIC_|UNDERLINE_|SLOWBLINK_|FASTBLINK_|INVERT_|CONCEAL_|" r"STRIKETHROUGH_|BOLDOFF_|FAINTOFF_|ITALICOFF_|UNDERLINEOFF_|BLINKOFF_|" r"INVERTOFF_|REVEALOFF_|STRIKETHROUGHOFF_)+))?" # colors r"(?P<color>BLACK|RED|GREEN|YELLOW|BLUE|PURPLE|CYAN|WHITE|INTENSE_BLACK|" r"INTENSE_RED|INTENSE_GREEN|INTENSE_YELLOW|INTENSE_BLUE|INTENSE_PURPLE|" r"INTENSE_CYAN|INTENSE_WHITE|#" + hex + "{3}|#" + hex + "{6}|DEFAULT)" ) bghex = ( "bg#" + hex + "{3}|" "bg#" + hex + "{6}|" "BG#" + hex + "{3}|" "BG#" + hex + "{6}" ) s = "^((?P<reset>RESET|NO_COLOR)|(?P<bghex>" + bghex + ")|" + s + ")$" return re.compile(s)
[docs] def iscolor(s): """Tests if a string is a valid color""" return RE_XONSH_COLOR.match(s) is not None
@lazyobject def CLUT(): """color look-up table""" return [ # 8-bit, RGB hex # Primary 3-bit (8 colors). Unique representation! ("0", "000000"), ("1", "800000"), ("2", "008000"), ("3", "808000"), ("4", "000080"), ("5", "800080"), ("6", "008080"), ("7", "c0c0c0"), # Equivalent "bright" versions of original 8 colors. ("8", "808080"), ("9", "ff0000"), ("10", "00ff00"), ("11", "ffff00"), ("12", "0000ff"), ("13", "ff00ff"), ("14", "00ffff"), ("15", "ffffff"), # Strictly ascending. ("16", "000000"), ("17", "00005f"), ("18", "000087"), ("19", "0000af"), ("20", "0000d7"), ("21", "0000ff"), ("22", "005f00"), ("23", "005f5f"), ("24", "005f87"), ("25", "005faf"), ("26", "005fd7"), ("27", "005fff"), ("28", "008700"), ("29", "00875f"), ("30", "008787"), ("31", "0087af"), ("32", "0087d7"), ("33", "0087ff"), ("34", "00af00"), ("35", "00af5f"), ("36", "00af87"), ("37", "00afaf"), ("38", "00afd7"), ("39", "00afff"), ("40", "00d700"), ("41", "00d75f"), ("42", "00d787"), ("43", "00d7af"), ("44", "00d7d7"), ("45", "00d7ff"), ("46", "00ff00"), ("47", "00ff5f"), ("48", "00ff87"), ("49", "00ffaf"), ("50", "00ffd7"), ("51", "00ffff"), ("52", "5f0000"), ("53", "5f005f"), ("54", "5f0087"), ("55", "5f00af"), ("56", "5f00d7"), ("57", "5f00ff"), ("58", "5f5f00"), ("59", "5f5f5f"), ("60", "5f5f87"), ("61", "5f5faf"), ("62", "5f5fd7"), ("63", "5f5fff"), ("64", "5f8700"), ("65", "5f875f"), ("66", "5f8787"), ("67", "5f87af"), ("68", "5f87d7"), ("69", "5f87ff"), ("70", "5faf00"), ("71", "5faf5f"), ("72", "5faf87"), ("73", "5fafaf"), ("74", "5fafd7"), ("75", "5fafff"), ("76", "5fd700"), ("77", "5fd75f"), ("78", "5fd787"), ("79", "5fd7af"), ("80", "5fd7d7"), ("81", "5fd7ff"), ("82", "5fff00"), ("83", "5fff5f"), ("84", "5fff87"), ("85", "5fffaf"), ("86", "5fffd7"), ("87", "5fffff"), ("88", "870000"), ("89", "87005f"), ("90", "870087"), ("91", "8700af"), ("92", "8700d7"), ("93", "8700ff"), ("94", "875f00"), ("95", "875f5f"), ("96", "875f87"), ("97", "875faf"), ("98", "875fd7"), ("99", "875fff"), ("100", "878700"), ("101", "87875f"), ("102", "878787"), ("103", "8787af"), ("104", "8787d7"), ("105", "8787ff"), ("106", "87af00"), ("107", "87af5f"), ("108", "87af87"), ("109", "87afaf"), ("110", "87afd7"), ("111", "87afff"), ("112", "87d700"), ("113", "87d75f"), ("114", "87d787"), ("115", "87d7af"), ("116", "87d7d7"), ("117", "87d7ff"), ("118", "87ff00"), ("119", "87ff5f"), ("120", "87ff87"), ("121", "87ffaf"), ("122", "87ffd7"), ("123", "87ffff"), ("124", "af0000"), ("125", "af005f"), ("126", "af0087"), ("127", "af00af"), ("128", "af00d7"), ("129", "af00ff"), ("130", "af5f00"), ("131", "af5f5f"), ("132", "af5f87"), ("133", "af5faf"), ("134", "af5fd7"), ("135", "af5fff"), ("136", "af8700"), ("137", "af875f"), ("138", "af8787"), ("139", "af87af"), ("140", "af87d7"), ("141", "af87ff"), ("142", "afaf00"), ("143", "afaf5f"), ("144", "afaf87"), ("145", "afafaf"), ("146", "afafd7"), ("147", "afafff"), ("148", "afd700"), ("149", "afd75f"), ("150", "afd787"), ("151", "afd7af"), ("152", "afd7d7"), ("153", "afd7ff"), ("154", "afff00"), ("155", "afff5f"), ("156", "afff87"), ("157", "afffaf"), ("158", "afffd7"), ("159", "afffff"), ("160", "d70000"), ("161", "d7005f"), ("162", "d70087"), ("163", "d700af"), ("164", "d700d7"), ("165", "d700ff"), ("166", "d75f00"), ("167", "d75f5f"), ("168", "d75f87"), ("169", "d75faf"), ("170", "d75fd7"), ("171", "d75fff"), ("172", "d78700"), ("173", "d7875f"), ("174", "d78787"), ("175", "d787af"), ("176", "d787d7"), ("177", "d787ff"), ("178", "d7af00"), ("179", "d7af5f"), ("180", "d7af87"), ("181", "d7afaf"), ("182", "d7afd7"), ("183", "d7afff"), ("184", "d7d700"), ("185", "d7d75f"), ("186", "d7d787"), ("187", "d7d7af"), ("188", "d7d7d7"), ("189", "d7d7ff"), ("190", "d7ff00"), ("191", "d7ff5f"), ("192", "d7ff87"), ("193", "d7ffaf"), ("194", "d7ffd7"), ("195", "d7ffff"), ("196", "ff0000"), ("197", "ff005f"), ("198", "ff0087"), ("199", "ff00af"), ("200", "ff00d7"), ("201", "ff00ff"), ("202", "ff5f00"), ("203", "ff5f5f"), ("204", "ff5f87"), ("205", "ff5faf"), ("206", "ff5fd7"), ("207", "ff5fff"), ("208", "ff8700"), ("209", "ff875f"), ("210", "ff8787"), ("211", "ff87af"), ("212", "ff87d7"), ("213", "ff87ff"), ("214", "ffaf00"), ("215", "ffaf5f"), ("216", "ffaf87"), ("217", "ffafaf"), ("218", "ffafd7"), ("219", "ffafff"), ("220", "ffd700"), ("221", "ffd75f"), ("222", "ffd787"), ("223", "ffd7af"), ("224", "ffd7d7"), ("225", "ffd7ff"), ("226", "ffff00"), ("227", "ffff5f"), ("228", "ffff87"), ("229", "ffffaf"), ("230", "ffffd7"), ("231", "ffffff"), # Gray-scale range. ("232", "080808"), ("233", "121212"), ("234", "1c1c1c"), ("235", "262626"), ("236", "303030"), ("237", "3a3a3a"), ("238", "444444"), ("239", "4e4e4e"), ("240", "585858"), ("241", "626262"), ("242", "6c6c6c"), ("243", "767676"), ("244", "808080"), ("245", "8a8a8a"), ("246", "949494"), ("247", "9e9e9e"), ("248", "a8a8a8"), ("249", "b2b2b2"), ("250", "bcbcbc"), ("251", "c6c6c6"), ("252", "d0d0d0"), ("253", "dadada"), ("254", "e4e4e4"), ("255", "eeeeee"), ] def _str2hex(hexstr): return int(hexstr, 16) def _strip_hash(rgb): # Strip leading `#` if exists. if rgb.startswith("#"): rgb = rgb.lstrip("#") return rgb @lazyobject def SHORT_TO_RGB(): return dict(CLUT) @lazyobject def RGB_TO_SHORT(): return {v: k for k, v in SHORT_TO_RGB.items()}
[docs] def short2rgb(short): short = short.lstrip("0") if short == "": short = "0" return SHORT_TO_RGB[short]
[docs] def rgb_to_256(rgb): """Find the closest ANSI 256 approximation to the given RGB value. >>> rgb2short('123456') ('23', '005f5f') >>> rgb2short('ffffff') ('231', 'ffffff') >>> rgb2short('0DADD6') # vimeo logo ('38', '00afd7') Parameters ---------- rgb : Hex code representing an RGB value, eg, 'abcdef' Returns ------- Tuple of String between 0 and 255 (compatible with xterm) and hex code (length-6). """ rgb = rgb.lstrip("#") if len(rgb) == 0: return "0", "000000" incs = (0x00, 0x5F, 0x87, 0xAF, 0xD7, 0xFF) # Break 6-char RGB code into 3 integer vals. parts = rgb_to_ints(rgb) res = [] for part in parts: i = 0 while i < len(incs) - 1: s, b = incs[i], incs[i + 1] # smaller, bigger if s <= part <= b: s1 = abs(s - part) b1 = abs(b - part) if s1 < b1: closest = s else: closest = b res.append(closest) break i += 1 res = "".join([("%02.x" % i) for i in res]) equiv = RGB_TO_SHORT[res] return equiv, res
rgb2short = rgb_to_256 @lazyobject def RE_RGB3(): return re.compile(r"(.)(.)(.)") @lazyobject def RE_RGB6(): return re.compile(r"(..)(..)(..)")
[docs] def rgb_to_ints(rgb): if len(rgb) == 6: return tuple(int(h, 16) for h in RE_RGB6.split(rgb)[1:4]) else: return tuple(int(h * 2, 16) for h in RE_RGB3.split(rgb)[1:4])
[docs] def short_to_ints(short): """Coverts a short (256) color to a 3-tuple of ints.""" return rgb_to_ints(short2rgb(short))
[docs] def color_dist(x, y) -> float: return math.sqrt((x[0] - y[0]) ** 2 + (x[1] - y[1]) ** 2 + (x[2] - y[2]) ** 2)
[docs] def find_closest_color(x, palette): return min( sorted(palette.keys(), reverse=True), key=lambda k: color_dist(x, palette[k]) )
[docs] def make_palette(strings): """Makes a color palette from a collection of strings.""" palette = {} for s in strings: while "#" in s: _, t = s.split("#", 1) t, _, s = t.partition(" ") palette[t] = rgb_to_ints(t) return palette
[docs] def warn_deprecated_no_color(): """Show a warning once if NO_COLOR was used instead of RESET.""" global _NO_COLOR_WARNING_SHOWN if not _NO_COLOR_WARNING_SHOWN: print_warning("NO_COLOR is deprecated and should be replaced with RESET.") _NO_COLOR_WARNING_SHOWN = True