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}