use std::{collections::HashMap, fmt};
use anstyle::{Ansi256Color, Color, Effects, RgbColor, Style};
use serde::Serialize;
use crate::{
UserInput,
svg::{EmbeddedFont, TemplateOptions},
};
pub(super) mod serde_color {
use std::fmt;
use anstyle::RgbColor;
use serde::{Deserializer, Serialize, Serializer, de};
use styled_str::{parse_hex_color, rgb_color_to_hex};
#[allow(clippy::trivially_copy_pass_by_ref)] pub(crate) fn serialize<S: Serializer>(
color: &RgbColor,
serializer: S,
) -> Result<S::Ok, S::Error> {
rgb_color_to_hex(*color).serialize(serializer)
}
pub(crate) fn deserialize<'de, D: Deserializer<'de>>(
deserializer: D,
) -> Result<RgbColor, D::Error> {
#[derive(Debug)]
struct ColorVisitor;
impl de::Visitor<'_> for ColorVisitor {
type Value = RgbColor;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("hex color, such as #fed or #a757ff")
}
fn visit_str<E: de::Error>(self, value: &str) -> Result<Self::Value, E> {
parse_hex_color(value.as_bytes()).map_err(E::custom)
}
}
deserializer.deserialize_str(ColorVisitor)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
#[serde(untagged)]
pub(super) enum SerdeColor {
Index(u8),
Rgb(#[serde(with = "serde_color")] RgbColor),
}
impl From<Color> for SerdeColor {
fn from(color: Color) -> Self {
match color {
Color::Ansi(color) => Self::Index(color as u8),
Color::Ansi256(Ansi256Color(idx)) => Self::Index(idx),
Color::Rgb(color) => Self::Rgb(color),
}
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Serialize)]
#[allow(clippy::struct_excessive_bools)] pub(super) struct SerdeStyle {
#[serde(skip_serializing_if = "SerdeStyle::is_false")]
pub(super) bold: bool,
#[serde(skip_serializing_if = "SerdeStyle::is_false")]
pub(super) italic: bool,
#[serde(skip_serializing_if = "SerdeStyle::is_false")]
pub(super) underline: bool,
#[serde(skip_serializing_if = "SerdeStyle::is_false")]
pub(super) dimmed: bool,
#[serde(skip_serializing_if = "SerdeStyle::is_false")]
pub(super) strikethrough: bool,
#[serde(skip_serializing_if = "SerdeStyle::is_false")]
pub(super) inverted: bool,
#[serde(skip_serializing_if = "SerdeStyle::is_false")]
pub(super) blink: bool,
#[serde(skip_serializing_if = "SerdeStyle::is_false")]
pub(super) concealed: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub(super) fg: Option<SerdeColor>,
#[serde(skip_serializing_if = "Option::is_none")]
pub(super) bg: Option<SerdeColor>,
}
impl SerdeStyle {
#[allow(clippy::trivially_copy_pass_by_ref)] fn is_false(&value: &bool) -> bool {
!value
}
}
impl From<Style> for SerdeStyle {
fn from(style: Style) -> Self {
let effects = style.get_effects();
Self {
bold: effects.contains(Effects::BOLD),
italic: effects.contains(Effects::ITALIC),
underline: effects.contains(Effects::UNDERLINE),
dimmed: effects.contains(Effects::DIMMED),
strikethrough: effects.contains(Effects::STRIKETHROUGH),
inverted: effects.contains(Effects::INVERT),
blink: effects.contains(Effects::BLINK),
concealed: effects.contains(Effects::HIDDEN),
fg: style.get_fg_color().map(SerdeColor::from),
bg: style.get_bg_color().map(SerdeColor::from),
}
}
}
#[derive(Debug, Serialize)]
pub(super) struct SerdeStyledSpan<'a> {
#[serde(flatten)]
pub(super) style: SerdeStyle,
pub(super) text: &'a str,
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub(super) enum LineBreak {
Hard,
}
#[derive(Debug, Default, Serialize)]
pub(super) struct StyledLine<'a> {
pub(super) spans: Vec<SerdeStyledSpan<'a>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub(super) br: Option<LineBreak>,
}
#[derive(Debug, Serialize)]
#[non_exhaustive]
pub struct HandlebarsData<'r> {
pub creator: CreatorData,
#[serde(flatten)]
pub options: &'r TemplateOptions,
pub interactions: Vec<SerializedInteraction<'r>>,
pub has_failures: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub embedded_font: Option<EmbeddedFont>,
}
#[derive(Debug, Serialize)]
#[non_exhaustive]
pub struct CreatorData {
pub name: &'static str,
pub version: &'static str,
pub repo: &'static str,
}
impl Default for CreatorData {
fn default() -> Self {
Self {
name: env!("CARGO_PKG_NAME"),
version: env!("CARGO_PKG_VERSION"),
repo: env!("CARGO_PKG_REPOSITORY"),
}
}
}
#[derive(Serialize)]
#[non_exhaustive]
pub struct SerializedInteraction<'a> {
pub input: &'a UserInput,
pub(super) output: Vec<StyledLine<'a>>,
pub exit_status: Option<i32>,
pub failure: bool,
}
impl fmt::Debug for SerializedInteraction<'_> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("SerializedInteraction")
.field("input", &self.input)
.field("output.line_count", &self.output.len())
.field("exit_status", &self.exit_status)
.finish_non_exhaustive()
}
}
#[derive(Debug, Serialize)]
pub(super) struct CompleteHandlebarsData<'r> {
#[serde(flatten)]
pub inner: HandlebarsData<'r>,
#[serde(rename = "const")]
pub constants: &'r HashMap<&'static str, u32>,
}