#!/bin/bash # Central theme drift guard (basic/.config/themes/scripts/sync-theme.sh). # # Fails if hardcoded theme hex values appear anywhere in the repo outside: # - basic/.config/themes (the single source of truth) # - term/.config/tmux/plugins/ (vendored upstream, stow-ignored) # - term/.config/yazi/theme.toml (declared mirror: yazi has no include) # - term/.config/starship.toml (declared mirror: starship has no include) # - hypr/.config/rofi/themes/ltmnight.rasi (declared mirror: rasi has no # alpha() function, so the two translucency values are #RRGGBBAA hex; # everything else maps onto the central theme via @import) # - test/ + docs (may quote values in prose) # # Run with: bash basic/.config/themes/scripts/sync-theme.sh # test/lint.sh invokes it as part of `make test-lint`. set -uo pipefail REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../../.." && pwd)" cd "$REPO_ROOT" || exit 1 # Sentinel hex values of the active palette (case-insensitive). SENTINELS='74c7ec|1e1e2e|cdd6f4|45475a|b4befe|f38ba8|a6e3a1|f9e2af|181825|11111b|89b4fa|f5e0dc' matches="$(grep -rniE --include='*.conf' --include='*.css' --include='*.rasi' \ --include='*.lua' --include='*.toml' --include='*.fish' --include='*.sh' \ --exclude-dir='.git' --exclude-dir='plugins' --exclude-dir='test' \ -E "$SENTINELS" . 2>/dev/null | grep -vE '^./basic/.config/themes' \ | grep -vE 'term/.config/(yazi/theme.toml|starship.toml)' \ | grep -vE 'hypr/.config/rofi/themes/ltmnight.rasi' || true)" if [[ -n "$matches" ]]; then echo "FAIL: hardcoded theme colors outside basic/.config/themes (and declared mirrors):" printf '%s\n' "$matches" | sed 's/^/ /' echo "Move the values into basic/.config/themes and reference them instead." exit 1 fi echo "ok: no hardcoded theme colors outside the central theme" # --------------------------------------------------------------------------- # Role/palette consistency: every role in theme.css must equal its palette # twin (roles are literal hex because some parsers drop @-references). # --------------------------------------------------------------------------- python3 - <<'EOF' import re, sys css_path = "basic/.config/themes/themes/catppuccin-mocha/theme.css" defines = dict(re.findall(r"@define-color\s+([a-z0-9_]+)\s+(#[0-9a-fA-F]+)", open(css_path).read())) # role -> palette twin (bg_translucent intentionally excluded: crust + alpha). pairs = { "bg": "base", "bg_dim": "mantle", "bg_deep": "crust", "fg": "text", "fg_dim": "subtext0", "fg_muted": "overlay1", "surface": "surface0", "border": "surface1", "accent": "sapphire", "accent2": "lavender", "ok": "green", "warn": "yellow", "urgent": "red", } missing = [k for k in list(pairs) + list(pairs.values()) if k not in defines] if missing: print(f"FAIL: theme.css missing defines: {sorted(missing)}") sys.exit(1) bad = [r for r, p in pairs.items() if defines[r].lower() != defines[p].lower()] if bad: print(f"FAIL: roles out of sync with palette in {css_path}: {bad}") sys.exit(1) trans = defines.get("bg_translucent_raw", "") m = re.fullmatch(r"rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*([0-9.]+)\s*\)", trans) if not m: # defines[] regex only captures #hex; re-read raw value (may be rgba()). raw = re.search(r"@define-color\s+bg_translucent\s+([^;]+);", open(css_path).read()) trans = raw.group(1).strip() if raw else "" m = re.fullmatch(r"rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*([0-9.]+)\s*\)", trans) crust_rgb = tuple(int(defines["crust"][i:i+2], 16) for i in (1, 3, 5)) if not m or tuple(map(int, m.groups()[:3])) != crust_rgb or not (0 < float(m.group(4)) < 1): print(f"FAIL: bg_translucent must be rgba(crust, alpha<1) in {css_path}") sys.exit(1) print("ok: theme.css roles match palette") EOF