Files
logseq/scripts/build_color_dicts.bb
scheinriese 4669d24821 feat: custom color picker with recents, contrast, and named-color autocomplete
Adds a hex/HSV picker pane to the icon-picker color popover, plus a
per-graph recents lane and cross-theme contrast adjustment. Hex input
supports named-color autocomplete (148 CSS + 949 XKCD) with a ghost
suffix that Tab/Right-arrow accepts. Picked colors that fail WCAG 3:1
against either light or dark background are auto-shifted in OKLCH per
mode; recents and the trigger swatch render as half-pie splits when the
two modes diverge.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-26 19:39:38 +02:00

130 lines
4.2 KiB
Clojure
Executable File

#!/usr/bin/env bb
;; build_color_dicts.bb
;;
;; Downloads the XKCD color survey (`https://xkcd.com/color/rgb.txt`) and
;; emits `src/main/frontend/colors/named.cljs` as a deterministic, hermetic
;; CLJS source file containing the dataset as a `def` map literal.
;;
;; The XKCD dataset is licensed CC0 (public domain). It hasn't changed
;; since 2010. We embed it at build time so the bundle works fully offline.
;;
;; Usage: bb scripts/build_color_dicts.bb
;;
;; Idempotent: rerunning produces byte-identical output.
(ns build-color-dicts
(:require [babashka.curl :as curl]
[babashka.fs :as fs]
[clojure.java.io :as io]
[clojure.string :as str]))
(def ^:private xkcd-url "https://xkcd.com/color/rgb.txt")
(def ^:private cache-path "/tmp/xkcd_rgb.txt")
(def ^:private repo-root
(-> *file* fs/parent fs/parent str))
(def ^:private out-path
(str repo-root "/src/main/frontend/colors/named.cljs"))
(defn- fetch!
"Return the raw rgb.txt body. Caches at /tmp/xkcd_rgb.txt to avoid hammering xkcd.com."
[]
(if (fs/exists? cache-path)
(slurp cache-path)
(let [body (:body (curl/get xkcd-url))]
(spit cache-path body)
body)))
(defn- parse-line
"Parse one entry line. Returns [name hex] or nil for the license comment."
[line]
(when-not (or (str/blank? line)
(str/starts-with? line "#"))
(let [[raw-name raw-hex] (str/split line #"\t")]
(when (and raw-name raw-hex)
(let [nm (str/lower-case (str/trim raw-name))
hx (-> raw-hex str/trim (str/replace #"^#" "") str/lower-case)]
(when (and (re-matches #"[0-9a-f]{6}" hx)
(seq nm))
[nm hx]))))))
(defn- parse-all [body]
(->> (str/split-lines body)
(keep parse-line)
(into (sorted-map))))
(defn- format-pairs
"Format the sorted map into one `key value` pair per line, indented to
align with the open brace at column 3 (`(def^:export named-colors{`)."
[pairs]
(->> pairs
(map (fn [[nm hx]]
(str " " (pr-str nm) " " (pr-str hx))))
(str/join "\n")))
(def ^:private file-template
"(ns frontend.colors.named
\"XKCD color survey dataset (~949 colors).
Public domain (CC0): https://xkcd.com/color/rgb.txt
Generated by scripts/build_color_dicts.bb -- do not edit by hand.\")
(def ^:export named-colors
\"Map of normalized lowercase color name -> 6-char hex (no leading '#').
Names with spaces are stored as-is; the resolver normalizes input the
same way before lookup.\"
{%PAIRS%})
(def ^:private by-hex
(into {} (map (fn [[n h]] [h n])) named-colors))
(defn name->hex
\"Look up a normalized color name (lowercase, single-spaced). Returns
6-char hex without '#' or nil.\"
[s]
(get named-colors s))
(defn hex->name
\"Reverse lookup: 6-char lowercase hex (no '#') -> canonical name or nil.\"
[h]
(get by-hex h))
")
(defn- render [pairs]
;; The opening `{` sits flush at column 3 (after ` `). We render the
;; first pair on the same line as the brace and subsequent pairs at the
;; same column, matching the spec example formatting.
(let [lines (->> pairs
(map (fn [[nm hx]]
(str (pr-str nm) " " (pr-str hx)))))
indent " "
joined (->> (rest lines)
(map #(str indent %))
(cons (first lines))
(str/join "\n"))]
(str/replace file-template "%PAIRS%" joined)))
(defn- gzip-size
"Estimate gzipped size of a string in bytes."
[s]
(let [bs (.getBytes ^String s "UTF-8")
baos (java.io.ByteArrayOutputStream.)]
(with-open [gz (java.util.zip.GZIPOutputStream. baos)]
(.write gz bs))
(count (.toByteArray baos))))
(defn -main [& _]
(let [body (fetch!)
pairs (parse-all body)
out (render pairs)]
(fs/create-dirs (fs/parent out-path))
(spit out-path out)
(let [raw-bytes (count (.getBytes ^String out "UTF-8"))]
(println "wrote" out-path)
(println " entries: " (count pairs))
(println " raw size: " raw-bytes "bytes")
(println " gzipped est:" (gzip-size out) "bytes"))))
(when (= *file* (System/getProperty "babashka.file"))
(apply -main *command-line-args*))