evobench_tools/output_table/
html.rs

1use ahtml::{AVec, HtmlAllocator, Node, att, util::SoftPre};
2
3use crate::{
4    output_table::{BarKind, CellValue, OutputStyle, OutputTable},
5    utillib::html_util::anchor,
6    warn,
7};
8
9use super::{OutputTableTitle, Row};
10
11impl OutputStyle {
12    fn to_css_style(self) -> String {
13        let OutputStyle {
14            faded,
15            bold,
16            italic,
17            color,
18            font_size,
19        } = self;
20
21        let mut s = String::new();
22
23        if italic {
24            s.push_str("font-style: italic; ");
25        }
26        if bold {
27            s.push_str("font-weight: bold; ");
28        }
29
30        let mut htmlcolor: Option<&str> = None;
31        if let Some(col) = color {
32            match col {
33                4 => {
34                    htmlcolor = {
35                        // terminal color 4 is blue, but choose something
36                        // else to differentiate from links
37                        Some("#e46000")
38                    }
39                }
40                _ => {
41                    warn!("ignoring unknown color code {col}");
42                }
43            }
44        }
45        if faded {
46            if htmlcolor.is_some() {
47                warn!(
48                    "both 'faded' and 'color' were given, ignoring 'faded' (should it shade color?)"
49                );
50            } else {
51                htmlcolor = Some("gray");
52            }
53        }
54        if let Some(htmlcol) = htmlcolor {
55            s.push_str("color: ");
56            s.push_str(htmlcol);
57            s.push_str("; ");
58        }
59
60        if let Some(fs) = font_size {
61            s.push_str("font-size: ");
62            s.push_str(fs.as_ref());
63            s.push_str("; ");
64        }
65
66        // trim whitespace at the end in place
67        s.truncate(s.trim_end().len());
68
69        s
70    }
71}
72
73pub struct HtmlTable<'allocator> {
74    num_columns: usize,
75    table_body: AVec<'allocator, Node>,
76    // Don't need a separate allocator field since table_body carries it already.
77    // html: &'allocator HtmlAllocator,
78}
79
80impl<'allocator> HtmlTable<'allocator> {
81    pub fn new(num_columns: usize, html: &'allocator HtmlAllocator) -> Self {
82        Self {
83            num_columns,
84            table_body: html.new_vec(),
85        }
86    }
87}
88
89impl<'allocator> OutputTable for HtmlTable<'allocator> {
90    type Output = AVec<'allocator, Node>;
91
92    fn num_columns(&self) -> usize {
93        self.num_columns
94    }
95
96    fn write_row<'url, V: CellValue<'url>>(
97        &mut self,
98        row: Row<V>,
99        line_style: Option<OutputStyle>,
100    ) -> anyhow::Result<()> {
101        let htmlstyle = line_style.map(OutputStyle::to_css_style);
102        let html = self.table_body.allocator();
103        let mut cells = html.new_vec();
104        match row {
105            Row::WithSpans(items) => {
106                for item in items {
107                    let OutputTableTitle {
108                        text,
109                        span,
110                        anchor_name,
111                    } = item;
112                    let s: &str = text.as_ref();
113                    let text_node = html.text(s)?;
114                    let text = if let Some(style) = &htmlstyle {
115                        html.span([att("style", style)], text_node)?
116                    } else {
117                        text_node
118                    };
119                    let content = if let Some(anchor_name) = anchor_name {
120                        anchor(anchor_name, text, html)?
121                    } else {
122                        text
123                    };
124                    cells.push(html.td([att("colspan", *span)], content)?)?;
125                }
126            }
127            Row::PlainStrings(items) => {
128                for item in items {
129                    let s: &str = item.as_ref();
130                    let text_node = html.text(s)?;
131                    let text = if let Some(style) = &htmlstyle {
132                        html.span([att("style", style)], text_node)?
133                    } else {
134                        text_node
135                    };
136                    let content = if let Some(url) = item.perhaps_url() {
137                        html.a([att("href", url)], text)?
138                    } else {
139                        text
140                    };
141                    let content = if let Some(name) = item.perhaps_anchor_name() {
142                        anchor(name, content, html)?
143                    } else {
144                        content
145                    };
146                    cells.push(html.td([], content)?)?;
147                }
148            }
149        }
150        self.table_body.push(html.tr([], cells)?)
151    }
152
153    /// You might want to give an OutputStyle with a font_size that is
154    /// larger (otherwise it is the default which is the same as the
155    /// body text?).
156    fn write_title_row(
157        &mut self,
158        titles: &[OutputTableTitle],
159        line_style: Option<OutputStyle>,
160    ) -> anyhow::Result<()> {
161        let htmlstyle = line_style.map(OutputStyle::to_css_style);
162        let html = self.table_body.allocator();
163        let mut cells = html.new_vec();
164        for item in titles {
165            let OutputTableTitle {
166                text,
167                span,
168                anchor_name,
169            } = item;
170            let s: &str = text.as_ref();
171            let text_node = html.text(s)?;
172            let text = if let Some(style) = &htmlstyle {
173                html.span([att("style", style)], text_node)?
174            } else {
175                text_node
176            };
177            let content = if let Some(anchor_name) = anchor_name {
178                anchor(anchor_name, text, html)?
179            } else {
180                text
181            };
182            cells.push(html.th([att("colspan", *span)], content)?)?;
183        }
184        self.table_body.push(html.tr([], cells)?)
185    }
186
187    fn write_bar(&mut self, bar_kind: BarKind, anchor_name: Option<&str>) -> anyhow::Result<()> {
188        let html = self.table_body.allocator();
189
190        let style = match bar_kind {
191            BarKind::Thin => "width: 100%; border-style: dashed;",
192            BarKind::Thick => "width: 100%;",
193        };
194
195        let hr = html.hr([att("style", style)], [])?;
196        let content = if let Some(anchor_name) = anchor_name {
197            anchor(anchor_name, hr, html)?
198        } else {
199            hr
200        };
201
202        self.table_body
203            .push(html.tr([], html.td([att("colspan", self.num_columns())], content)?)?)
204    }
205
206    fn print<'url, V: CellValue<'url>>(&mut self, value: V) -> anyhow::Result<()> {
207        let html = self.table_body.allocator();
208        let soft_pre = SoftPre {
209            tabs_to_nbsp: Some(8),
210            autolink: true,
211            input_line_separator: "\n",
212            trailing_br: false,
213        };
214        let text = soft_pre.format(value.as_ref(), html)?;
215        let contents = if let Some(link) = value.perhaps_url() {
216            html.a([att("href", link)], text)?
217        } else {
218            text
219        };
220        self.table_body
221            .push(html.tr([], html.td([att("colspan", self.num_columns())], contents)?)?)
222    }
223
224    fn finish(self) -> anyhow::Result<Self::Output> {
225        Ok(self.table_body)
226    }
227}