1use console::Color;
28
29#[derive(Debug, Clone, PartialEq, Eq)]
31pub enum ColorDef {
32 Named(Color),
34 Color256(u8),
36 Rgb(u8, u8, u8),
38}
39
40impl ColorDef {
41 pub fn parse_value(value: &serde_yaml::Value) -> Result<Self, String> {
48 match value {
49 serde_yaml::Value::String(s) => Self::parse_string(s),
50 serde_yaml::Value::Number(n) => {
51 let index = n
52 .as_u64()
53 .ok_or_else(|| format!("Invalid color palette index: {}", n))?;
54 if index > 255 {
55 return Err(format!(
56 "Color palette index {} out of range (0-255)",
57 index
58 ));
59 }
60 Ok(ColorDef::Color256(index as u8))
61 }
62 serde_yaml::Value::Sequence(seq) => Self::parse_rgb_tuple(seq),
63 _ => Err(format!("Invalid color value: {:?}", value)),
64 }
65 }
66
67 pub fn parse_string(s: &str) -> Result<Self, String> {
74 let s = s.trim();
75
76 if let Some(hex) = s.strip_prefix('#') {
78 return Self::parse_hex(hex);
79 }
80
81 Self::parse_named(s)
83 }
84
85 fn parse_hex(hex: &str) -> Result<Self, String> {
87 match hex.len() {
88 3 => {
90 let r = u8::from_str_radix(&hex[0..1], 16)
91 .map_err(|_| format!("Invalid hex: {}", hex))?
92 * 17;
93 let g = u8::from_str_radix(&hex[1..2], 16)
94 .map_err(|_| format!("Invalid hex: {}", hex))?
95 * 17;
96 let b = u8::from_str_radix(&hex[2..3], 16)
97 .map_err(|_| format!("Invalid hex: {}", hex))?
98 * 17;
99 Ok(ColorDef::Rgb(r, g, b))
100 }
101 6 => {
103 let r = u8::from_str_radix(&hex[0..2], 16)
104 .map_err(|_| format!("Invalid hex: {}", hex))?;
105 let g = u8::from_str_radix(&hex[2..4], 16)
106 .map_err(|_| format!("Invalid hex: {}", hex))?;
107 let b = u8::from_str_radix(&hex[4..6], 16)
108 .map_err(|_| format!("Invalid hex: {}", hex))?;
109 Ok(ColorDef::Rgb(r, g, b))
110 }
111 _ => Err(format!(
112 "Invalid hex color: #{} (must be 3 or 6 digits)",
113 hex
114 )),
115 }
116 }
117
118 fn parse_named(name: &str) -> Result<Self, String> {
120 let name_lower = name.to_lowercase();
121
122 if let Some(base) = name_lower.strip_prefix("bright_") {
124 return Self::parse_bright_color(base);
125 }
126
127 let color = match name_lower.as_str() {
129 "black" => Color::Black,
130 "red" => Color::Red,
131 "green" => Color::Green,
132 "yellow" => Color::Yellow,
133 "blue" => Color::Blue,
134 "magenta" => Color::Magenta,
135 "cyan" => Color::Cyan,
136 "white" => Color::White,
137 "gray" | "grey" => Color::White,
139 _ => return Err(format!("Unknown color name: {}", name)),
140 };
141
142 Ok(ColorDef::Named(color))
143 }
144
145 fn parse_bright_color(base: &str) -> Result<Self, String> {
147 let index = match base {
149 "black" => 8,
150 "red" => 9,
151 "green" => 10,
152 "yellow" => 11,
153 "blue" => 12,
154 "magenta" => 13,
155 "cyan" => 14,
156 "white" => 15,
157 _ => return Err(format!("Unknown bright color: bright_{}", base)),
158 };
159
160 Ok(ColorDef::Color256(index))
161 }
162
163 fn parse_rgb_tuple(seq: &[serde_yaml::Value]) -> Result<Self, String> {
165 if seq.len() != 3 {
166 return Err(format!(
167 "RGB tuple must have exactly 3 values, got {}",
168 seq.len()
169 ));
170 }
171
172 let mut components = [0u8; 3];
173 for (i, val) in seq.iter().enumerate() {
174 let n = val
175 .as_u64()
176 .ok_or_else(|| format!("RGB component {} is not a number", i))?;
177 if n > 255 {
178 return Err(format!("RGB component {} out of range (0-255): {}", i, n));
179 }
180 components[i] = n as u8;
181 }
182
183 Ok(ColorDef::Rgb(components[0], components[1], components[2]))
184 }
185
186 pub fn to_console_color(&self) -> Color {
188 match self {
189 ColorDef::Named(c) => *c,
190 ColorDef::Color256(n) => Color::Color256(*n),
191 ColorDef::Rgb(r, g, b) => Color::Color256(crate::rgb_to_ansi256((*r, *g, *b))),
192 }
193 }
194}
195
196#[cfg(test)]
197mod tests {
198 use super::*;
199 use serde_yaml::Value;
200
201 #[test]
206 fn test_parse_named_colors() {
207 assert_eq!(
208 ColorDef::parse_string("red").unwrap(),
209 ColorDef::Named(Color::Red)
210 );
211 assert_eq!(
212 ColorDef::parse_string("green").unwrap(),
213 ColorDef::Named(Color::Green)
214 );
215 assert_eq!(
216 ColorDef::parse_string("blue").unwrap(),
217 ColorDef::Named(Color::Blue)
218 );
219 assert_eq!(
220 ColorDef::parse_string("yellow").unwrap(),
221 ColorDef::Named(Color::Yellow)
222 );
223 assert_eq!(
224 ColorDef::parse_string("magenta").unwrap(),
225 ColorDef::Named(Color::Magenta)
226 );
227 assert_eq!(
228 ColorDef::parse_string("cyan").unwrap(),
229 ColorDef::Named(Color::Cyan)
230 );
231 assert_eq!(
232 ColorDef::parse_string("white").unwrap(),
233 ColorDef::Named(Color::White)
234 );
235 assert_eq!(
236 ColorDef::parse_string("black").unwrap(),
237 ColorDef::Named(Color::Black)
238 );
239 }
240
241 #[test]
242 fn test_parse_named_colors_case_insensitive() {
243 assert_eq!(
244 ColorDef::parse_string("RED").unwrap(),
245 ColorDef::Named(Color::Red)
246 );
247 assert_eq!(
248 ColorDef::parse_string("Red").unwrap(),
249 ColorDef::Named(Color::Red)
250 );
251 }
252
253 #[test]
254 fn test_parse_gray_aliases() {
255 assert_eq!(
256 ColorDef::parse_string("gray").unwrap(),
257 ColorDef::Named(Color::White)
258 );
259 assert_eq!(
260 ColorDef::parse_string("grey").unwrap(),
261 ColorDef::Named(Color::White)
262 );
263 }
264
265 #[test]
266 fn test_parse_unknown_color() {
267 assert!(ColorDef::parse_string("purple").is_err());
268 assert!(ColorDef::parse_string("orange").is_err());
269 }
270
271 #[test]
276 fn test_parse_bright_colors() {
277 assert_eq!(
278 ColorDef::parse_string("bright_red").unwrap(),
279 ColorDef::Color256(9)
280 );
281 assert_eq!(
282 ColorDef::parse_string("bright_green").unwrap(),
283 ColorDef::Color256(10)
284 );
285 assert_eq!(
286 ColorDef::parse_string("bright_blue").unwrap(),
287 ColorDef::Color256(12)
288 );
289 assert_eq!(
290 ColorDef::parse_string("bright_black").unwrap(),
291 ColorDef::Color256(8)
292 );
293 assert_eq!(
294 ColorDef::parse_string("bright_white").unwrap(),
295 ColorDef::Color256(15)
296 );
297 }
298
299 #[test]
300 fn test_parse_unknown_bright_color() {
301 assert!(ColorDef::parse_string("bright_purple").is_err());
302 }
303
304 #[test]
309 fn test_parse_hex_6_digit() {
310 assert_eq!(
311 ColorDef::parse_string("#ff6b35").unwrap(),
312 ColorDef::Rgb(255, 107, 53)
313 );
314 assert_eq!(
315 ColorDef::parse_string("#000000").unwrap(),
316 ColorDef::Rgb(0, 0, 0)
317 );
318 assert_eq!(
319 ColorDef::parse_string("#ffffff").unwrap(),
320 ColorDef::Rgb(255, 255, 255)
321 );
322 }
323
324 #[test]
325 fn test_parse_hex_3_digit() {
326 assert_eq!(
327 ColorDef::parse_string("#fff").unwrap(),
328 ColorDef::Rgb(255, 255, 255)
329 );
330 assert_eq!(
331 ColorDef::parse_string("#000").unwrap(),
332 ColorDef::Rgb(0, 0, 0)
333 );
334 assert_eq!(
335 ColorDef::parse_string("#f80").unwrap(),
336 ColorDef::Rgb(255, 136, 0)
337 );
338 }
339
340 #[test]
341 fn test_parse_hex_case_insensitive() {
342 assert_eq!(
343 ColorDef::parse_string("#FF6B35").unwrap(),
344 ColorDef::Rgb(255, 107, 53)
345 );
346 assert_eq!(
347 ColorDef::parse_string("#FFF").unwrap(),
348 ColorDef::Rgb(255, 255, 255)
349 );
350 }
351
352 #[test]
353 fn test_parse_hex_invalid() {
354 assert!(ColorDef::parse_string("#ff").is_err());
355 assert!(ColorDef::parse_string("#ffff").is_err());
356 assert!(ColorDef::parse_string("#gggggg").is_err());
357 }
358
359 #[test]
364 fn test_parse_value_string() {
365 let val = Value::String("red".into());
366 assert_eq!(
367 ColorDef::parse_value(&val).unwrap(),
368 ColorDef::Named(Color::Red)
369 );
370 }
371
372 #[test]
373 fn test_parse_value_number() {
374 let val = Value::Number(208.into());
375 assert_eq!(
376 ColorDef::parse_value(&val).unwrap(),
377 ColorDef::Color256(208)
378 );
379 }
380
381 #[test]
382 fn test_parse_value_number_out_of_range() {
383 let val = Value::Number(256.into());
384 assert!(ColorDef::parse_value(&val).is_err());
385 }
386
387 #[test]
388 fn test_parse_value_sequence() {
389 let val = Value::Sequence(vec![
390 Value::Number(255.into()),
391 Value::Number(107.into()),
392 Value::Number(53.into()),
393 ]);
394 assert_eq!(
395 ColorDef::parse_value(&val).unwrap(),
396 ColorDef::Rgb(255, 107, 53)
397 );
398 }
399
400 #[test]
401 fn test_parse_value_sequence_wrong_length() {
402 let val = Value::Sequence(vec![Value::Number(255.into()), Value::Number(107.into())]);
403 assert!(ColorDef::parse_value(&val).is_err());
404 }
405
406 #[test]
407 fn test_parse_value_sequence_out_of_range() {
408 let val = Value::Sequence(vec![
409 Value::Number(256.into()),
410 Value::Number(107.into()),
411 Value::Number(53.into()),
412 ]);
413 assert!(ColorDef::parse_value(&val).is_err());
414 }
415
416 #[test]
421 fn test_to_console_color_named() {
422 let c = ColorDef::Named(Color::Red);
423 assert_eq!(c.to_console_color(), Color::Red);
424 }
425
426 #[test]
427 fn test_to_console_color_256() {
428 let c = ColorDef::Color256(208);
429 assert_eq!(c.to_console_color(), Color::Color256(208));
430 }
431
432 #[test]
433 fn test_to_console_color_rgb() {
434 let c = ColorDef::Rgb(255, 107, 53);
435 if let Color::Color256(_) = c.to_console_color() {
437 } else {
439 panic!("Expected Color256");
440 }
441 }
442}