term_transcript/svg/
data.rs

1//! Data provided to Handlebars templates.
2
3use std::{collections::HashMap, fmt};
4
5use serde::Serialize;
6
7use super::write::StyledLine;
8use crate::{
9    svg::{EmbeddedFont, TemplateOptions},
10    UserInput,
11};
12
13/// Root data structure sent to the Handlebars template.
14///
15/// # Examples
16///
17/// Here's example of JSON serialization of this type:
18///
19/// ```
20/// # use term_transcript::{svg::{TemplateOptions, NamedPalette}, Transcript, UserInput};
21/// let mut transcript = Transcript::new();
22/// let input = UserInput::command("rainbow");
23/// transcript.add_interaction(input, "Hello, \u{1b}[32mworld\u{1b}[0m!");
24/// let template_options = TemplateOptions {
25///     palette: NamedPalette::Dracula.into(),
26///     font_family: "Consolas, Menlo, monospace".to_owned(),
27///     ..TemplateOptions::default()
28/// }
29/// .validated()?;
30/// let data = template_options.render_data(&transcript).unwrap();
31///
32/// let expected_json = serde_json::json!({
33///     "creator": {
34///         "name": "term-transcript",
35///         "version": "0.4.0",
36///         "repo": "https://github.com/slowli/term-transcript",
37///     },
38///     "width": 720,
39///     "line_height": null,
40///     "advance_width": null,
41///     "palette": {
42///         "colors": {
43///             "black": "#282936",
44///             "red": "#ea51b2",
45///             "green": "#ebff87",
46///             "yellow": "#00f769",
47///             "blue": "#62d6e8",
48///             "magenta": "#b45bcf",
49///             "cyan": "#a1efe4",
50///             "white": "#e9e9f4",
51///         },
52///         "intense_colors": {
53///             "black": "#626483",
54///             "red": "#b45bcf",
55///             "green": "#3a3c4e",
56///             "yellow": "#4d4f68",
57///             "blue": "#62d6e8",
58///             "magenta": "#f1f2f8",
59///             "cyan": "#00f769",
60///             "white": "#f7f7fb",
61///         },
62///     },
63///     "font_family": "Consolas, Menlo, monospace",
64///     "window": null,
65///     "wrap": {
66///         "hard_break_at": {
67///             "chars": 80,
68///             "mark": "ยป",
69///         },
70///     },
71///     "line_numbers": null,
72///     "dim_opacity": 0.7,
73///     "blink": {
74///         "opacity": 0.7,
75///         "interval": 1.0,
76///     },
77///     "has_failures": false,
78///     "interactions": [{
79///         "input": {
80///             "text": "rainbow",
81///             "prompt": "$",
82///             "hidden": false,
83///         },
84///         "output": [{
85///             "spans": [
86///                 { "text": "Hello, " },
87///                 { "text": "world", "fg": 2 },
88///                 { "text": "!" },
89///             ],
90///         }],
91///         "failure": false,
92///         "exit_status": null,
93///     }]
94/// });
95/// assert_eq!(serde_json::to_value(data).unwrap(), expected_json);
96/// # anyhow::Ok(())
97/// ```
98#[derive(Debug, Serialize)]
99#[non_exhaustive]
100pub struct HandlebarsData<'r> {
101    /// Information about the rendering software.
102    pub creator: CreatorData,
103    /// Template options used for rendering. These options are flattened into the parent
104    /// during serialization.
105    #[serde(flatten)]
106    pub options: &'r TemplateOptions,
107    /// Recorded terminal interactions.
108    pub interactions: Vec<SerializedInteraction<'r>>,
109    /// Has any of terminal interactions failed?
110    pub has_failures: bool,
111    /// A font (usually subset) to be embedded into the generated transcript.
112    #[serde(skip_serializing_if = "Option::is_none")]
113    pub embedded_font: Option<EmbeddedFont>,
114}
115
116// 1. font-subset -> term-transcript -> font-subset-cli, ..
117// Problem: different workspaces / repos; meaning that font-subset-cli will depend on 2 `font-subset`s (????)
118// Patching the font-subset dep sort of works, unless term-transcript code needs to be modified
119//
120// 2. same, but font-subset is an optional dep in term-transcript, not used in font-subset-cli
121// (replaced with a local module)
122
123/// Information about software used for rendering (i.e., this crate).
124///
125/// It can make sense to include this info as a comment in the rendered template
126/// for debugging purposes.
127#[derive(Debug, Serialize)]
128#[non_exhaustive]
129pub struct CreatorData {
130    /// Name of software rendering the template.
131    pub name: &'static str,
132    /// Version of the rendering software.
133    pub version: &'static str,
134    /// Link to the git repository with the rendering software.
135    pub repo: &'static str,
136}
137
138impl Default for CreatorData {
139    fn default() -> Self {
140        Self {
141            name: env!("CARGO_PKG_NAME"),
142            version: env!("CARGO_PKG_VERSION"),
143            repo: env!("CARGO_PKG_REPOSITORY"),
144        }
145    }
146}
147
148/// Serializable version of [`Interaction`](crate::Interaction).
149#[derive(Serialize)]
150#[non_exhaustive]
151pub struct SerializedInteraction<'a> {
152    /// User's input.
153    pub input: &'a UserInput,
154    /// Terminal output in the [HTML format](#html-output).
155    pub(crate) output: Vec<StyledLine>,
156    /// Exit status of the latest executed program, or `None` if it cannot be determined.
157    pub exit_status: Option<i32>,
158    /// Was execution unsuccessful judging by the [`ExitStatus`](crate::ExitStatus)?
159    pub failure: bool,
160}
161
162impl fmt::Debug for SerializedInteraction<'_> {
163    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
164        formatter
165            .debug_struct("SerializedInteraction")
166            .field("input", &self.input)
167            .field("output.line_count", &self.output.len())
168            .field("exit_status", &self.exit_status)
169            .finish_non_exhaustive()
170    }
171}
172
173#[derive(Debug, Serialize)]
174pub(super) struct CompleteHandlebarsData<'r> {
175    #[serde(flatten)]
176    pub inner: HandlebarsData<'r>,
177    #[serde(rename = "const")]
178    pub constants: &'r HashMap<&'static str, u32>,
179}