use colored::*;
use serde::{Deserialize, Deserializer, Serialize};
use std::fmt;
#[derive(Debug, Deserialize, Serialize, PartialEq, Eq, Clone)]
#[serde(deny_unknown_fields)]
pub struct ColourTheme {
#[serde(
default,
serialize_with = "color_serializer",
deserialize_with = "deserialize_option_color_from_string"
)]
pub name: Option<Color>,
#[serde(
default,
serialize_with = "color_serializer",
deserialize_with = "deserialize_option_color_from_string"
)]
pub serial: Option<Color>,
#[serde(
default,
serialize_with = "color_serializer",
deserialize_with = "deserialize_option_color_from_string"
)]
pub manufacturer: Option<Color>,
#[serde(
default,
serialize_with = "color_serializer",
deserialize_with = "deserialize_option_color_from_string"
)]
pub driver: Option<Color>,
#[serde(
default,
serialize_with = "color_serializer",
deserialize_with = "deserialize_option_color_from_string"
)]
pub string: Option<Color>,
#[serde(
default,
serialize_with = "color_serializer",
deserialize_with = "deserialize_option_color_from_string"
)]
pub icon: Option<Color>,
#[serde(
default,
serialize_with = "color_serializer",
deserialize_with = "deserialize_option_color_from_string"
)]
pub location: Option<Color>,
#[serde(
default,
serialize_with = "color_serializer",
deserialize_with = "deserialize_option_color_from_string"
)]
pub path: Option<Color>,
#[serde(
default,
serialize_with = "color_serializer",
deserialize_with = "deserialize_option_color_from_string"
)]
pub number: Option<Color>,
#[serde(
default,
serialize_with = "color_serializer",
deserialize_with = "deserialize_option_color_from_string"
)]
pub speed: Option<Color>,
#[serde(
default,
serialize_with = "color_serializer",
deserialize_with = "deserialize_option_color_from_string"
)]
pub vid: Option<Color>,
#[serde(
default,
serialize_with = "color_serializer",
deserialize_with = "deserialize_option_color_from_string"
)]
pub pid: Option<Color>,
#[serde(
default,
serialize_with = "color_serializer",
deserialize_with = "deserialize_option_color_from_string"
)]
pub class_code: Option<Color>,
#[serde(
default,
serialize_with = "color_serializer",
deserialize_with = "deserialize_option_color_from_string"
)]
pub sub_code: Option<Color>,
#[serde(
default,
serialize_with = "color_serializer",
deserialize_with = "deserialize_option_color_from_string"
)]
pub protocol: Option<Color>,
#[serde(
default,
serialize_with = "color_serializer",
deserialize_with = "deserialize_option_color_from_string"
)]
pub attributes: Option<Color>,
#[serde(
default,
serialize_with = "color_serializer",
deserialize_with = "deserialize_option_color_from_string"
)]
pub power: Option<Color>,
#[serde(
default,
serialize_with = "color_serializer",
deserialize_with = "deserialize_option_color_from_string"
)]
pub tree: Option<Color>,
#[serde(
default,
serialize_with = "color_serializer",
deserialize_with = "deserialize_option_color_from_string"
)]
pub tree_bus_start: Option<Color>,
#[serde(
default,
serialize_with = "color_serializer",
deserialize_with = "deserialize_option_color_from_string"
)]
pub tree_bus_terminator: Option<Color>,
#[serde(
default,
serialize_with = "color_serializer",
deserialize_with = "deserialize_option_color_from_string"
)]
pub tree_configuration_terminator: Option<Color>,
#[serde(
default,
serialize_with = "color_serializer",
deserialize_with = "deserialize_option_color_from_string"
)]
pub tree_interface_terminator: Option<Color>,
#[serde(
default,
serialize_with = "color_serializer",
deserialize_with = "deserialize_option_color_from_string"
)]
pub tree_endpoint_in: Option<Color>,
#[serde(
default,
serialize_with = "color_serializer",
deserialize_with = "deserialize_option_color_from_string"
)]
pub tree_endpoint_out: Option<Color>,
#[serde(
default,
serialize_with = "color_serializer",
deserialize_with = "deserialize_option_color_from_string"
)]
pub muted: Option<Color>,
}
fn deserialize_option_color_from_string<'de, D>(deserializer: D) -> Result<Option<Color>, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(untagged)]
enum ColorOrNull<'a> {
Str(&'a str),
#[serde(deserialize_with = "deserialize_color")]
FromStr(Color),
Null,
}
match ColorOrNull::deserialize(deserializer)? {
ColorOrNull::Str(s) => match s {
"" => Ok(None),
_ => Ok(Some(deserialize_color(
serde::de::IntoDeserializer::into_deserializer(s),
)?)),
},
ColorOrNull::FromStr(i) => Ok(Some(i)),
ColorOrNull::Null => Ok(None),
}
}
fn deserialize_color<'de, D>(deserializer: D) -> Result<Color, D::Error>
where
D: serde::de::Deserializer<'de>,
{
struct ColorVisitor;
impl<'de> serde::de::Visitor<'de> for ColorVisitor {
type Value = Color;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("colour string, '#rgb' or `3 u8 RGB array`")
}
fn visit_str<E>(self, value: &str) -> Result<Color, E>
where
E: serde::de::Error,
{
if let Some(stripped) = value.strip_prefix('$') {
let num = u8::from_str_radix(stripped, 16).map_err(|_| {
serde::de::Error::custom(format!(
"Invalid ANSI color code: {}. Expected format is '$XX' where XX is a hex number between 00 and FF.",
value
))
})?;
Ok(Color::AnsiColor(num))
} else {
Ok(Color::from(value))
}
}
fn visit_seq<M>(self, mut seq: M) -> Result<Color, M::Error>
where
M: serde::de::SeqAccess<'de>,
{
let mut values = Vec::new();
if let Some(size) = seq.size_hint() {
if size != 3 {
return Err(serde::de::Error::invalid_length(
size,
&"a list of size 3(RGB)",
));
}
}
loop {
match seq.next_element::<u8>() {
Ok(Some(x)) => {
values.push(x);
}
Ok(None) => break,
Err(e) => {
return Err(e);
}
}
}
if values.len() != 3 {
return Err(serde::de::Error::invalid_length(
values.len(),
&"A u8 list of size 3: [R, G, B]",
));
}
Ok(Color::TrueColor {
r: values[0],
g: values[1],
b: values[2],
})
}
}
deserializer.deserialize_any(ColorVisitor)
}
fn color_to_string(color: Color) -> String {
match color {
Color::Black => "black".into(),
Color::Red => "red".into(),
Color::Green => "green".into(),
Color::Yellow => "yellow".into(),
Color::Blue => "blue".into(),
Color::Magenta => "magenta".into(),
Color::Cyan => "cyan".into(),
Color::White => "white".into(),
Color::BrightBlack => "bright black".into(),
Color::BrightRed => "bright red".into(),
Color::BrightGreen => "bright green".into(),
Color::BrightYellow => "bright yellow".into(),
Color::BrightBlue => "bright blue".into(),
Color::BrightMagenta => "bright magenta".into(),
Color::BrightCyan => "bright cyan".into(),
Color::BrightWhite => "bright white".into(),
Color::AnsiColor(n) => format!("${:02X}", n),
Color::TrueColor { r, g, b } => format!("#{:02X}{:02X}{:02X}", r, g, b),
}
}
fn color_serializer<S>(color: &Option<Color>, s: S) -> Result<S::Ok, S::Error>
where
S: serde::ser::Serializer,
{
match color {
Some(c) => s.serialize_str(&color_to_string(*c)),
None => s.serialize_none(),
}
}
impl Default for ColourTheme {
fn default() -> Self {
ColourTheme::new()
}
}
impl ColourTheme {
pub fn new() -> Self {
ColourTheme {
name: Some(Color::BrightBlue),
serial: Some(Color::Green),
manufacturer: Some(Color::Blue),
driver: Some(Color::BrightMagenta),
string: Some(Color::Blue),
icon: None,
location: Some(Color::Magenta),
path: Some(Color::BrightCyan),
number: Some(Color::Cyan),
speed: Some(Color::Magenta),
vid: Some(Color::BrightYellow),
pid: Some(Color::Yellow),
class_code: Some(Color::BrightYellow),
sub_code: Some(Color::Yellow),
protocol: Some(Color::Yellow),
attributes: Some(Color::Magenta),
power: Some(Color::Red),
tree: Some(Color::BrightBlack),
tree_bus_start: Some(Color::BrightBlack),
tree_bus_terminator: Some(Color::BrightBlack),
tree_configuration_terminator: Some(Color::BrightBlack),
tree_interface_terminator: Some(Color::BrightBlack),
tree_endpoint_in: Some(Color::Yellow),
tree_endpoint_out: Some(Color::Magenta),
muted: Some(Color::BrightBlack),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_serialize_color_theme() {
let ct: ColourTheme = ColourTheme::new();
println!("{}", serde_json::to_string_pretty(&ct).unwrap());
}
#[test]
fn test_deserialize_color_theme() {
let ct: ColourTheme = serde_json::from_str(r#"{"name": "blue"}"#).unwrap();
assert_eq!(ct.name, Some(Color::Blue));
}
#[test]
fn test_serialize_deserialize_color_theme() {
let ct: ColourTheme = ColourTheme::new();
let ser = serde_json::to_string_pretty(&ct).unwrap();
let ctrt: ColourTheme = serde_json::from_str(&ser).unwrap();
assert_eq!(ct, ctrt);
}
#[test]
fn test_deserialize_other_colors() {
let ct: ColourTheme = serde_json::from_str(r##"{"name": "#FF5733"}"##).unwrap();
assert_eq!(
ct.name,
Some(Color::TrueColor {
r: 0xFF,
g: 0x57,
b: 0x33
})
);
let ct2: ColourTheme = serde_json::from_str(r#"{"name": [255, 87, 51]}"#).unwrap();
assert_eq!(
ct2.name,
Some(Color::TrueColor {
r: 0xFF,
g: 0x57,
b: 0x33
})
);
let ct3: ColourTheme = serde_json::from_str(r##"{"name": "$1A"}"##).unwrap();
assert_eq!(ct3.name, Some(Color::AnsiColor(0x1A)));
}
}