evobench_tools/stats_tables/tables/
excel_table_view.rs1use std::{borrow::Cow, path::Path};
4
5use anyhow::{Context, Result, anyhow};
6use cj_path_util::unix::polyfill::add_extension;
7use rust_xlsxwriter::{Color, Format, FormatAlign, workbook::Workbook};
8
9use super::table_view::{ColumnFormatting, Highlight, TableView, Unit};
10use crate::io_utils::div::xrename;
11
12const WIDTH_SAFETY_MARGIN_CHARS: f64 = 2.0;
16
17pub fn excel_file_write<'t>(
18 tables: impl IntoIterator<Item = &'t (dyn TableView + 't)>,
19 file: &Path,
20) -> Result<()> {
21 let mut workbook = Workbook::new();
22
23 for table in tables {
24 let worksheet = workbook.add_worksheet();
25 worksheet.set_name(table.table_name()).with_context(|| {
26 anyhow!(
27 "trying to use table name as worksheet name: {:?}",
28 table.table_name()
29 )
30 })?;
31
32 let _titles = table.table_view_header();
33 let titles = (*_titles).as_ref();
34
35 let mut column_widths: Vec<usize> = titles.iter().map(|_| 1).collect();
37
38 let mut rownum = 0;
39
40 {
41 let mut num_lines = 1;
43 for (i, (label, unit, _column_formatting)) in titles.iter().enumerate() {
44 {
46 let colnum =
47 u16::try_from(i).with_context(|| anyhow!("too many columns for excel"))?;
48 let perhaps_unit: Cow<str> = match unit {
49 Unit::None => "".into(),
50 Unit::DimensionLess => "".into(),
51 Unit::Count => "\n(count)".into(),
52 Unit::ViewType(unit) => format!("\n({unit})").into(),
53 };
54 let val = format!("{label}{perhaps_unit}");
55 {
56 let max_width = val
57 .split('\n')
58 .map(|s| s.chars().count())
59 .max()
60 .unwrap_or(0);
61 column_widths[i] = column_widths[i].max(max_width);
62 }
63 let format = Format::new().set_bold();
64 worksheet
65 .write_with_format(rownum, colnum, &val, &format)
66 .with_context(|| anyhow!("write title value {val:?}"))?;
67 }
68
69 {
71 let label_linebreaks = label.chars().filter(|c| *c == '\n').count();
72 let unit_lines = match unit {
73 Unit::None => 0,
74 Unit::DimensionLess => 0,
75 Unit::Count => 1,
76 Unit::ViewType(_) => 1,
77 };
78 num_lines = num_lines.max(label_linebreaks + 1 + unit_lines);
79 }
80 }
81
82 let height = (num_lines * 15) as f64;
83 worksheet
84 .set_row_height(rownum, height)
85 .with_context(|| anyhow!("setting height of row {rownum} to height {height}"))?;
86 }
87
88 for row in table.table_view_body() {
89 rownum += 1;
90 for (i, (val, highlight)) in row.iter().enumerate() {
91 let column_formatting: ColumnFormatting = titles[i].2;
92 let colnum =
93 u16::try_from(i).with_context(|| anyhow!("too many columns for excel"))?;
94
95 let mut format = Format::new();
96 match column_formatting {
97 ColumnFormatting::Spacer => (),
98 ColumnFormatting::Number => {
99 format = format.set_align(FormatAlign::Right);
100 }
101 ColumnFormatting::String { width_chars: _ } => (),
102 }
103 match highlight {
104 Highlight::Spacer => (),
105 Highlight::Neutral => (),
106 Highlight::Red => {
107 format = format.set_font_color(Color::Red);
108 }
109 Highlight::Green => {
110 format = format.set_background_color(Color::Green);
111 }
112 }
113
114 {
115 let max_width = val
116 .split('\n')
117 .map(|s| s.chars().count())
118 .max()
119 .unwrap_or(0);
120 column_widths[i] = column_widths[i].max(max_width);
121 }
122
123 worksheet
124 .write_with_format(rownum, colnum, val.as_ref(), &format)
125 .with_context(|| anyhow!("write value {val:?}"))?;
126 }
127 }
128
129 {
131 for (i, num_chars) in column_widths.iter().enumerate() {
140 let colnum =
141 u16::try_from(i).with_context(|| anyhow!("too many columns for excel"))?;
142 let width = *num_chars as f64 + WIDTH_SAFETY_MARGIN_CHARS;
143 worksheet.set_column_width(colnum, width).with_context(|| {
144 anyhow!("setting column width on column {colnum} to {width}")
145 })?;
146 }
147
148 for (i, (_label, _unit, column_formatting)) in titles.as_ref().iter().enumerate() {
149 let colnum =
150 u16::try_from(i).with_context(|| anyhow!("too many columns for excel"))?;
151
152 match column_formatting {
153 ColumnFormatting::Spacer => {
154 worksheet
155 .set_column_width(colnum, 3.0)
156 .with_context(|| anyhow!("setting column width on column {colnum}"))?;
157 }
158 ColumnFormatting::Number => {
159 }
166 ColumnFormatting::String { width_chars } => {
167 if let Some(width_chars) = width_chars {
168 worksheet
169 .set_column_width(colnum, *width_chars)
170 .with_context(|| {
171 anyhow!("setting column width on column {colnum}")
172 })?;
173 }
174 }
175 }
176 }
177 }
178 }
179
180 let file_tmp =
181 add_extension(file, "tmp").ok_or_else(|| anyhow!("path misses a filename: {file:?}"))?;
182 workbook
183 .save(&file_tmp)
184 .with_context(|| anyhow!("saving to file {file_tmp:?}"))?;
185 xrename(&file_tmp, &file)?;
186
187 Ok(())
188}