1use std::{collections::HashMap, fmt};
4
5use anstyle::{Ansi256Color, Color, Effects, RgbColor, Style};
6use serde::Serialize;
7
8use crate::{
9 UserInput,
10 svg::{EmbeddedFont, TemplateOptions},
11};
12
13pub(super) mod serde_color {
14 use std::fmt;
15
16 use anstyle::RgbColor;
17 use serde::{Deserializer, Serialize, Serializer, de};
18 use styled_str::{parse_hex_color, rgb_color_to_hex};
19
20 #[allow(clippy::trivially_copy_pass_by_ref)] pub(crate) fn serialize<S: Serializer>(
22 color: &RgbColor,
23 serializer: S,
24 ) -> Result<S::Ok, S::Error> {
25 rgb_color_to_hex(*color).serialize(serializer)
26 }
27
28 pub(crate) fn deserialize<'de, D: Deserializer<'de>>(
29 deserializer: D,
30 ) -> Result<RgbColor, D::Error> {
31 #[derive(Debug)]
32 struct ColorVisitor;
33
34 impl de::Visitor<'_> for ColorVisitor {
35 type Value = RgbColor;
36
37 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
38 formatter.write_str("hex color, such as #fed or #a757ff")
39 }
40
41 fn visit_str<E: de::Error>(self, value: &str) -> Result<Self::Value, E> {
42 parse_hex_color(value.as_bytes()).map_err(E::custom)
43 }
44 }
45
46 deserializer.deserialize_str(ColorVisitor)
47 }
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
51#[serde(untagged)]
52pub(super) enum SerdeColor {
53 Index(u8),
54 Rgb(#[serde(with = "serde_color")] RgbColor),
55}
56
57impl From<Color> for SerdeColor {
58 fn from(color: Color) -> Self {
59 match color {
60 Color::Ansi(color) => Self::Index(color as u8),
61 Color::Ansi256(Ansi256Color(idx)) => Self::Index(idx),
62 Color::Rgb(color) => Self::Rgb(color),
63 }
64 }
65}
66
67#[derive(Debug, Default, Clone, Copy, PartialEq, Serialize)]
69#[allow(clippy::struct_excessive_bools)] pub(super) struct SerdeStyle {
71 #[serde(skip_serializing_if = "SerdeStyle::is_false")]
72 pub(super) bold: bool,
73 #[serde(skip_serializing_if = "SerdeStyle::is_false")]
74 pub(super) italic: bool,
75 #[serde(skip_serializing_if = "SerdeStyle::is_false")]
76 pub(super) underline: bool,
77 #[serde(skip_serializing_if = "SerdeStyle::is_false")]
78 pub(super) dimmed: bool,
79 #[serde(skip_serializing_if = "SerdeStyle::is_false")]
80 pub(super) strikethrough: bool,
81 #[serde(skip_serializing_if = "SerdeStyle::is_false")]
82 pub(super) inverted: bool,
83 #[serde(skip_serializing_if = "SerdeStyle::is_false")]
84 pub(super) blink: bool,
85 #[serde(skip_serializing_if = "SerdeStyle::is_false")]
86 pub(super) concealed: bool,
87 #[serde(skip_serializing_if = "Option::is_none")]
88 pub(super) fg: Option<SerdeColor>,
89 #[serde(skip_serializing_if = "Option::is_none")]
90 pub(super) bg: Option<SerdeColor>,
91}
92
93impl SerdeStyle {
94 #[allow(clippy::trivially_copy_pass_by_ref)] fn is_false(&value: &bool) -> bool {
96 !value
97 }
98}
99
100impl From<Style> for SerdeStyle {
101 fn from(style: Style) -> Self {
102 let effects = style.get_effects();
103 Self {
104 bold: effects.contains(Effects::BOLD),
105 italic: effects.contains(Effects::ITALIC),
106 underline: effects.contains(Effects::UNDERLINE),
107 dimmed: effects.contains(Effects::DIMMED),
108 strikethrough: effects.contains(Effects::STRIKETHROUGH),
109 inverted: effects.contains(Effects::INVERT),
110 blink: effects.contains(Effects::BLINK),
111 concealed: effects.contains(Effects::HIDDEN),
112 fg: style.get_fg_color().map(SerdeColor::from),
113 bg: style.get_bg_color().map(SerdeColor::from),
114 }
115 }
116}
117
118#[derive(Debug, Serialize)]
120pub(super) struct SerdeStyledSpan<'a> {
121 #[serde(flatten)]
122 pub(super) style: SerdeStyle,
123 pub(super) text: &'a str,
124}
125
126#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
127#[serde(rename_all = "snake_case")]
128pub(super) enum LineBreak {
129 Hard,
130}
131
132#[derive(Debug, Default, Serialize)]
133pub(super) struct StyledLine<'a> {
134 pub(super) spans: Vec<SerdeStyledSpan<'a>>,
135 #[serde(skip_serializing_if = "Option::is_none")]
136 pub(super) br: Option<LineBreak>,
137}
138
139#[derive(Debug, Serialize)]
227#[non_exhaustive]
228pub struct HandlebarsData<'r> {
229 pub creator: CreatorData,
231 #[serde(flatten)]
234 pub options: &'r TemplateOptions,
235 pub interactions: Vec<SerializedInteraction<'r>>,
237 pub has_failures: bool,
239 #[serde(skip_serializing_if = "Option::is_none")]
241 pub embedded_font: Option<EmbeddedFont>,
242}
243
244#[derive(Debug, Serialize)]
256#[non_exhaustive]
257pub struct CreatorData {
258 pub name: &'static str,
260 pub version: &'static str,
262 pub repo: &'static str,
264}
265
266impl Default for CreatorData {
267 fn default() -> Self {
268 Self {
269 name: env!("CARGO_PKG_NAME"),
270 version: env!("CARGO_PKG_VERSION"),
271 repo: env!("CARGO_PKG_REPOSITORY"),
272 }
273 }
274}
275
276#[derive(Serialize)]
278#[non_exhaustive]
279pub struct SerializedInteraction<'a> {
280 pub input: &'a UserInput,
282 pub(super) output: Vec<StyledLine<'a>>,
284 pub exit_status: Option<i32>,
286 pub failure: bool,
288}
289
290impl fmt::Debug for SerializedInteraction<'_> {
291 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
292 formatter
293 .debug_struct("SerializedInteraction")
294 .field("input", &self.input)
295 .field("output.line_count", &self.output.len())
296 .field("exit_status", &self.exit_status)
297 .finish_non_exhaustive()
298 }
299}
300
301#[derive(Debug, Serialize)]
302pub(super) struct CompleteHandlebarsData<'r> {
303 #[serde(flatten)]
304 pub inner: HandlebarsData<'r>,
305 #[serde(rename = "const")]
306 pub constants: &'r HashMap<&'static str, u32>,
307}