#!/usr/bin/env python3 # Regenerate the [icon] block of theme.toml against a newer Yazi preset. # git clone --depth 1 https://github.com/sxyazi/yazi # awk "/^\[icon\]/{f=1} f" yazi/yazi-config/preset/theme-dark.toml > icons-src.toml # ./regen-icons.py icons-src.toml # writes icon-gruvbox.toml, paste over the [icon] block import re, sys, math SRC = sys.argv[1] if len(sys.argv) > 1 else "theme-dark.toml" # Gruvbox dark palette, split so chromatic sources never collapse into grey # and desaturated sources never turn into a vivid accent. CHROMATIC = { "#fb4934": "red", "#b8bb26": "green", "#98971a": "green dim", "#fabd2f": "yellow", "#d79921": "yellow dim", "#83a598": "blue", "#458588": "blue dim", "#d3869b": "purple", "#b16286": "purple dim", "#8ec07c": "aqua", "#689d6a": "aqua dim", "#fe8019": "orange", "#d65d0e": "orange dim", } NEUTRAL = { "#fbf1c7": "fg0", "#ebdbb2": "fg1", "#d5c4a1": "fg2", "#bdae93": "fg3", "#a89984": "fg4", "#928374": "gray", } def hex2rgb(h): h = h.lstrip("#") return tuple(int(h[i:i+2], 16) / 255 for i in (0, 2, 4)) def srgb_to_linear(c): return c / 12.92 if c <= 0.04045 else ((c + 0.055) / 1.055) ** 2.4 def oklab(h): r, g, b = (srgb_to_linear(c) for c in hex2rgb(h)) l = 0.4122214708*r + 0.5363325363*g + 0.0514459929*b m = 0.2119034982*r + 0.6806995451*g + 0.1073969566*b s = 0.0883024619*r + 0.2817188376*g + 0.6299787005*b l, m, s = (math.copysign(abs(v) ** (1/3), v) for v in (l, m, s)) return ( 0.2104542553*l + 0.7936177850*m - 0.0040720468*s, 1.9779984951*l - 2.4285922050*m + 0.4505937099*s, 0.0259040371*l + 0.7827717662*m - 0.8086757660*s, ) def chroma(h): _, a, b = oklab(h) return math.hypot(a, b) CHROMA_FLOOR = 0.04 # below this a source colour reads as grey/white/black def nearest(src): pool = NEUTRAL if chroma(src) < CHROMA_FLOOR else CHROMATIC sl, sa, sb = oklab(src) best, bestd = None, None for cand in pool: cl, ca, cb = oklab(cand) # lightness weighted down so hue drives the match d = (0.7*(sl-cl))**2 + (sa-ca)**2 + (sb-cb)**2 if bestd is None or d < bestd: best, bestd = cand, d return best text = open(SRC).read() seen = {} def sub(m): src = m.group(1).lower() dst = seen.setdefault(src, nearest(src)) return 'fg = "%s"' % dst out = re.sub(r'fg = "(#[0-9a-fA-F]{6})"', sub, text) open("icon-gruvbox.toml", "w").write(out) # report from collections import Counter c = Counter(seen.values()) names = {**CHROMATIC, **NEUTRAL} print("distinct source colours:", len(seen)) print("mapped onto %d gruvbox tones:" % len(c)) for col, n in c.most_common(): print(" %-9s %-11s %3d sources" % (col, names[col], n))