term_transcript/term/
mod.rs

1use termcolor::NoColor;
2
3use std::{borrow::Cow, fmt::Write as WriteStr};
4
5#[cfg(feature = "svg")]
6use crate::write::{SvgLine, SvgWriter};
7use crate::{
8    utils::{normalize_newlines, WriteAdapter},
9    write::HtmlWriter,
10    TermError,
11};
12
13mod parser;
14#[cfg(test)]
15mod tests;
16
17pub(crate) use self::parser::TermOutputParser;
18
19/// Marker trait for supported types of terminal output.
20pub trait TermOutput: Clone + Send + Sync + 'static {}
21
22/// Output captured from the terminal.
23#[derive(Debug, Clone)]
24pub struct Captured(String);
25
26impl AsRef<str> for Captured {
27    fn as_ref(&self) -> &str {
28        &self.0
29    }
30}
31
32impl Captured {
33    pub(crate) fn new(term_output: String) -> Self {
34        // Normalize newlines to `\n`.
35        Self(match normalize_newlines(&term_output) {
36            Cow::Owned(normalized) => normalized,
37            Cow::Borrowed(_) => term_output,
38        })
39    }
40
41    pub(crate) fn write_as_html(
42        &self,
43        output: &mut dyn WriteStr,
44        wrap_width: Option<usize>,
45    ) -> Result<(), TermError> {
46        let mut html_writer = HtmlWriter::new(output, wrap_width);
47        TermOutputParser::new(&mut html_writer).parse(self.0.as_bytes())
48    }
49
50    #[cfg(feature = "svg")]
51    pub(crate) fn write_as_svg(
52        &self,
53        wrap_width: Option<usize>,
54    ) -> Result<Vec<SvgLine>, TermError> {
55        let mut svg_writer = SvgWriter::new(wrap_width);
56        TermOutputParser::new(&mut svg_writer).parse(self.0.as_bytes())?;
57        Ok(svg_writer.into_lines())
58    }
59
60    /// Converts this terminal output to an HTML string.
61    ///
62    /// The conversion applies styles by wrapping colored / styled text into `span`s with
63    /// the following `class`es:
64    ///
65    /// - `bold`, `italic`, `dimmed`, `underline` are self-explanatory
66    /// - `fg0`, `fg1`, ..., `fg15` are used to indicate indexed 4-bit ANSI color of the text.
67    ///   Indexes 0..=7 correspond to the ordinary color variations, and 8..=15
68    ///   to the intense ones.
69    /// - `bg0`, `bg1`, ..., `bg15` work similarly, but for the background color instead of
70    ///   text color.
71    ///
72    /// Indexed ANSI colors with indexes >15 and ANSI RGB colors are rendered using the `style`
73    /// attribute.
74    ///
75    /// The output string retains whitespace of the input. Hence, it needs to be wrapped
76    /// into a `pre` element or an element with the [`white-space`] CSS property set to `pre`
77    /// in order to be displayed properly.
78    ///
79    /// # Errors
80    ///
81    /// Returns an error if there was an issue processing output.
82    ///
83    /// [`white-space`]: https://developer.mozilla.org/en-US/docs/Web/CSS/white-space
84    pub fn to_html(&self) -> Result<String, TermError> {
85        let mut output = String::with_capacity(self.0.len());
86        self.write_as_html(&mut output, None)?;
87        Ok(output)
88    }
89
90    fn write_as_plaintext(&self, output: &mut dyn WriteStr) -> Result<(), TermError> {
91        let mut plaintext_writer = NoColor::new(WriteAdapter::new(output));
92        TermOutputParser::new(&mut plaintext_writer).parse(self.0.as_bytes())
93    }
94
95    /// Converts this terminal output to plaintext.
96    ///
97    /// # Errors
98    ///
99    /// Returns an error if there was an issue processing output.
100    pub fn to_plaintext(&self) -> Result<String, TermError> {
101        let mut output = String::with_capacity(self.0.len());
102        self.write_as_plaintext(&mut output)?;
103        Ok(output)
104    }
105}
106
107impl TermOutput for Captured {}