1use rich::table::Cell;
4use rich::{Console, ConsoleOptions, Renderable, Segment, Table, Text};
5
6use super::group::{Aggregate, GroupBy};
7use super::rules::{ResolvedRules, StyleRules};
8use super::sort::{SortKey, sorted_indices};
9use super::{Column, Frame, Value, frame_builders, headers, style};
10
11#[derive(Clone, Debug)]
57#[must_use = "builder methods return a new value; it does nothing until it is rendered or used"]
58pub struct TableData {
59 columns: Vec<Column>,
60 rows: Vec<Vec<Value>>,
61 sort: Vec<SortKey>,
62 group: Option<GroupBy>,
63 totals: Vec<Aggregate>,
64 totals_label: String,
65 rules: StyleRules,
66 pub(super) frame: Frame,
67}
68
69frame_builders!([] TableData);
70
71impl TableData {
72 pub fn new(columns: impl IntoIterator<Item = Column>) -> Self {
74 TableData {
75 columns: columns.into_iter().collect(),
76 rows: Vec::new(),
77 sort: Vec::new(),
78 group: None,
79 totals: Vec::new(),
80 totals_label: "total".to_string(),
81 rules: StyleRules::new(),
82 frame: Frame::default(),
83 }
84 }
85
86 pub fn push(&mut self, row: impl IntoIterator<Item = Value>) -> &mut Self {
88 let row = normalize(row, self.columns.len());
89 self.rows.push(row);
90 self
91 }
92
93 pub fn extend<R: IntoIterator<Item = Value>>(
95 &mut self,
96 rows: impl IntoIterator<Item = R>,
97 ) -> &mut Self {
98 for row in rows {
99 self.push(row);
100 }
101 self
102 }
103
104 pub fn sort_by(mut self, keys: impl IntoIterator<Item = SortKey>) -> Self {
106 self.set_sort(keys);
107 self
108 }
109
110 pub fn set_sort(&mut self, keys: impl IntoIterator<Item = SortKey>) {
112 self.sort = keys.into_iter().collect();
113 }
114
115 pub fn group_by(mut self, group: GroupBy) -> Self {
117 self.group = Some(group);
118 self
119 }
120
121 pub fn totals(
125 mut self,
126 label: impl Into<String>,
127 aggregates: impl IntoIterator<Item = Aggregate>,
128 ) -> Self {
129 self.totals_label = label.into();
130 self.totals = aggregates.into_iter().collect();
131 self
132 }
133
134 pub fn style_rules(mut self, rules: StyleRules) -> Self {
138 self.rules = rules;
139 self
140 }
141
142 pub fn columns(&self) -> &[Column] {
144 &self.columns
145 }
146
147 pub fn rows(&self) -> &[Vec<Value>] {
149 &self.rows
150 }
151
152 pub fn sort_keys(&self) -> &[SortKey] {
154 &self.sort
155 }
156
157 pub fn order(&self) -> Vec<usize> {
159 sorted_indices(&self.rows, &self.sort)
160 }
161
162 pub fn to_table(&self, console: &Console) -> Table {
165 let headers = headers(console, &self.columns, &self.sort);
166 let mut table = self.frame.table(&self.columns, &headers, true, true);
167 let order = self.order();
168 let headers: Vec<&str> = self.columns.iter().map(Column::header).collect();
169 let rules = (!self.rules.is_empty()).then(|| self.rules.resolve(&headers, &self.rows));
170 let add = |table: &mut Table, row: &[Value]| match &rules {
171 None => {
172 table.add_row_text(self.cells(row));
173 }
174 Some(rules) => self.add_styled(table, rules, row),
175 };
176 match &self.group {
177 None => {
178 for &row in &order {
179 add(&mut table, &self.rows[row]);
180 }
181 }
182 Some(group) => {
183 let group_style = style(console, "table.group");
184 for g in group.groups(&self.rows, &order) {
185 let mut label = if g.key.is_empty() {
186 Text::new("(empty)")
187 } else {
188 self.columns
189 .get(group.column())
190 .map_or_else(|| g.key.to_text(), |c| c.cell(&g.key))
191 };
192 if group.column() != 0
193 && let Some(column) = self.columns.get(group.column())
194 {
195 label = Text::new(format!("{}: ", column.header())).append_text(&label);
196 }
197 let len = label.plain().len();
198 label.stylize(group_style.clone(), 0, len);
199 let mut header = vec![Text::new(""); self.columns.len()];
200 if let Some(first) = header.first_mut() {
201 *first = label;
202 }
203 table.add_row_text(header);
204 for &row in &g.rows {
205 add(&mut table, &self.rows[row]);
206 }
207 if !group.aggregates().is_empty() {
208 table.add_row_text(self.summary(
209 console,
210 group.summary_label(),
211 group.aggregates(),
212 &g.aggregates,
213 ));
214 }
215 }
216 }
217 }
218 if !self.totals.is_empty() {
219 let values: Vec<Value> = self
220 .totals
221 .iter()
222 .map(|aggregate| aggregate.over(&self.rows, &order))
223 .collect();
224 table.add_row_text(self.summary(console, &self.totals_label, &self.totals, &values));
225 }
226 table
227 }
228
229 fn cells(&self, row: &[Value]) -> Vec<Text> {
230 self.columns
231 .iter()
232 .zip(row)
233 .map(|(column, value)| column.cell(value))
234 .collect()
235 }
236
237 fn add_styled(&self, table: &mut Table, rules: &ResolvedRules<'_>, row: &[Value]) {
240 let cells = self
241 .cells(row)
242 .into_iter()
243 .enumerate()
244 .map(|(index, mut text)| {
245 if let Some(style) = rules.cell_style(row, index) {
246 let len = text.plain().len();
247 text.stylize(style, 0, len);
248 }
249 Cell::Text(text)
250 })
251 .collect();
252 table.add_row_with(cells, rules.row_style(row).map(Into::into), false);
253 }
254
255 fn summary(
259 &self,
260 console: &Console,
261 label: &str,
262 aggregates: &[Aggregate],
263 values: &[Value],
264 ) -> Vec<Text> {
265 let mut cells: Vec<Option<Text>> = vec![None; self.columns.len()];
266 for (aggregate, value) in aggregates.iter().zip(values) {
267 let Some(column) = self.columns.get(aggregate.column()) else {
268 continue;
269 };
270 let text = if aggregate.is_count() {
271 value.to_text()
272 } else {
273 column.cell(value)
274 };
275 let cell = &mut cells[aggregate.column()];
276 *cell = Some(match cell.take() {
277 Some(previous) => previous.append_text(&Text::new(", ")).append_text(&text),
278 None => text,
279 });
280 }
281 if let Some(first) = cells.first_mut() {
282 *first = match first.take() {
283 None => Some(Text::new(label)),
284 Some(value) if label.is_empty() => Some(value),
285 Some(value) => Some(Text::new(format!("{label}: ")).append_text(&value)),
286 };
287 }
288 let aggregate_style = style(console, "table.aggregate");
289 cells
290 .into_iter()
291 .map(|cell| {
292 let mut text = cell.unwrap_or_else(|| Text::new(""));
293 let len = text.plain().len();
294 text.stylize(aggregate_style.clone(), 0, len);
295 text
296 })
297 .collect()
298 }
299}
300
301pub(crate) fn normalize(row: impl IntoIterator<Item = Value>, columns: usize) -> Vec<Value> {
303 let mut row: Vec<Value> = row.into_iter().take(columns).collect();
304 row.resize(columns, Value::Null);
305 row
306}
307
308impl Renderable for TableData {
309 fn rich_render(&self, console: &Console, options: &ConsoleOptions) -> Vec<Segment> {
310 self.to_table(console).rich_render(console, options)
311 }
312
313 fn measure(&self, console: &Console, options: &ConsoleOptions) -> rich::measure::Measurement {
314 self.to_table(console).measure(console, options)
315 }
316}
317
318impl crate::a11y::AccessibleText for TableData {
319 fn accessible_text(&self, width: usize) -> String {
320 let console = Console::builder().width(width.max(1)).build();
321 self.to_table(&console).accessible_text(width)
322 }
323}