alopex_cli/output/
table.rs1use std::io::Write;
6
7use comfy_table::{presets::UTF8_FULL, ContentArrangement, Table};
8use crossterm::terminal;
9
10use crate::error::Result;
11use crate::models::{Column, Row, Value};
12
13use super::formatter::Formatter;
14
15pub struct TableFormatter {
20 table: Table,
22 header_set: bool,
24}
25
26impl TableFormatter {
27 pub fn new() -> Self {
29 let mut table = Table::new();
30 table
31 .load_preset(UTF8_FULL)
32 .set_content_arrangement(ContentArrangement::Dynamic);
33 if let Ok((width, _)) = terminal::size() {
34 if width == 0 {
35 table.set_content_arrangement(ContentArrangement::Disabled);
36 }
37 }
38
39 Self {
40 table,
41 header_set: false,
42 }
43 }
44}
45
46impl Default for TableFormatter {
47 fn default() -> Self {
48 Self::new()
49 }
50}
51
52impl Formatter for TableFormatter {
53 fn write_header(&mut self, _writer: &mut dyn Write, columns: &[Column]) -> Result<()> {
54 let headers: Vec<&str> = columns.iter().map(|c| c.name.as_str()).collect();
56 self.table.set_header(headers);
57 self.header_set = true;
58 Ok(())
59 }
60
61 fn write_row(&mut self, _writer: &mut dyn Write, row: &Row) -> Result<()> {
62 let cells: Vec<String> = row.columns.iter().map(format_value_table).collect();
64 self.table.add_row(cells);
65 Ok(())
66 }
67
68 fn write_footer(&mut self, writer: &mut dyn Write) -> Result<()> {
69 if self.header_set {
71 writeln!(writer, "{}", self.table)?;
72 }
73 Ok(())
74 }
75
76 fn supports_streaming(&self) -> bool {
77 false
78 }
79}
80
81fn format_value_table(value: &Value) -> String {
83 match value {
84 Value::Null => "NULL".to_string(),
85 Value::Bool(b) => b.to_string(),
86 Value::Int(i) => i.to_string(),
87 Value::Float(f) => format!("{:.6}", f),
88 Value::Text(s) => s.clone(),
89 Value::Bytes(b) => {
90 let hex: String = b
92 .iter()
93 .take(32)
94 .map(|byte| format!("{:02x}", byte))
95 .collect();
96 if b.len() > 32 {
97 format!("{}...", hex)
98 } else {
99 hex
100 }
101 }
102 Value::Vector(v) => {
103 if v.len() <= 4 {
105 format!(
106 "[{}]",
107 v.iter()
108 .map(|x| format!("{:.4}", x))
109 .collect::<Vec<_>>()
110 .join(", ")
111 )
112 } else {
113 format!(
114 "[{}, ... ({} dims)]",
115 v.iter()
116 .take(3)
117 .map(|x| format!("{:.4}", x))
118 .collect::<Vec<_>>()
119 .join(", "),
120 v.len()
121 )
122 }
123 }
124 }
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130 use crate::models::DataType;
131
132 fn test_columns() -> Vec<Column> {
133 vec![
134 Column::new("id", DataType::Int),
135 Column::new("name", DataType::Text),
136 ]
137 }
138
139 #[test]
140 fn test_table_basic() {
141 let mut formatter = TableFormatter::new();
142 let mut output = Vec::new();
143
144 let columns = test_columns();
145 formatter.write_header(&mut output, &columns).unwrap();
146
147 let row = Row::new(vec![Value::Int(1), Value::Text("Alice".to_string())]);
148 formatter.write_row(&mut output, &row).unwrap();
149
150 formatter.write_footer(&mut output).unwrap();
151
152 let result = String::from_utf8(output).unwrap();
153 assert!(result.contains("id"));
154 assert!(result.contains("name"));
155 assert!(result.contains("1"));
156 assert!(result.contains("Alice"));
157 }
158
159 #[test]
160 fn test_table_multiple_rows() {
161 let mut formatter = TableFormatter::new();
162 let mut output = Vec::new();
163
164 let columns = test_columns();
165 formatter.write_header(&mut output, &columns).unwrap();
166
167 let row1 = Row::new(vec![Value::Int(1), Value::Text("Alice".to_string())]);
168 let row2 = Row::new(vec![Value::Int(2), Value::Text("Bob".to_string())]);
169
170 formatter.write_row(&mut output, &row1).unwrap();
171 formatter.write_row(&mut output, &row2).unwrap();
172
173 formatter.write_footer(&mut output).unwrap();
174
175 let result = String::from_utf8(output).unwrap();
176 assert!(result.contains("Alice"));
177 assert!(result.contains("Bob"));
178 }
179
180 #[test]
181 fn test_table_null_value() {
182 let mut formatter = TableFormatter::new();
183 let mut output = Vec::new();
184
185 let columns = test_columns();
186 formatter.write_header(&mut output, &columns).unwrap();
187
188 let row = Row::new(vec![Value::Int(1), Value::Null]);
189 formatter.write_row(&mut output, &row).unwrap();
190
191 formatter.write_footer(&mut output).unwrap();
192
193 let result = String::from_utf8(output).unwrap();
194 assert!(result.contains("NULL"));
195 }
196
197 #[test]
198 fn test_table_does_not_support_streaming() {
199 let formatter = TableFormatter::new();
200 assert!(!formatter.supports_streaming());
201 }
202
203 #[test]
204 fn test_format_value_vector_short() {
205 let v = Value::Vector(vec![1.0, 2.0, 3.0]);
206 let result = format_value_table(&v);
207 assert!(result.contains("["));
208 assert!(result.contains("]"));
209 }
210
211 #[test]
212 fn test_format_value_vector_long() {
213 let v = Value::Vector(vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
214 let result = format_value_table(&v);
215 assert!(result.contains("..."));
216 assert!(result.contains("6 dims"));
217 }
218}