use {
super::ScrollBarStyleDef,
crate::{
minimad::Alignment,
parse_compound_style,
parse_line_style,
parse_styled_char,
LineStyle,
MadSkin,
TableBorderChars,
ATTRIBUTES,
},
serde::{
de,
ser::SerializeMap,
Deserialize,
Serialize,
Serializer,
},
std::fmt,
};
impl<'de> de::Deserialize<'de> for MadSkin {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: de::Deserializer<'de>,
{
struct SkinVisitor;
impl<'de> de::Visitor<'de> for SkinVisitor {
type Value = MadSkin;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("MadSkin")
}
fn visit_map<V>(self, mut map: V) -> Result<Self::Value, V::Error>
where
V: de::MapAccess<'de>,
{
let mut skin = MadSkin::default();
while let Some(key) = map.next_key::<String>()? {
match key.as_str() {
"bold" => {
let value = map.next_value::<String>()?;
let cs = parse_compound_style(&value).map_err(de::Error::custom)?;
skin.bold = cs;
}
"italic" => {
let value = map.next_value::<String>()?;
let cs = parse_compound_style(&value).map_err(de::Error::custom)?;
skin.italic = cs;
}
"strikeout" => {
let value = map.next_value::<String>()?;
let cs = parse_compound_style(&value).map_err(de::Error::custom)?;
skin.strikeout = cs;
}
"inline_code" | "inline-code" => {
let value = map.next_value::<String>()?;
let cs = parse_compound_style(&value).map_err(de::Error::custom)?;
skin.inline_code = cs;
}
"ellipsis" => {
let value = map.next_value::<String>()?;
let cs = parse_compound_style(&value).map_err(de::Error::custom)?;
skin.ellipsis = cs;
}
"bullet" => {
let value = map.next_value::<String>()?;
let sc = parse_styled_char(&value, '*').map_err(de::Error::custom)?;
skin.bullet = sc;
}
"quote_mark" | "quote" | "quote-mark" => {
let value = map.next_value::<String>()?;
let sc = parse_styled_char(&value, '*').map_err(de::Error::custom)?;
skin.quote_mark = sc;
}
"horizontal_rule" | "horizontal-rule" | "rule" => {
let value = map.next_value::<String>()?;
let sc = parse_styled_char(&value, '*').map_err(de::Error::custom)?;
skin.horizontal_rule = sc;
}
"scrollbar" => {
let def: ScrollBarStyleDef = map.next_value()?;
skin.scrollbar = def.into_scrollbar_style();
}
"paragraph" => {
let value = map.next_value::<String>()?;
let ls = parse_line_style(&value).map_err(de::Error::custom)?;
skin.paragraph = ls;
}
"code_block" | "code-block" => {
let value = map.next_value::<String>()?;
let ls = parse_line_style(&value).map_err(de::Error::custom)?;
skin.code_block = ls;
}
"table" => {
let value = map.next_value::<String>()?;
let ls = parse_line_style(&value).map_err(de::Error::custom)?;
skin.table = ls;
}
"headers" => match map.next_value::<HeadersStyleInfo>()? {
HeadersStyleInfo::Add(ls) => {
for h in &mut skin.headers {
if let Some(fg) = ls.compound_style.get_fg() {
h.compound_style.set_fg(fg);
}
if let Some(bg) = ls.compound_style.get_bg() {
h.compound_style.set_bg(bg);
}
for &attr in ATTRIBUTES {
if ls.compound_style.has_attr(attr) {
h.compound_style.add_attr(attr);
}
}
if ls.align != Alignment::Unspecified {
h.align = ls.align;
}
}
}
HeadersStyleInfo::Levels(mut vls) => {
for (lvl, h) in vls.drain(..).enumerate() {
if lvl < skin.headers.len() {
skin.headers[lvl] = h;
}
}
}
},
"table_border_chars" | "table-border-chars" => {
let key = map.next_value::<String>()?;
if let Some(chars) = TableBorderChars::by_key(&key) {
skin.table_border_chars = chars;
}
}
_ => {
let _ = map.next_value::<String>()?;
println!("unknown key: {key}");
}
}
}
Ok(skin)
}
}
deserializer.deserialize_map(SkinVisitor {})
}
}
impl Serialize for MadSkin {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut skin = serializer.serialize_map(None)?;
skin.serialize_entry("bold", &self.bold)?;
skin.serialize_entry("italic", &self.italic)?;
skin.serialize_entry("strikeout", &self.strikeout)?;
skin.serialize_entry("inline_code", &self.inline_code)?;
skin.serialize_entry("ellipsis", &self.ellipsis)?;
skin.serialize_entry("bullet", &self.bullet)?;
skin.serialize_entry("quote", &self.quote_mark)?;
skin.serialize_entry("horizontal_rule", &self.horizontal_rule)?;
let def: ScrollBarStyleDef = (&self.scrollbar).into();
skin.serialize_entry("scrollbar", &def)?;
skin.serialize_entry("paragraph", &self.paragraph)?;
skin.serialize_entry("code_block", &self.code_block)?;
skin.serialize_entry("table", &self.table)?;
skin.serialize_entry("headers", &self.headers)?;
if let Some(key) = self.table_border_chars.key() {
skin.serialize_entry("table_border_chars", key)?;
}
skin.end()
}
}
#[derive(Deserialize)]
#[serde(untagged)]
enum HeadersStyleInfo {
Add(LineStyle),
Levels(Vec<LineStyle>),
}
#[test]
fn skin_json_roundtrip() {
use {
crate::{
crossterm::style::{
Attribute,
Color::*,
},
gray,
rgb,
StyledChar,
ROUNDED_TABLE_BORDER_CHARS,
},
pretty_assertions::assert_eq,
};
let skin = MadSkin::default();
let serialized = serde_json::to_string_pretty(&skin).unwrap();
let deserialized = serde_json::from_str(&serialized).unwrap();
assert_eq!(skin, deserialized);
let mut skin = MadSkin::no_style();
skin.limit_to_ascii();
let serialized = serde_json::to_string_pretty(&skin).unwrap();
let deserialized = serde_json::from_str(&serialized).unwrap();
assert_eq!(skin, deserialized);
let skin = MadSkin::default_dark();
let serialized = serde_json::to_string_pretty(&skin).unwrap();
let deserialized = serde_json::from_str(&serialized).unwrap();
assert_eq!(skin, deserialized);
let skin = MadSkin::default_light();
let serialized = serde_json::to_string_pretty(&skin).unwrap();
let deserialized = serde_json::from_str(&serialized).unwrap();
assert_eq!(skin, deserialized);
let mut skin = MadSkin::default();
skin.set_headers_fg(AnsiValue(178));
skin.headers[2].set_fg(gray(22));
skin.bold.set_fg(Yellow);
skin.italic.set_fgbg(Magenta, rgb(30, 30, 40));
skin.bullet = StyledChar::from_fg_char(Yellow, '⟡');
skin.quote_mark.set_fg(Yellow);
skin.italic.set_fg(Magenta);
skin.scrollbar.thumb.set_fg(AnsiValue(178));
skin.table_border_chars = ROUNDED_TABLE_BORDER_CHARS;
skin.paragraph.align = Alignment::Center;
skin.table.align = Alignment::Center;
skin.inline_code.add_attr(Attribute::Reverse);
skin.paragraph.set_fgbg(Magenta, rgb(30, 30, 40));
skin.italic.add_attr(Attribute::Underlined);
skin.italic.add_attr(Attribute::OverLined);
let serialized = serde_json::to_string_pretty(&skin).unwrap();
let deserialized = serde_json::from_str(&serialized).unwrap();
assert_eq!(skin, deserialized);
}