theater_cli/utils/
formatting.rs1use console::style;
2use std::time::Duration;
3use theater::id::TheaterId;
4use theater::messages::ActorStatus;
5use theater::ChainEvent;
6
7pub fn format_id(id: &TheaterId) -> String {
9 style(id.to_string()).cyan().to_string()
10}
11
12pub fn format_short_id(id: &TheaterId) -> String {
14 let id_str = id.to_string();
15 let short_id = &id_str[..std::cmp::min(8, id_str.len())];
16 style(short_id).cyan().to_string()
17}
18
19pub fn format_status(status: &ActorStatus) -> String {
21 match status {
22 ActorStatus::Running => style("RUNNING").green().bold().to_string(),
23 ActorStatus::Stopped => style("STOPPED").red().bold().to_string(),
24 ActorStatus::Failed => style("FAILED").red().bold().to_string(),
25 }
26}
27
28pub fn format_timestamp(timestamp: &u64) -> String {
30 let datetime = chrono::DateTime::from_timestamp(*timestamp as i64, 0)
31 .unwrap_or_else(|| chrono::DateTime::UNIX_EPOCH);
32 datetime.format("%Y-%m-%d %H:%M:%S").to_string()
33}
34
35pub fn format_duration(duration: Duration) -> String {
37 let total_secs = duration.as_secs();
38
39 if total_secs < 60 {
40 return format!("{}s", total_secs);
41 }
42
43 let mins = total_secs / 60;
44 let secs = total_secs % 60;
45
46 if mins < 60 {
47 return format!("{}m {}s", mins, secs);
48 }
49
50 let hours = mins / 60;
51 let mins = mins % 60;
52
53 if hours < 24 {
54 return format!("{}h {}m {}s", hours, mins, secs);
55 }
56
57 let days = hours / 24;
58 let hours = hours % 24;
59
60 format!("{}d {}h {}m {}s", days, hours, mins, secs)
61}
62
63pub fn format_hash(hash: &[u8], shorten: bool) -> String {
65 let hex = hex::encode(hash);
66 if shorten && hex.len() > 16 {
67 format!("{}..{}", &hex[0..8], &hex[hex.len() - 8..])
68 } else {
69 hex
70 }
71}
72
73pub fn format_section(title: &str) -> String {
75 format!(
76 "\n{}\n{}",
77 style(title).bold().underlined(),
78 style("─".repeat(title.len())).dim()
79 )
80}
81
82pub fn format_key_value(key: &str, value: &str) -> String {
84 format!("{}: {}", style(key).bold(), value)
85}
86
87pub fn format_event_summary(event: &ChainEvent) -> String {
89 let event_type = style(&event.event_type).yellow();
90 let timestamp = format_timestamp(&event.timestamp);
91 let hash = format_hash(&event.hash, true);
92
93 format!(
94 "{} at {} (hash: {})",
95 event_type,
96 style(timestamp).dim(),
97 style(hash).dim()
98 )
99}
100
101#[allow(dead_code)]
103pub fn format_table(headers: &[&str], rows: &[Vec<String>], indent: usize) -> String {
104 if rows.is_empty() {
105 return "No data available".to_string();
106 }
107
108 let mut widths: Vec<usize> = headers.iter().map(|h| h.len()).collect();
110
111 for row in rows {
112 for (i, cell) in row.iter().enumerate() {
113 if i < widths.len() {
114 widths[i] = std::cmp::max(widths[i], cell.len());
115 }
116 }
117 }
118
119 let mut result = " ".repeat(indent);
121 for (i, header) in headers.iter().enumerate() {
122 result.push_str(&format!(
123 "{:<width$} ",
124 style(*header).bold(),
125 width = widths[i]
126 ));
127 }
128 result.push('\n');
129
130 result.push_str(&" ".repeat(indent));
132 for width in &widths {
133 result.push_str(&style("─".repeat(*width)).dim().to_string());
134 result.push(' ');
135 }
136 result.push('\n');
137
138 for row in rows {
140 result.push_str(&" ".repeat(indent));
141 for (i, cell) in row.iter().enumerate() {
142 if i < widths.len() {
143 result.push_str(&format!("{:<width$} ", cell, width = widths[i]));
144 }
145 }
146 result.push('\n');
147 }
148
149 result
150}
151
152pub fn format_success(message: &str) -> String {
154 format!("{} {}", style("✓").green().bold(), message)
155}
156
157pub fn format_error(message: &str) -> String {
159 format!("{} {}", style("✗").red().bold(), message)
160}
161
162pub fn format_info(message: &str) -> String {
164 format!("{} {}", style("ℹ").blue().bold(), message)
165}
166
167pub fn format_warning(message: &str) -> String {
169 format!("{} {}", style("⚠").yellow().bold(), message)
170}