1use chrono::DateTime;
2use comfy_table::{ContentArrangement, Table};
3
4pub struct OutputConfig {
5 pub mode: String,
6 pub fields: Option<Vec<String>>,
7 pub no_header: bool,
8 pub quiet: bool,
9}
10
11impl OutputConfig {
12 pub fn from_cli(mode: &str, fields: &Option<String>, no_header: bool, quiet: bool) -> Self {
13 Self {
14 mode: mode.to_string(),
15 fields: fields
16 .as_ref()
17 .map(|f| f.split(',').map(|s| s.trim().to_string()).collect()),
18 no_header,
19 quiet,
20 }
21 }
22
23 pub fn print_items(
24 &self,
25 items: &[serde_json::Value],
26 default_fields: &[&str],
27 id_field: &str,
28 ) {
29 if self.quiet {
30 for item in items {
31 if let Some(id) = item.get(id_field).and_then(|v| v.as_str()) {
32 println!("{}", id);
33 }
34 }
35 return;
36 }
37
38 let fields: Vec<&str> = match &self.fields {
39 Some(f) => f.iter().map(|s| s.as_str()).collect(),
40 None => default_fields.to_vec(),
41 };
42
43 match self.mode.as_str() {
44 "json" => {
45 println!("{}", serde_json::to_string_pretty(items).unwrap());
46 }
47 "json-compact" => {
48 let filtered = compact_items(items, &fields);
49 println!("{}", serde_json::to_string_pretty(&filtered).unwrap());
50 }
51 "csv" => {
52 if !self.no_header {
53 println!("{}", fields.join(","));
54 }
55 for item in items {
56 let row: Vec<String> =
57 fields.iter().map(|&f| flatten_value(item.get(f))).collect();
58 println!("{}", row.join(","));
59 }
60 }
61 _ => {
62 let mut table = Table::new();
64 table.set_content_arrangement(ContentArrangement::Dynamic);
65 if !self.no_header {
66 table.set_header(fields.iter().map(|f| f.to_string()).collect::<Vec<_>>());
67 }
68 for item in items {
69 let row: Vec<String> =
70 fields.iter().map(|&f| flatten_value(item.get(f))).collect();
71 table.add_row(row);
72 }
73 println!("{}", table);
74 }
75 }
76 }
77
78 pub fn print_single(&self, item: &serde_json::Value, default_fields: &[&str], id_field: &str) {
79 self.print_items(std::slice::from_ref(item), default_fields, id_field);
80 }
81
82 pub fn print_message(&self, message: &str) {
83 if self.mode == "json" {
84 println!("{}", serde_json::json!({ "message": message }));
85 } else {
86 println!("{}", message);
87 }
88 }
89}
90
91pub fn compact_items(items: &[serde_json::Value], fields: &[&str]) -> serde_json::Value {
94 let compacted: Vec<serde_json::Value> = items
95 .iter()
96 .map(|item| {
97 let mut obj = serde_json::Map::new();
98 for &field in fields {
99 let val = flatten_value(item.get(field));
100 obj.insert(field.to_string(), serde_json::Value::String(val));
101 }
102 serde_json::Value::Object(obj)
103 })
104 .collect();
105 serde_json::Value::Array(compacted)
106}
107
108pub fn flatten_value(value: Option<&serde_json::Value>) -> String {
109 match value {
110 None | Some(serde_json::Value::Null) => "-".to_string(),
111 Some(serde_json::Value::String(s)) => {
112 if let Ok(ms) = s.parse::<i64>() {
114 if ms > 1_000_000_000_000 && ms < 10_000_000_000_000 {
115 if let Some(dt) = DateTime::from_timestamp_millis(ms) {
116 return dt.format("%Y-%m-%d").to_string();
117 }
118 }
119 }
120 s.clone()
121 }
122 Some(serde_json::Value::Number(n)) => n.to_string(),
123 Some(serde_json::Value::Bool(b)) => b.to_string(),
124 Some(serde_json::Value::Array(arr)) => {
125 let items: Vec<String> = arr
127 .iter()
128 .map(|v| {
129 if let Some(username) = v.get("username").and_then(|u| u.as_str()) {
130 username.to_string()
131 } else if let Some(s) = v.as_str() {
132 s.to_string()
133 } else {
134 v.to_string()
135 }
136 })
137 .collect();
138 if items.is_empty() {
139 "-".to_string()
140 } else {
141 items.join(", ")
142 }
143 }
144 Some(serde_json::Value::Object(obj)) => {
145 if let Some(inner) = obj.get("status").and_then(|v| v.as_str()) {
147 inner.to_string()
148 } else if let Some(inner) = obj.get("priority").and_then(|v| v.as_str()) {
149 inner.to_string()
150 } else if let Some(name) = obj.get("name").and_then(|v| v.as_str()) {
151 name.to_string()
152 } else if let Some(username) = obj.get("username").and_then(|v| v.as_str()) {
153 username.to_string()
154 } else {
155 serde_json::to_string(&serde_json::Value::Object(obj.clone())).unwrap()
156 }
157 }
158 }
159}