use tastty::Color;
#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
#[error("invalid color spec '{input}'")]
pub struct ParseColorError {
input: String,
}
impl ParseColorError {
fn new(input: &str) -> Self {
Self {
input: input.to_string(),
}
}
}
pub fn parse_color(input: &str) -> Result<Color, ParseColorError> {
let lower = input.to_ascii_lowercase();
match lower.as_str() {
"default" => return Ok(Color::Default),
"black" => return Ok(Color::Index(0)),
"red" => return Ok(Color::Index(1)),
"green" => return Ok(Color::Index(2)),
"yellow" => return Ok(Color::Index(3)),
"blue" => return Ok(Color::Index(4)),
"magenta" => return Ok(Color::Index(5)),
"cyan" => return Ok(Color::Index(6)),
"white" => return Ok(Color::Index(7)),
"bright_black" | "brightblack" => return Ok(Color::Index(8)),
"bright_red" | "brightred" => return Ok(Color::Index(9)),
"bright_green" | "brightgreen" => return Ok(Color::Index(10)),
"bright_yellow" | "brightyellow" => return Ok(Color::Index(11)),
"bright_blue" | "brightblue" => return Ok(Color::Index(12)),
"bright_magenta" | "brightmagenta" => return Ok(Color::Index(13)),
"bright_cyan" | "brightcyan" => return Ok(Color::Index(14)),
"bright_white" | "brightwhite" => return Ok(Color::Index(15)),
_ => {}
}
if let Some(inner) = lower
.strip_prefix("color(")
.and_then(|s| s.strip_suffix(')'))
{
return inner
.parse::<u8>()
.map(Color::Index)
.map_err(|_err| ParseColorError::new(input));
}
if let Some(inner) = lower.strip_prefix("rgb(").and_then(|s| s.strip_suffix(')')) {
let mut parts = inner.split(',').map(str::trim);
let r = parse_rgb_part(parts.next(), input)?;
let g = parse_rgb_part(parts.next(), input)?;
let b = parse_rgb_part(parts.next(), input)?;
if parts.next().is_some() {
return Err(ParseColorError::new(input));
}
return Ok(Color::Rgb(r, g, b));
}
if let Some(hex) = lower.strip_prefix('#')
&& hex.len() == 6
{
let r = u8::from_str_radix(&hex[0..2], 16).map_err(|_err| ParseColorError::new(input))?;
let g = u8::from_str_radix(&hex[2..4], 16).map_err(|_err| ParseColorError::new(input))?;
let b = u8::from_str_radix(&hex[4..6], 16).map_err(|_err| ParseColorError::new(input))?;
return Ok(Color::Rgb(r, g, b));
}
Err(ParseColorError::new(input))
}
fn parse_rgb_part(part: Option<&str>, input: &str) -> Result<u8, ParseColorError> {
part.ok_or_else(|| ParseColorError::new(input))?
.parse()
.map_err(|_err| ParseColorError::new(input))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_named_index_and_rgb_colors() {
assert_eq!(parse_color("red").unwrap(), Color::Index(1));
assert_eq!(parse_color("bright_blue").unwrap(), Color::Index(12));
assert_eq!(parse_color("color(196)").unwrap(), Color::Index(196));
assert_eq!(
parse_color("rgb(255,0,16)").unwrap(),
Color::Rgb(255, 0, 16)
);
assert_eq!(parse_color("#ff0010").unwrap(), Color::Rgb(255, 0, 16));
}
#[test]
fn rejects_invalid_colors() {
parse_color("rgb(1,2)").unwrap_err();
parse_color("color(999)").unwrap_err();
parse_color("#xyzxyz").unwrap_err();
}
}