use crate::cla::Style;
#[derive(Debug, Clone, Copy)]
pub struct Rgb(pub u8, pub u8, pub u8);
impl Rgb {
pub fn fg(self) -> String {
format!("\x1b[38;2;{};{};{}m", self.0, self.1, self.2)
}
}
#[derive(Debug, Clone, Copy)]
pub struct Colors {
pub logo: [Rgb; 3],
pub label: Rgb,
pub value: Rgb,
}
pub const RESET: &str = "\x1b[0m";
const fn c(r: u8, g: u8, b: u8) -> Rgb {
Rgb(r, g, b)
}
impl Colors {
fn from_five(v: [Rgb; 5]) -> Colors {
Colors {
logo: [v[0], v[1], v[2]],
label: v[3],
value: v[4],
}
}
pub fn named(scheme: &str) -> Option<Colors> {
Some(Self::from_five(match scheme.to_lowercase().as_str() {
"intel" => [
c(0, 113, 197),
c(0, 113, 197),
c(0, 113, 197),
c(0, 113, 197),
c(255, 255, 255),
],
"intel-new" => [
c(0, 133, 199),
c(0, 133, 199),
c(0, 133, 199),
c(0, 133, 199),
c(255, 255, 255),
],
"amd" => [
c(0, 158, 82),
c(0, 158, 82),
c(0, 158, 82),
c(0, 158, 82),
c(255, 255, 255),
],
"ibm" => [
c(15, 98, 254),
c(15, 98, 254),
c(15, 98, 254),
c(15, 98, 254),
c(255, 255, 255),
],
"arm" => [
c(0, 145, 189),
c(0, 145, 189),
c(0, 145, 189),
c(0, 145, 189),
c(255, 255, 255),
],
"rockchip" => [
c(0, 174, 239),
c(0, 174, 239),
c(0, 174, 239),
c(0, 174, 239),
c(255, 255, 255),
],
"sifive" => [
c(83, 61, 209),
c(83, 61, 209),
c(83, 61, 209),
c(83, 61, 209),
c(255, 255, 255),
],
_ => return None,
}))
}
pub fn custom(spec: &str) -> Result<Colors, String> {
let groups: Vec<&str> = spec.split(':').collect();
if groups.len() != 5 {
return Err(format!(
"Error: invalid --color value '{}': expected 5 colon-separated R,G,B triplets",
spec
));
}
let mut out = [Rgb(0, 0, 0); 5];
for (i, g) in groups.iter().enumerate() {
let parts: Vec<&str> = g.split(',').collect();
if parts.len() != 3 {
return Err(format!(
"Error: invalid --color triplet '{}': expected R,G,B",
g
));
}
let mut rgb = [0u8; 3];
for (j, p) in parts.iter().enumerate() {
rgb[j] = p
.trim()
.parse::<u16>()
.ok()
.filter(|v| *v <= 255)
.ok_or_else(|| {
format!("Error: invalid color component '{}' (must be 0-255)", p)
})? as u8;
}
out[i] = Rgb(rgb[0], rgb[1], rgb[2]);
}
Ok(Self::from_five(out))
}
pub fn for_vendor(vendor: &str) -> Colors {
let key = match vendor {
v if v.eq_ignore_ascii_case("Intel") => "intel-new",
v if v.eq_ignore_ascii_case("AMD") => "amd",
v if v.eq_ignore_ascii_case("IBM") => "ibm",
v if v.eq_ignore_ascii_case("ARM") => "arm",
v if v.eq_ignore_ascii_case("Rockchip") => "rockchip",
v if v.eq_ignore_ascii_case("SiFive") => "sifive",
_ => "arm",
};
Self::named(key).unwrap()
}
pub fn legacy() -> Colors {
let none = Rgb(255, 255, 255);
Colors {
logo: [none; 3],
label: none,
value: none,
}
}
}
pub fn resolve(
style: Style,
color_arg: Option<&str>,
vendor: &str,
) -> Result<(Style, Colors, bool), String> {
let no_color_env = std::env::var_os("NO_COLOR").is_some_and(|v| !v.is_empty());
let effective_style = if no_color_env { Style::Legacy } else { style };
if effective_style == Style::Legacy {
return Ok((effective_style, Colors::legacy(), false));
}
let colors = match color_arg {
Some(spec) => Colors::named(spec)
.map(Ok)
.unwrap_or_else(|| Colors::custom(spec))?,
None => Colors::for_vendor(vendor),
};
Ok((effective_style, colors, true))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn custom_color_parses_five_triplets() {
let c = Colors::custom("1,2,3:4,5,6:7,8,9:10,11,12:13,14,15").unwrap();
assert_eq!((c.logo[0].0, c.logo[0].1, c.logo[0].2), (1, 2, 3));
assert_eq!((c.logo[2].0, c.logo[2].1, c.logo[2].2), (7, 8, 9));
assert_eq!((c.label.0, c.label.1, c.label.2), (10, 11, 12));
assert_eq!((c.value.0, c.value.1, c.value.2), (13, 14, 15));
}
#[test]
fn custom_color_rejects_wrong_group_count() {
assert!(Colors::custom("1,2,3:4,5,6").is_err());
}
#[test]
fn custom_color_rejects_out_of_range_component() {
assert!(Colors::custom("1,2,300:4,5,6:7,8,9:10,11,12:13,14,15").is_err());
}
#[test]
fn named_scheme_is_case_insensitive() {
assert!(Colors::named("INTEL").is_some());
assert!(Colors::named("not-a-scheme").is_none());
}
#[test]
fn legacy_style_disables_color_regardless_of_arg() {
let (style, _, colored) = resolve(Style::Legacy, Some("intel"), "Intel").unwrap();
assert_eq!(style, Style::Legacy);
assert!(!colored);
}
}