lutra_bin/table/
layout.rs1use bytes::Buf;
4
5use crate::ArrayReader;
6use crate::ir;
7use crate::tabular::TableCell;
8
9use super::format::{format_ty_name, format_value, truncate};
10use super::{Config, Table};
11
12#[derive(Debug, Clone)]
14pub struct Layout {
15 pub names_height: usize,
17 pub column_groups: Vec<ColumnGroup>,
19 pub columns: Vec<Column>,
21
22 pub col_index_width: usize,
24 pub col_widths: Vec<usize>,
26 pub row_heights: Vec<usize>,
28 pub total_rows: usize,
30}
31
32#[derive(Debug, Clone)]
34pub struct Column {
35 pub name: String,
37 pub ty_name: String,
39 pub align: Align,
41}
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub enum Align {
46 Left,
47 Right,
48}
49
50#[derive(Debug, Clone)]
52pub struct ColumnGroup {
53 pub name: String,
55 pub children: Vec<ColumnGroup>,
57}
58
59impl ColumnGroup {
60 pub fn leaf_count(&self) -> usize {
62 if self.children.is_empty() {
63 1
64 } else {
65 self.children.iter().map(|c| c.leaf_count()).sum()
66 }
67 }
68
69 pub fn depth(&self) -> usize {
71 if self.children.is_empty() {
72 1
73 } else {
74 1 + self.children.iter().map(|c| c.depth()).max().unwrap_or(0)
75 }
76 }
77}
78
79impl<'d, 't> Table<'d, 't> {
80 pub fn compute_layout(mut self, config: &Config) -> Layout {
82 let column_groups = self.build_column_groups(self.row_ty());
83 let columns = self.flatten_columns(&column_groups, self.row_ty());
84
85 let names_height = max_column_depth(&column_groups);
86
87 let total_rows = self.remaining();
89 let col_index_width = total_rows.saturating_sub(1).to_string().len();
90
91 let mut col_widths: Vec<usize> = columns
93 .iter()
94 .map(|c| {
95 let name_width = c.name.chars().count();
96 let ty_width = c.ty_name.chars().count();
97 name_width.max(ty_width)
98 })
99 .collect();
100
101 let mut row_heights = Vec::new();
102 let mut rows_scanned = 0;
103
104 while let Some(row) = self.next() {
105 let mut row_height = 1usize;
106 for (i, cell) in row.iter().enumerate() {
107 let (width, height) = self.measure_cell(cell, config);
108 if i < col_widths.len() {
109 col_widths[i] = col_widths[i].max(width);
110 }
111 row_height = row_height.max(height);
112 }
113 row_heights.push(row_height);
114
115 rows_scanned += 1;
116 if let Some(l) = config.sample_rows
117 && rows_scanned >= l
118 {
119 break;
120 }
121 }
122
123 Layout {
124 names_height,
125 column_groups,
126 columns,
127 col_widths,
128 col_index_width,
129 row_heights,
130 total_rows,
131 }
132 }
133
134 fn build_column_groups(&self, ty: &'t ir::Ty) -> Vec<ColumnGroup> {
135 let ty = self.get_ty_mat(ty);
136 match &ty.kind {
137 ir::TyKind::Tuple(fields) => fields
138 .iter()
139 .enumerate()
140 .map(|(i, f)| {
141 let name = f.name.clone().unwrap_or_else(|| i.to_string());
142 let children = self.build_column_groups(&f.ty);
143 ColumnGroup { name, children }
144 })
145 .collect(),
146 _ => vec![],
147 }
148 }
149
150 fn flatten_columns(&self, groups: &[ColumnGroup], ty: &'t ir::Ty) -> Vec<Column> {
151 let ty = self.get_ty_mat(ty);
152 match &ty.kind {
153 ir::TyKind::Tuple(fields) => {
154 let mut leaves = Vec::new();
155 for (group, field) in groups.iter().zip(fields.iter()) {
156 if group.children.is_empty() {
157 leaves.push(Column {
158 name: group.name.clone(),
159 ty_name: format_ty_name(&field.ty, self),
160 align: self.infer_align(&field.ty),
161 });
162 } else {
163 leaves.extend(self.flatten_columns(&group.children, &field.ty));
164 }
165 }
166 leaves
167 }
168 _ => {
169 vec![Column {
171 name: "value".into(),
172 ty_name: format_ty_name(ty, self),
173 align: self.infer_align(ty),
174 }]
175 }
176 }
177 }
178
179 fn infer_align(&self, ty: &ir::Ty) -> Align {
181 let ty = self.get_ty_mat(ty);
182 match &ty.kind {
183 ir::TyKind::Primitive(p) => match p {
184 ir::TyPrimitive::bool | ir::TyPrimitive::text => Align::Left,
185 _ => Align::Right, },
187 ir::TyKind::Enum(_) => Align::Left,
188 ir::TyKind::Array(_) => Align::Left,
189 ir::TyKind::Tuple(_) => Align::Left,
190 ir::TyKind::Function(_) => Align::Left,
191 ir::TyKind::Ident(_) => unreachable!("should be resolved"),
192 }
193 }
194
195 fn measure_cell(&self, cell: &TableCell, config: &Config) -> (usize, usize) {
197 let ty = self.get_ty_mat(cell.ty());
198
199 match &ty.kind {
200 ir::TyKind::Array(item_ty) if self.is_flat(item_ty) => {
201 let reader = ArrayReader::new_for_ty(cell.data(), ty);
203 let count = reader.remaining();
204
205 let height = if count == 0 {
206 1
207 } else if count <= config.max_array_items {
208 count
209 } else {
210 config.max_array_items };
212
213 let mut max_width = 0usize;
215 let items_to_measure = count.min(config.max_array_items);
216 for item_data in reader.take(items_to_measure) {
217 if let Ok((text, _)) = format_value(item_data.chunk(), item_ty, self) {
218 let text = truncate(&text, config.max_col_width);
219 max_width = max_width.max(text.chars().count());
220 }
221 }
222
223 if count > config.max_array_items {
225 let ellipsis_text = format!("… {} more", count - (config.max_array_items - 1));
226 max_width = max_width.max(ellipsis_text.chars().count());
227 }
228
229 (max_width.min(config.max_col_width), height)
230 }
231 ir::TyKind::Array(_) => {
232 (3, 1)
234 }
235 _ => {
236 if let Ok((text, _)) = format_value(cell.data(), cell.ty(), self) {
238 let text = truncate(&text, config.max_col_width);
239 (text.chars().count(), 1)
240 } else {
241 (3, 1) }
243 }
244 }
245 }
246}
247
248fn max_column_depth(column_groups: &[ColumnGroup]) -> usize {
249 column_groups.iter().map(|c| c.depth()).max().unwrap_or(1)
250}