use std::{borrow::Cow, fmt};
use ansi_term::{Color, Style};
use serde::{
de::{Error, Unexpected, Visitor},
Deserializer, Serializer,
};
struct StringVisitor;
impl<'de> Visitor<'de> for StringVisitor {
type Value = String;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
write!(formatter, "a string")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: Error,
{
Ok(v.to_owned())
}
fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
where
E: Error,
{
Ok(v)
}
}
fn parse_color(input: &str) -> Option<Color> {
match input {
"black" => Some(Color::Black),
"red" => Some(Color::Red),
"green" => Some(Color::Green),
"yellow" => Some(Color::Yellow),
"blue" => Some(Color::Blue),
"purple" => Some(Color::Purple),
"cyan" => Some(Color::Cyan),
"white" => Some(Color::White),
_ => {
let value = input.parse::<u8>();
if let Ok(value) = value {
Some(Color::Fixed(value))
} else if input.starts_with("rgb(") && input.ends_with(')') {
let input = &input[4..input.len() - 1];
let mut colors = input.split(',').filter_map(|num| num.parse::<u8>().ok());
let r = colors.next();
let g = colors.next();
let b = colors.next();
match (r, g, b) {
(Some(r), Some(g), Some(b)) => Some(Color::RGB(r, g, b)),
_ => None,
}
} else if input.starts_with('#') {
let input = input.trim_start_matches('#');
let value = u32::from_str_radix(input, 16);
if let Ok(value) = value {
let r = (value >> 16) as u8;
let g = (value >> 8) as u8;
let b = value as u8;
Some(Color::RGB(r, g, b))
} else {
None
}
} else {
None
}
}
}
}
pub fn deserialize<'de, D>(des: D) -> Result<Style, D::Error>
where
D: Deserializer<'de>,
{
let string = des.deserialize_str(StringVisitor)?;
let mut style = Style::new();
let mut next_color_is_bg = false;
for word in string.split(' ') {
match word {
"bold" => style = style.bold(),
"italic" => style = style.italic(),
"dimmed" | "dim" => style = style.dimmed(),
"underline" | "under" => style = style.underline(),
"blink" => style = style.blink(),
"strikethrough" | "strike" => style = style.strikethrough(),
"hidden" | "none" => style = style.hidden(),
"on" => next_color_is_bg = true,
"plain" | "default" => (),
_ => {
if let Some(color) = parse_color(word) {
if next_color_is_bg {
style = style.on(color);
} else {
style = style.fg(color);
}
} else {
return Err(D::Error::invalid_value(
Unexpected::Str(word),
&"valid color token",
));
}
}
}
}
Ok(style)
}
fn color_to_string(color: Color) -> Cow<'static, str> {
match color {
Color::Black => "black".into(),
Color::Red => "red".into(),
Color::Green => "green".into(),
Color::Yellow => "yellow".into(),
Color::Blue => "blue".into(),
Color::Purple => "purple".into(),
Color::Cyan => "cyan".into(),
Color::White => "white".into(),
Color::Fixed(i) => format!("{}", i).into(),
Color::RGB(r, g, b) => format!("rgb({},{},{})", r, g, b).into(),
}
}
pub fn serialize<S>(style: &Style, ser: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut result: Vec<Cow<'static, str>> = vec![];
if style.is_bold {
result.push("bold".into());
}
if style.is_italic {
result.push("italic".into());
}
if style.is_dimmed {
result.push("dimmed".into());
}
if style.is_hidden {
result.push("hidden".into());
}
if style.is_blink {
result.push("blink".into());
}
if style.is_reverse {
result.push("reverse".into());
}
if style.is_strikethrough {
result.push("strikethrough".into());
}
if style.is_underline {
result.push("underline".into());
}
if let Some(fg) = style.foreground {
result.push(color_to_string(fg));
}
if let Some(bg) = style.background {
result.push("on".into());
result.push(color_to_string(bg));
}
if result.is_empty() {
result.push("plain".into());
}
ser.serialize_str(&result.join(" "))
}