cellstate/entity/
output.rs1use console::style;
9use serde::Serialize;
10use serde_json::Value;
11
12pub struct OutputConfig {
14 pub json: bool,
15}
16
17impl OutputConfig {
18 pub fn print<T: Serialize>(&self, value: &T) {
20 let v = serde_json::to_value(value).unwrap_or(Value::Null);
21 self.print_value(&v);
22 }
23
24 pub fn print_value(&self, value: &Value) {
26 if self.json {
27 println!(
28 "{}",
29 serde_json::to_string_pretty(value).unwrap_or_default()
30 );
31 } else {
32 print_human(value, 0);
33 }
34 }
35
36 pub fn print_list<T: Serialize>(&self, items: &[T], total: i64) {
38 if self.json {
39 let count = items.len();
40 let out = serde_json::json!({
41 "items": items,
42 "count": count,
43 "total": total,
44 });
45 println!("{}", serde_json::to_string_pretty(&out).unwrap_or_default());
46 } else {
47 for item in items {
48 let v = serde_json::to_value(item).unwrap_or(Value::Null);
49 print_human(&v, 0);
50 println!();
51 }
52 println!("{} total", style(total).dim());
53 }
54 }
55}
56
57fn print_human(value: &Value, indent: usize) {
60 match value {
61 Value::Object(map) => {
62 for (key, val) in map {
63 if key == "_links" || key == "links" {
65 continue;
66 }
67 if val.is_null() {
68 continue;
69 }
70 let prefix = " ".repeat(indent);
71 match val {
72 Value::Object(_) => {
73 println!("{prefix}{}", style(key).bold());
74 print_human(val, indent + 2);
75 }
76 Value::Array(arr) => {
77 println!("{prefix}{}: [{}]", style(key).dim(), arr.len());
78 }
79 _ => {
80 let display = format_value(key, val);
81 println!("{prefix} {}: {display}", style(key).dim());
82 }
83 }
84 }
85 }
86 _ => println!("{value}"),
87 }
88}
89
90fn format_value(key: &str, value: &Value) -> String {
91 match value {
92 Value::String(s) => {
93 if key.ends_with("_id") && s.len() > 8 && looks_like_uuid(s) {
95 format!("{}...", &s[..8])
96 } else if key == "status" {
97 match s.as_str() {
99 "active" | "idle" => style(s).green().to_string(),
100 "completed" | "success" => style(s).cyan().to_string(),
101 "failed" | "error" | "offline" | "killed" => style(s).red().to_string(),
102 "suspended" | "pending" => style(s).yellow().to_string(),
103 _ => s.clone(),
104 }
105 } else {
106 s.clone()
107 }
108 }
109 Value::Number(n) => n.to_string(),
110 Value::Bool(b) => b.to_string(),
111 _ => value.to_string(),
112 }
113}
114
115fn looks_like_uuid(s: &str) -> bool {
117 s.len() == 36 && s.as_bytes().get(8) == Some(&b'-')
118}