1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
//! Data provided to Handlebars templates.

use serde::Serialize;

use crate::{svg::TemplateOptions, write::SvgLine, UserInput};

/// Root data structure sent to the Handlebars template.
///
/// # Examples
///
/// Here's example of JSON serialization of this type:
///
/// ```
/// # use term_transcript::{svg::{TemplateOptions, NamedPalette}, Transcript, UserInput};
/// let mut transcript = Transcript::new();
/// let input = UserInput::command("rainbow");
/// transcript.add_interaction(input, "Hello, \u{1b}[32mworld\u{1b}[0m!");
/// let template_options = TemplateOptions {
///     palette: NamedPalette::Dracula.into(),
///     font_family: "Consolas, Menlo, monospace".to_owned(),
///     ..TemplateOptions::default()
/// };
/// let data = template_options.render_data(&transcript).unwrap();
///
/// let expected_json = serde_json::json!({
///     "creator": {
///         "name": "term-transcript",
///         "version": "0.3.0",
///         "repo": "https://github.com/slowli/term-transcript",
///     },
///     "width": 720,
///     "palette": {
///         "colors": {
///             "black": "#282936",
///             "red": "#ea51b2",
///             "green": "#ebff87",
///             "yellow": "#00f769",
///             "blue": "#62d6e8",
///             "magenta": "#b45bcf",
///             "cyan": "#a1efe4",
///             "white": "#e9e9f4",
///         },
///         "intense_colors": {
///             "black": "#626483",
///             "red": "#b45bcf",
///             "green": "#3a3c4e",
///             "yellow": "#4d4f68",
///             "blue": "#62d6e8",
///             "magenta": "#f1f2f8",
///             "cyan": "#00f769",
///             "white": "#f7f7fb",
///         },
///     },
///     "additional_styles": "",
///     "font_family": "Consolas, Menlo, monospace",
///     "window_frame": false,
///     "wrap": {
///         "hard_break_at": 80,
///     },
///     "line_numbers": null,
///     "has_failures": false,
///     "interactions": [{
///         "input": {
///             "text": "rainbow",
///             "prompt": "$",
///             "hidden": false,
///         },
///         "output_html": "Hello, <span class=\"fg2\">world</span>!",
/// #       "output_svg": [{
/// #           "background": null,
/// #           "foreground": "Hello,\u{a0}<tspan class=\"fg2\">world</tspan>!",
/// #       }],
/// #       // ^ Implementation detail for now
///         "failure": false,
///         "exit_status": null,
///     }]
/// });
/// assert_eq!(serde_json::to_value(data).unwrap(), expected_json);
/// ```
#[derive(Debug, Serialize)]
#[non_exhaustive]
pub struct HandlebarsData<'r> {
    /// Information about the rendering software.
    pub creator: CreatorData,
    /// Template options used for rendering. These options are flattened into the parent
    /// during serialization.
    #[serde(flatten)]
    pub options: &'r TemplateOptions,
    /// Recorded terminal interactions.
    pub interactions: Vec<SerializedInteraction<'r>>,
    /// Has any of terminal interactions failed?
    pub has_failures: bool,
}

/// Information about software used for rendering (i.e., this crate).
///
/// It can make sense to include this info as a comment in the rendered template
/// for debugging purposes.
#[derive(Debug, Serialize)]
#[non_exhaustive]
pub struct CreatorData {
    /// Name of software rendering the template.
    pub name: &'static str,
    /// Version of the rendering software.
    pub version: &'static str,
    /// Link to the git repository with the rendering software.
    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"),
        }
    }
}

/// Serializable version of [`Interaction`](crate::Interaction).
///
/// # HTML output
///
/// An interaction contains rendered HTML for the output with styles applied
/// to the relevant segments as `<span>`s. The styles are signalled using `class`es
/// and inline `style`s:
///
/// - `fg0`–`fg15` classes specify the foreground color being
///   0th–15th [base terminal color][colors]. `fg0`–`fg7` are ordinary colors,
///   and `fg8`–`fg15` are intense variations.
/// - Likewise, `bg0`–`bg15` classes specify the background color as one of the base terminal
///   colors.
/// - Remaining indexed colors and 24-bit colors have a definite value, and thus are signalled
///   via an inline `style` (e.g., `color: #c0ffee` or `background: #c0ffee`).
/// - `bold`, `italic`, `underline`, `dimmed` classes correspond to the corresponding text styles.
/// - [Hard breaks], if they are enabled, are represented by `<b class="hard-br"><br/></b>`.
///
/// The rendered HTML is assumed to be included into a container that preserves whitespace,
/// i.e., has [`white-space`] CSS property set to `pre`. An example of such container is `<pre>`.
///
/// [colors]: https://en.wikipedia.org/wiki/ANSI_escape_code#3-bit_and_4-bit
/// [`white-space`]: https://developer.mozilla.org/en-US/docs/Web/CSS/white-space
/// [Hard breaks]: crate::svg::WrapOptions::HardBreakAt
#[derive(Debug, Serialize)]
#[non_exhaustive]
pub struct SerializedInteraction<'a> {
    /// User's input.
    pub input: &'a UserInput,
    /// Terminal output in the [HTML format](#html-output).
    pub output_html: String,
    /// Terminal output in the SVG format.
    pub(crate) output_svg: Vec<SvgLine>,
    /// Exit status of the latest executed program, or `None` if it cannot be determined.
    pub exit_status: Option<i32>,
    /// Was execution unsuccessful judging by the [`ExitStatus`](crate::ExitStatus)?
    pub failure: bool,
}