Skip to main content

raps_cli/output/
formatter.rs

1use super::OutputFormat;
2use anyhow::Result;
3use serde::Serialize;
4use std::io::Write;
5
6pub struct OutputFormatter;
7
8impl OutputFormatter {
9    pub fn print_output<T, W>(data: &T, format: OutputFormat, writer: &mut W) -> Result<()>
10    where
11        T: Serialize + ?Sized,
12        W: Write,
13    {
14        match format {
15            OutputFormat::Json => {
16                serde_json::to_writer_pretty(&mut *writer, data)?;
17                writeln!(writer)?;
18            }
19            OutputFormat::Yaml => {
20                serde_yaml::to_writer(&mut *writer, data)?;
21            }
22            OutputFormat::Csv => {
23                // Try to serialize as JSON first to get the structure for CSV
24                let json_value = serde_json::to_value(data)?;
25                write_csv(json_value, writer)?;
26            }
27            OutputFormat::Plain => {
28                // For plain text, we'll use a simple JSON-like structure without colors
29                serde_json::to_writer_pretty(&mut *writer, data)?;
30                writeln!(writer)?;
31            }
32            OutputFormat::Table => {
33                // Render a proper table from the serialized data
34                let json_value = serde_json::to_value(data)?;
35                write_table(json_value, writer)?;
36            }
37        }
38        Ok(())
39    }
40}
41
42fn write_csv<W: Write>(json_value: serde_json::Value, writer: &mut W) -> Result<()> {
43    match json_value {
44        serde_json::Value::Array(items) if !items.is_empty() => {
45            // Get headers from first item
46            if let Some(serde_json::Value::Object(map)) = items.first() {
47                // csv::Writer takes any writer. We give it a reborrow.
48                let mut wtr = csv::Writer::from_writer(&mut *writer);
49
50                // Write headers
51                let headers: Vec<String> = map.keys().cloned().collect();
52                wtr.write_record(&headers)?;
53
54                // Write each row
55                for item in items {
56                    if let serde_json::Value::Object(map) = item {
57                        let mut row = Vec::new();
58                        for header in &headers {
59                            let value = map.get(header).unwrap_or(&serde_json::Value::Null);
60                            row.push(format_value_for_csv(value));
61                        }
62                        wtr.write_record(&row)?;
63                    }
64                }
65                wtr.flush()?;
66                return Ok(());
67            }
68        }
69        _ => {
70            // For non-array data, fall back to JSON as CSV doesn't match well
71            serde_json::to_writer_pretty(&mut *writer, &json_value)?;
72            writeln!(writer)?;
73        }
74    }
75    Ok(())
76}
77
78fn write_table<W: Write>(json_value: serde_json::Value, writer: &mut W) -> Result<()> {
79    match json_value {
80        serde_json::Value::Array(items) if !items.is_empty() => {
81            if let Some(serde_json::Value::Object(first)) = items.first() {
82                let headers: Vec<String> = first.keys().cloned().collect();
83
84                // Calculate column widths (capped at 50 chars)
85                let mut widths: Vec<usize> = headers.iter().map(|h| h.len()).collect();
86                for item in &items {
87                    if let serde_json::Value::Object(map) = item {
88                        for (i, header) in headers.iter().enumerate() {
89                            let val = map.get(header).unwrap_or(&serde_json::Value::Null);
90                            let val_str = format_value_for_table(val);
91                            widths[i] = widths[i].max(val_str.len()).min(50);
92                        }
93                    }
94                }
95
96                // Header row
97                let header_row: Vec<String> = headers
98                    .iter()
99                    .enumerate()
100                    .map(|(i, h)| format!("{:<width$}", h, width = widths[i]))
101                    .collect();
102                writeln!(writer, "{}", header_row.join("  "))?;
103
104                // Separator
105                let separator: Vec<String> = widths.iter().map(|&w| "\u{2500}".repeat(w)).collect();
106                writeln!(writer, "{}", separator.join("\u{2500}\u{2500}"))?;
107
108                // Data rows
109                for item in &items {
110                    if let serde_json::Value::Object(map) = item {
111                        let row: Vec<String> = headers
112                            .iter()
113                            .enumerate()
114                            .map(|(i, header)| {
115                                let val = map.get(header).unwrap_or(&serde_json::Value::Null);
116                                let val_str = format_value_for_table(val);
117                                let truncated = if val_str.len() > 50 {
118                                    format!("{}...", &val_str[..47])
119                                } else {
120                                    val_str
121                                };
122                                format!("{:<width$}", truncated, width = widths[i])
123                            })
124                            .collect();
125                        writeln!(writer, "{}", row.join("  "))?;
126                    }
127                }
128
129                writeln!(writer, "\n{} row(s)", items.len())?;
130            } else {
131                for item in &items {
132                    writeln!(writer, "{}", format_value_for_table(item))?;
133                }
134            }
135        }
136        serde_json::Value::Object(map) => {
137            let max_key_len = map.keys().map(|k| k.len()).max().unwrap_or(0);
138            for (key, value) in &map {
139                let val_str = format_value_for_table(value);
140                writeln!(writer, "{:<width$}  {}", key, val_str, width = max_key_len)?;
141            }
142        }
143        serde_json::Value::Array(_) => {
144            writeln!(writer, "(empty)")?;
145        }
146        other => {
147            writeln!(writer, "{}", format_value_for_table(&other))?;
148        }
149    }
150    Ok(())
151}
152
153fn format_value_for_table(value: &serde_json::Value) -> String {
154    match value {
155        serde_json::Value::Null => "-".to_string(),
156        serde_json::Value::Bool(b) => b.to_string(),
157        serde_json::Value::Number(n) => n.to_string(),
158        serde_json::Value::String(s) => s.clone(),
159        serde_json::Value::Array(arr) => {
160            if arr.is_empty() {
161                "-".to_string()
162            } else {
163                arr.iter()
164                    .map(format_value_for_table)
165                    .collect::<Vec<_>>()
166                    .join(", ")
167            }
168        }
169        serde_json::Value::Object(obj) => {
170            serde_json::to_string(obj).unwrap_or_else(|_| "{}".to_string())
171        }
172    }
173}
174
175fn format_value_for_csv(value: &serde_json::Value) -> String {
176    match value {
177        serde_json::Value::Null => String::new(),
178        serde_json::Value::Bool(b) => b.to_string(),
179        serde_json::Value::Number(n) => n.to_string(),
180        serde_json::Value::String(s) => s.clone(),
181        serde_json::Value::Array(arr) => arr
182            .iter()
183            .map(format_value_for_csv)
184            .collect::<Vec<_>>()
185            .join("; "),
186        serde_json::Value::Object(obj) => serde_json::to_string(obj).unwrap_or_default(),
187    }
188}