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
//! `HtmlWriter` and related types.

use std::{fmt, io};

use termcolor::{ColorSpec, WriteColor};

use super::{
    fmt_to_io_error, IndexOrRgb, LineBreak, LineSplitter, StyledSpan, WriteLines, WriteStr,
};

impl StyledSpan {
    fn set_html_bg(&mut self, spec: &ColorSpec) -> io::Result<()> {
        use fmt::Write as _;

        let mut back_color_class = String::with_capacity(4);
        back_color_class.push_str("bg");
        let back_color = spec.bg().map(|&color| IndexOrRgb::new(color)).transpose()?;
        match back_color {
            Some(IndexOrRgb::Index(idx)) => {
                let final_idx = if spec.intense() { idx | 8 } else { idx };
                write!(&mut back_color_class, "{final_idx}").unwrap();
                // ^-- `unwrap` is safe; writing to a string never fails.
                self.classes.push(back_color_class);
            }
            Some(IndexOrRgb::Rgb(r, g, b)) => {
                self.styles
                    .push(format!("background: #{r:02x}{g:02x}{b:02x}"));
            }
            None => { /* Do nothing. */ }
        }
        Ok(())
    }
}

/// `WriteColor` implementation that renders output as HTML.
///
/// **NB.** The implementation relies on `ColorSpec`s supplied to `set_color` always having
/// `reset()` flag set. This is true for `TermOutputParser`.
pub(crate) struct HtmlWriter<'a> {
    output: &'a mut dyn fmt::Write,
    is_colored: bool,
    line_splitter: Option<LineSplitter>,
}

impl<'a> HtmlWriter<'a> {
    pub fn new(output: &'a mut dyn fmt::Write, max_width: Option<usize>) -> Self {
        Self {
            output,
            is_colored: false,
            line_splitter: max_width.map(LineSplitter::new),
        }
    }

    fn write_color(&mut self, spec: &ColorSpec) -> io::Result<()> {
        let mut span = StyledSpan::new(spec, "color")?;
        span.set_html_bg(spec)?;
        span.write_tag(self, "span")?;
        Ok(())
    }
}

impl WriteStr for HtmlWriter<'_> {
    fn write_str(&mut self, s: &str) -> io::Result<()> {
        self.output.write_str(s).map_err(fmt_to_io_error)
    }
}

impl WriteLines for HtmlWriter<'_> {
    fn line_splitter_mut(&mut self) -> Option<&mut LineSplitter> {
        self.line_splitter.as_mut()
    }

    fn write_line_break(&mut self, br: LineBreak, _char_width: usize) -> io::Result<()> {
        self.write_str(br.as_html())
    }

    fn write_new_line(&mut self, _char_width: usize) -> io::Result<()> {
        self.write_str("\n")
    }
}

impl io::Write for HtmlWriter<'_> {
    fn write(&mut self, buffer: &[u8]) -> io::Result<usize> {
        self.io_write(buffer, false)
    }

    fn flush(&mut self) -> io::Result<()> {
        Ok(())
    }
}

impl WriteColor for HtmlWriter<'_> {
    fn supports_color(&self) -> bool {
        true
    }

    fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> {
        debug_assert!(spec.reset());
        self.reset()?;
        if !spec.is_none() {
            self.write_color(spec)?;
            self.is_colored = true;
        }
        Ok(())
    }

    fn reset(&mut self) -> io::Result<()> {
        if self.is_colored {
            self.is_colored = false;
            self.write_str("</span>")?;
        }
        Ok(())
    }
}

impl LineBreak {
    fn as_html(self) -> &'static str {
        match self {
            Self::Hard => r#"<b class="hard-br"><br/></b>"#,
        }
    }
}