journal_engine/logs/
table.rs1use super::query::LogEntryData;
2use journal_core::Result;
3use std::collections::HashMap;
4use std::fmt;
5
6#[derive(Debug, Clone)]
8pub struct CellValue {
9 pub raw: Option<String>,
10 pub display: Option<String>,
11}
12
13impl CellValue {
14 pub fn new(value: Option<String>) -> Self {
16 Self {
17 raw: value.clone(),
18 display: value,
19 }
20 }
21
22 pub fn with_display(raw: Option<String>, display: Option<String>) -> Self {
24 Self { raw, display }
25 }
26}
27
28#[derive(Debug, Clone)]
30pub struct ColumnInfo {
31 pub name: String,
32 pub index: usize,
33}
34
35impl ColumnInfo {
36 pub fn new(name: String, index: usize) -> Self {
37 Self { name, index }
38 }
39}
40
41#[derive(Debug, Clone)]
43pub struct Table {
44 pub columns: Vec<ColumnInfo>,
45 pub data: Vec<Vec<CellValue>>,
46}
47
48impl Table {
49 pub fn new(column_names: Vec<String>) -> Self {
51 let columns = column_names
52 .into_iter()
53 .enumerate()
54 .map(|(index, name)| ColumnInfo::new(name, index))
55 .collect();
56
57 Self {
58 columns,
59 data: Vec::new(),
60 }
61 }
62
63 pub fn add_row(&mut self, row: Vec<CellValue>) {
65 self.data.push(row);
66 }
67
68 pub fn row_count(&self) -> usize {
70 self.data.len()
71 }
72
73 pub fn column_count(&self) -> usize {
75 self.columns.len()
76 }
77
78 pub fn columns(&self) -> &[ColumnInfo] {
80 &self.columns
81 }
82
83 pub fn rows(&self) -> &[Vec<CellValue>] {
85 &self.data
86 }
87
88 fn calculate_column_widths(&self) -> Vec<usize> {
90 const MESSAGE_MAX_WIDTH: usize = 80;
91
92 let mut widths: Vec<usize> = self.columns.iter().map(|col| col.name.len()).collect();
93
94 for row in &self.data {
96 for (col_idx, cell) in row.iter().enumerate() {
97 let display_len = cell.display.as_deref().unwrap_or("-").len();
98 if display_len > widths[col_idx] {
99 widths[col_idx] = display_len;
100 }
101 }
102 }
103
104 for (col_idx, col) in self.columns.iter().enumerate() {
106 if col.name == "MESSAGE" && widths[col_idx] > MESSAGE_MAX_WIDTH {
107 widths[col_idx] = MESSAGE_MAX_WIDTH;
108 }
109 }
110
111 widths
112 }
113}
114
115impl fmt::Display for Table {
116 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
117 if self.columns.is_empty() {
118 return writeln!(f, "(empty table)");
119 }
120
121 let widths = self.calculate_column_widths();
122 write_table_border(f, &widths)?;
123 write_table_header(f, &self.columns, &widths)?;
124 write_table_border(f, &widths)?;
125 write_table_rows(f, &self.data, &widths)?;
126 write_table_border(f, &widths)?;
127 Ok(())
128 }
129}
130
131fn table_total_width(widths: &[usize]) -> usize {
132 widths.iter().sum::<usize>() + (widths.len() - 1) * 3 + 2
133}
134
135fn write_table_border(f: &mut fmt::Formatter<'_>, widths: &[usize]) -> fmt::Result {
136 writeln!(f, "{}", "=".repeat(table_total_width(widths)))
137}
138
139fn write_table_header(
140 f: &mut fmt::Formatter<'_>,
141 columns: &[ColumnInfo],
142 widths: &[usize],
143) -> fmt::Result {
144 write!(f, "|")?;
145 for (col, width) in columns.iter().zip(widths) {
146 write!(f, " {:<width$} |", col.name, width = width)?;
147 }
148 writeln!(f)
149}
150
151fn write_table_rows(
152 f: &mut fmt::Formatter<'_>,
153 rows: &[Vec<CellValue>],
154 widths: &[usize],
155) -> fmt::Result {
156 for row in rows {
157 write!(f, "|")?;
158 for (cell, width) in row.iter().zip(widths) {
159 write_table_cell(f, cell, *width)?;
160 }
161 writeln!(f)?;
162 }
163 Ok(())
164}
165
166fn write_table_cell(f: &mut fmt::Formatter<'_>, cell: &CellValue, width: usize) -> fmt::Result {
167 let display = cell.display.as_deref().unwrap_or("-");
168 if display.len() > width {
169 write!(f, " {:<width$} |", &display[..width], width = width)
170 } else {
171 write!(f, " {:<width$} |", display, width = width)
172 }
173}
174
175pub fn entry_data_to_table(
189 entry_data: &[LogEntryData],
190 column_names: Vec<String>,
191) -> Result<Table> {
192 let mut all_columns = vec!["timestamp".to_string()];
194 all_columns.extend(column_names.clone());
195
196 let mut table = Table::new(all_columns);
197
198 let column_map: HashMap<&str, usize> = column_names
200 .iter()
201 .enumerate()
202 .map(|(idx, name)| (name.as_str(), idx + 1)) .collect();
204
205 for data in entry_data {
207 let num_cols = column_names.len() + 1;
208 let mut row = vec![CellValue::new(None); num_cols];
209
210 row[0] = CellValue::new(Some(data.timestamp.to_string()));
212
213 for pair in &data.fields {
215 if let Some(&col_idx) = column_map.get(pair.field()) {
216 row[col_idx] = CellValue::new(Some(pair.value().to_string()));
217 }
218 }
219
220 table.add_row(row);
221 }
222
223 Ok(table)
224}