raps_cli/output/
formatter.rs1use 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 let json_value = serde_json::to_value(data)?;
25 write_csv(json_value, writer)?;
26 }
27 OutputFormat::Plain => {
28 serde_json::to_writer_pretty(&mut *writer, data)?;
30 writeln!(writer)?;
31 }
32 OutputFormat::Table => {
33 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 if let Some(serde_json::Value::Object(map)) = items.first() {
47 let mut wtr = csv::Writer::from_writer(&mut *writer);
49
50 let headers: Vec<String> = map.keys().cloned().collect();
52 wtr.write_record(&headers)?;
53
54 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 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 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 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 let separator: Vec<String> = widths.iter().map(|&w| "\u{2500}".repeat(w)).collect();
106 writeln!(writer, "{}", separator.join("\u{2500}\u{2500}"))?;
107
108 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}