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: Vec<serde_json::Value> = items
42 .iter()
43 .map(|item| {
44 let mut obj = serde_json::Map::new();
45 for &field in &fields {
46 if let Some(val) = item.get(field) {
47 obj.insert(field.to_string(), val.clone());
48 }
49 }
50 serde_json::Value::Object(obj)
51 })
52 .collect();
53 println!("{}", serde_json::to_string_pretty(&filtered).unwrap());
54 }
55 "csv" => {
56 if !self.no_header {
57 println!("{}", fields.join(","));
58 }
59 for item in items {
60 let row: Vec<String> = fields
61 .iter()
62 .map(|&f| flatten_value(item.get(f)))
63 .collect();
64 println!("{}", row.join(","));
65 }
66 }
67 _ => {
68 let mut table = Table::new();
70 table.set_content_arrangement(ContentArrangement::Dynamic);
71 if !self.no_header {
72 table.set_header(fields.iter().map(|f| f.to_string()).collect::<Vec<_>>());
73 }
74 for item in items {
75 let row: Vec<String> = fields
76 .iter()
77 .map(|&f| flatten_value(item.get(f)))
78 .collect();
79 table.add_row(row);
80 }
81 println!("{}", table);
82 }
83 }
84 }
85
86 pub fn print_single(&self, item: &serde_json::Value, default_fields: &[&str], id_field: &str) {
87 self.print_items(&[item.clone()], default_fields, id_field);
88 }
89
90 pub fn print_message(&self, message: &str) {
91 if self.mode == "json" {
92 println!("{}", serde_json::json!({ "message": message }));
93 } else {
94 println!("{}", message);
95 }
96 }
97}
98
99pub fn compact_items(items: &[serde_json::Value], fields: &[&str]) -> serde_json::Value {
102 let compacted: Vec<serde_json::Value> = items
103 .iter()
104 .map(|item| {
105 let mut obj = serde_json::Map::new();
106 for &field in fields {
107 let val = flatten_value(item.get(field));
108 obj.insert(field.to_string(), serde_json::Value::String(val));
109 }
110 serde_json::Value::Object(obj)
111 })
112 .collect();
113 serde_json::Value::Array(compacted)
114}
115
116pub fn flatten_value(value: Option<&serde_json::Value>) -> String {
117 match value {
118 None | Some(serde_json::Value::Null) => "-".to_string(),
119 Some(serde_json::Value::String(s)) => {
120 if let Ok(ms) = s.parse::<i64>() {
122 if ms > 1_000_000_000_000 && ms < 10_000_000_000_000 {
123 if let Some(dt) = DateTime::from_timestamp_millis(ms) {
124 return dt.format("%Y-%m-%d").to_string();
125 }
126 }
127 }
128 s.clone()
129 }
130 Some(serde_json::Value::Number(n)) => n.to_string(),
131 Some(serde_json::Value::Bool(b)) => b.to_string(),
132 Some(serde_json::Value::Array(arr)) => {
133 let items: Vec<String> = arr
135 .iter()
136 .map(|v| {
137 if let Some(username) = v.get("username").and_then(|u| u.as_str()) {
138 username.to_string()
139 } else if let Some(s) = v.as_str() {
140 s.to_string()
141 } else {
142 v.to_string()
143 }
144 })
145 .collect();
146 if items.is_empty() { "-".to_string() } else { items.join(", ") }
147 }
148 Some(serde_json::Value::Object(obj)) => {
149 if let Some(inner) = obj.get("status").and_then(|v| v.as_str()) {
151 inner.to_string()
152 } else if let Some(inner) = obj.get("priority").and_then(|v| v.as_str()) {
153 inner.to_string()
154 } else if let Some(name) = obj.get("name").and_then(|v| v.as_str()) {
155 name.to_string()
156 } else if let Some(username) = obj.get("username").and_then(|v| v.as_str()) {
157 username.to_string()
158 } else {
159 serde_json::to_string(&serde_json::Value::Object(obj.clone())).unwrap()
160 }
161 }
162 }
163}