use crate::{Color, ParseColorError, ParseColorErrorKind};
#[inline]
fn parse_number(s: &str) -> Option<u8> {
if let Some(stripped) = s.strip_prefix("0x") {
u8::from_str_radix(stripped, 16).ok()
} else {
s.parse::<u8>().ok()
}
}
#[inline]
fn parse_percent_or_255(s: &str) -> Option<(u8, bool)> {
s.strip_suffix('%')
.and_then(|s| {
s.parse::<f32>()
.ok()
.map(|t| ((t * 255.0 / 100.0).round() as u8, true))
})
.or_else(|| parse_number(s).map(|t| (t, false)))
}
pub fn parse_rgb(s: &str) -> Result<Color, ParseColorError> {
let trimmed = if s.starts_with("rgb(") && s.ends_with(")") {
s.strip_prefix("rgb(").and_then(|s| s.strip_suffix(")")).ok_or_else(
|| ParseColorError {
kind: ParseColorErrorKind::InvalidRgb,
given: s.to_string(),
},
)?
} else {
s
};
let normalized = trimmed.replace([',', '/'], " ");
let components: Vec<&str> = normalized.split_whitespace().collect();
if components.len() != 3 {
return Err(ParseColorError {
kind: ParseColorErrorKind::InvalidRgb,
given: s.to_string(),
});
}
let colors: Result<Vec<u8>, ParseColorError> = components
.iter()
.map(|&component| {
parse_percent_or_255(component).map(|(value, _)| value).ok_or_else(
|| ParseColorError {
kind: ParseColorErrorKind::InvalidRgb,
given: s.to_string(),
},
)
})
.collect();
let colors = colors?;
if colors.iter().all(|&x| (0..=255).contains(&x)) {
Ok(Color::Rgb(colors[0], colors[1], colors[2]))
} else {
Err(ParseColorError {
kind: ParseColorErrorKind::InvalidRgb,
given: s.to_string(),
})
}
}
pub fn parse_hex(s: &str) -> Result<Color, ParseColorError> {
if !s.starts_with('#') || !s[1..].chars().all(|c| c.is_ascii_hexdigit()) {
return Err(ParseColorError {
kind: ParseColorErrorKind::InvalidHex,
given: s.to_string(),
});
}
if s.len() != 4 && s.len() != 7 {
return Err(ParseColorError {
kind: ParseColorErrorKind::InvalidHex,
given: s.to_string(),
});
}
let upper = s.to_ascii_uppercase();
Ok(Color::Hex(Box::leak(upper.into_boxed_str())))
}
pub fn parse_other(s: &str) -> Result<Color, ParseColorError> {
let s = s.replace([',', '/'], " ");
let codes = s.split_whitespace().collect::<Vec<&str>>();
if codes.len() == 1 {
if let Some(n) = parse_number(codes[0]) {
Ok(Color::Ansi256(n))
} else if s.chars().all(|c| c.is_ascii_hexdigit()) {
Err(ParseColorError {
kind: ParseColorErrorKind::InvalidAnsi256,
given: s.to_string(),
})
} else {
Err(ParseColorError {
kind: ParseColorErrorKind::InvalidName,
given: s.to_string(),
})
}
} else if codes.len() == 3 {
parse_rgb(s.as_str())
} else {
Err(if s.contains(",") {
ParseColorError {
kind: ParseColorErrorKind::InvalidRgb,
given: s.to_string(),
}
} else {
ParseColorError {
kind: ParseColorErrorKind::InvalidName,
given: s.to_string(),
}
})
}
}