1#![allow(clippy::unwrap_used)]
9
10use crate::error::Result;
11use crate::types::{DxArray, DxObject, DxTable, DxValue};
12use std::fmt::Write as FmtWrite;
13
14#[derive(Debug, Clone)]
16pub struct FormatterConfig {
17 pub column_padding: usize,
19 pub use_unicode: bool,
21 pub add_dividers: bool,
23 pub use_colors: bool,
25}
26
27impl Default for FormatterConfig {
28 fn default() -> Self {
29 Self {
30 column_padding: 4,
31 use_unicode: true,
32 add_dividers: true,
33 use_colors: false,
34 }
35 }
36}
37
38pub struct HumanFormatter {
40 config: FormatterConfig,
41 output: String,
42 indent: usize,
43}
44
45impl HumanFormatter {
46 pub fn new(config: FormatterConfig) -> Self {
48 Self {
49 config,
50 output: String::new(),
51 indent: 0,
52 }
53 }
54
55 pub fn with_defaults() -> Self {
57 Self::new(FormatterConfig::default())
58 }
59
60 pub fn format(&mut self, value: &DxValue) -> Result<String> {
62 self.output.clear();
63 self.indent = 0;
64
65 if self.config.add_dividers {
66 self.write_header();
67 }
68
69 self.format_value(value)?;
70
71 Ok(self.output.clone())
72 }
73
74 fn write_header(&mut self) {
76 let line = "─".repeat(70);
77 writeln!(self.output, "┌{}┐", line).unwrap();
78 writeln!(
79 self.output,
80 "│ DX HUMAN VIEW • Enhanced Readability Mode{}│",
81 " ".repeat(31)
82 )
83 .unwrap();
84 writeln!(self.output, "└{}┘", line).unwrap();
85 writeln!(self.output).unwrap();
86 }
87
88 fn format_value(&mut self, value: &DxValue) -> Result<()> {
90 match value {
91 DxValue::Object(obj) => self.format_object(obj),
92 DxValue::Table(table) => self.format_table(table),
93 DxValue::Array(arr) => self.format_array(arr),
94 _ => {
95 self.write_indent();
96 self.write_simple_value(value)?;
97 writeln!(self.output).unwrap();
98 Ok(())
99 }
100 }
101 }
102
103 fn format_object(&mut self, obj: &DxObject) -> Result<()> {
105 let max_key_len = obj.iter().map(|(k, _)| k.len()).max().unwrap_or(0);
107
108 for (key, value) in obj.iter() {
109 self.write_indent();
110
111 write!(self.output, "{}", key).unwrap();
113 let padding = max_key_len - key.len() + self.config.column_padding;
114 write!(self.output, "{}", " ".repeat(padding)).unwrap();
115
116 match value {
117 DxValue::Table(table) => {
118 writeln!(self.output).unwrap();
119 if self.config.add_dividers {
120 self.write_indent();
121 writeln!(self.output, "┌─ {} TABLE ─┐", key.to_uppercase()).unwrap();
122 }
123 self.indent += 1;
124 self.format_table(table)?;
125 self.indent -= 1;
126 if self.config.add_dividers {
127 self.write_indent();
128 writeln!(self.output, "└{}┘", "─".repeat(15)).unwrap();
129 }
130 }
131 DxValue::Array(arr) if arr.is_stream => {
132 write!(self.output, "> ").unwrap();
133 for (i, val) in arr.values.iter().enumerate() {
134 if i > 0 {
135 write!(self.output, " | ").unwrap();
136 }
137 self.write_simple_value(val)?;
138 }
139 writeln!(self.output).unwrap();
140 }
141 DxValue::Object(nested) => {
142 writeln!(self.output).unwrap();
143 self.indent += 1;
144 self.format_object(nested)?;
145 self.indent -= 1;
146 }
147 _ => {
148 write!(self.output, ": ").unwrap();
149 self.write_simple_value(value)?;
150 writeln!(self.output).unwrap();
151 }
152 }
153 }
154
155 Ok(())
156 }
157
158 fn format_table(&mut self, table: &DxTable) -> Result<()> {
160 if table.rows.is_empty() {
161 self.write_indent();
162 writeln!(self.output, "(empty table)").unwrap();
163 return Ok(());
164 }
165
166 let mut col_widths: Vec<usize> =
168 table.schema.columns.iter().map(|c| c.name.len()).collect();
169
170 for row in &table.rows {
171 for (i, value) in row.iter().enumerate() {
172 let val_str = self.value_to_string(value);
173 col_widths[i] = col_widths[i].max(val_str.len());
174 }
175 }
176
177 self.write_indent();
179 for (i, col) in table.schema.columns.iter().enumerate() {
180 if i > 0 {
181 write!(self.output, " ").unwrap();
182 }
183 write!(self.output, "{:width$}", col.name, width = col_widths[i]).unwrap();
184 }
185 writeln!(self.output).unwrap();
186
187 self.write_indent();
189 for (i, width) in col_widths.iter().enumerate() {
190 if i > 0 {
191 write!(self.output, " ").unwrap();
192 }
193 write!(self.output, "{}", "─".repeat(*width)).unwrap();
194 }
195 writeln!(self.output).unwrap();
196
197 for row in &table.rows {
199 self.write_indent();
200 for (i, value) in row.iter().enumerate() {
201 if i > 0 {
202 write!(self.output, " ").unwrap();
203 }
204 let val_str = self.value_to_string(value);
205 write!(self.output, "{:width$}", val_str, width = col_widths[i]).unwrap();
206 }
207 writeln!(self.output).unwrap();
208 }
209
210 Ok(())
211 }
212
213 fn format_array(&mut self, arr: &DxArray) -> Result<()> {
215 if arr.is_stream {
216 write!(self.output, "> ").unwrap();
217 for (i, val) in arr.values.iter().enumerate() {
218 if i > 0 {
219 write!(self.output, " | ").unwrap();
220 }
221 self.write_simple_value(val)?;
222 }
223 writeln!(self.output).unwrap();
224 } else {
225 for (i, val) in arr.values.iter().enumerate() {
226 self.write_indent();
227 write!(self.output, "{}. ", i + 1).unwrap();
228 self.write_simple_value(val)?;
229 writeln!(self.output).unwrap();
230 }
231 }
232 Ok(())
233 }
234
235 fn write_simple_value(&mut self, value: &DxValue) -> Result<()> {
237 let s = match value {
238 DxValue::Null => "null".to_string(),
239 DxValue::Bool(true) => {
240 if self.config.use_unicode {
241 "✓".to_string()
242 } else {
243 "+".to_string()
244 }
245 }
246 DxValue::Bool(false) => {
247 if self.config.use_unicode {
248 "✗".to_string()
249 } else {
250 "-".to_string()
251 }
252 }
253 DxValue::Int(i) => i.to_string(),
254 DxValue::Float(f) => format!("{:.2}", f),
255 DxValue::String(s) => s.clone(),
256 DxValue::Ref(id) => format!("@{}", id),
257 _ => format!("{:?}", value),
258 };
259
260 write!(self.output, "{}", s).unwrap();
261 Ok(())
262 }
263
264 fn value_to_string(&self, value: &DxValue) -> String {
266 match value {
267 DxValue::Null => "null".to_string(),
268 DxValue::Bool(true) => {
269 if self.config.use_unicode {
270 "✓".to_string()
271 } else {
272 "+".to_string()
273 }
274 }
275 DxValue::Bool(false) => {
276 if self.config.use_unicode {
277 "✗".to_string()
278 } else {
279 "-".to_string()
280 }
281 }
282 DxValue::Int(i) => i.to_string(),
283 DxValue::Float(f) => format!("{:.2}", f),
284 DxValue::String(s) => s.clone(),
285 DxValue::Ref(id) => format!("@{}", id),
286 _ => format!("{:?}", value),
287 }
288 }
289
290 fn write_indent(&mut self) {
292 write!(self.output, "{}", " ".repeat(self.indent)).unwrap();
293 }
294}
295
296#[must_use = "formatting result should be used"]
298pub fn format_human(value: &DxValue) -> Result<String> {
299 let mut formatter = HumanFormatter::with_defaults();
300 formatter.format(value)
301}
302
303#[must_use = "formatting result should be used"]
305pub fn format_human_with_config(value: &DxValue, config: FormatterConfig) -> Result<String> {
306 let mut formatter = HumanFormatter::new(config);
307 formatter.format(value)
308}
309
310#[cfg(test)]
311mod tests {
312 use super::*;
313 use crate::parser::parse;
314
315 #[test]
316 fn test_format_simple() {
317 let input = b"name:Alice
318age:30
319active:+";
320
321 let value = parse(input).unwrap();
322 let formatted = format_human(&value).unwrap();
323
324 assert!(formatted.contains("Alice"));
325 assert!(formatted.contains("30"));
326 assert!(formatted.contains("✓"));
327 }
328
329 #[test]
330 fn test_format_table() {
331 let input = b"users=id%i name%s active%b
3321 Alice +
3332 Bob -";
334
335 let value = parse(input).unwrap();
336 let formatted = format_human(&value).unwrap();
337
338 assert!(formatted.contains("USERS TABLE"));
339 assert!(formatted.contains("Alice"));
340 assert!(formatted.contains("Bob"));
341 assert!(formatted.contains("✓"));
342 assert!(formatted.contains("✗"));
343 }
344}