use std::collections::HashMap;
use std::sync::{Mutex, OnceLock};
static CATALOG_COLORS: OnceLock<Mutex<HashMap<String, String>>> = OnceLock::new();
pub fn get_catalog_color(name: &str) -> String {
let colors = CATALOG_COLORS.get_or_init(|| {
let mut map = HashMap::new();
map.insert("default".to_string(), "#f69220".to_string());
Mutex::new(map)
});
if let Some(color) = colors
.lock()
.expect("catalog colors lock")
.get(name)
.cloned()
{
return color;
}
let mut hash = 0_i32;
for ch in name.chars() {
hash = ch as i32 + ((hash << 5) - hash);
}
let color = hsl_to_hex(hash % 360, 35, 65);
colors
.lock()
.expect("catalog colors lock")
.insert(name.to_string(), color.clone());
color
}
fn hsl_to_hex(h: i32, s: i32, l: i32) -> String {
let [r, g, b] = hsl_to_rgb(h, s, l);
format!("#{r:02x}{g:02x}{b:02x}")
}
fn hsl_to_rgb(mut h: i32, s: i32, l: i32) -> [u8; 3] {
h %= 360;
let h = h as f64 / 360.0;
let s = s as f64 / 100.0;
let l = l as f64 / 100.0;
if s == 0.0 {
let v = (l * 255.0).round().clamp(0.0, 255.0) as u8;
return [v, v, v];
}
let q = if l < 0.5 {
l * (1.0 + s)
} else {
l + s - l * s
};
let p = 2.0 * l - q;
[
(hue_to_rgb(p, q, h + 1.0 / 3.0) * 255.0)
.round()
.clamp(0.0, 255.0) as u8,
(hue_to_rgb(p, q, h) * 255.0).round().clamp(0.0, 255.0) as u8,
(hue_to_rgb(p, q, h - 1.0 / 3.0) * 255.0)
.round()
.clamp(0.0, 255.0) as u8,
]
}
fn hue_to_rgb(p: f64, q: f64, mut t: f64) -> f64 {
if t < 0.0 {
t += 1.0;
}
if t > 1.0 {
t -= 1.0;
}
if t < 1.0 / 6.0 {
p + (q - p) * 6.0 * t
} else if t < 1.0 / 2.0 {
q
} else if t < 2.0 / 3.0 {
p + (q - p) * (2.0 / 3.0 - t) * 6.0
} else {
p
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn returns_default_catalog_color() {
assert_eq!(get_catalog_color("default"), "#f69220");
}
#[test]
fn hashes_named_catalogs_compatibly() {
assert_eq!(get_catalog_color("react-lens"), "#87c5a4");
}
}