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