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