term_transcript/term/
mod.rs

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