1use colored::{ColoredString, Colorize};
7
8use crate::spec::SpecStatus;
9
10pub fn is_quiet() -> bool {
12 if std::env::var("CHANT_QUIET")
14 .map(|v| v == "1" || v.eq_ignore_ascii_case("true"))
15 .unwrap_or(false)
16 {
17 return true;
18 }
19
20 is_silent_mode()
22}
23
24pub fn is_silent_mode() -> bool {
26 crate::config::Config::load()
28 .ok()
29 .map(|config| config.project.silent)
30 .unwrap_or(false)
31}
32
33pub fn status_icon(status: &SpecStatus) -> ColoredString {
45 match status {
46 SpecStatus::Pending => "○".white(),
47 SpecStatus::InProgress => "◐".yellow(),
48 SpecStatus::Paused => "◑".cyan(),
49 SpecStatus::Completed => "●".green(),
50 SpecStatus::Failed => "✗".red(),
51 SpecStatus::NeedsAttention => "⚠".yellow(),
52 SpecStatus::Ready => "◕".cyan(),
53 SpecStatus::Blocked => "⊗".red(),
54 SpecStatus::Cancelled => "✓".dimmed(),
55 }
56}
57
58pub fn attention_symbol(status: &SpecStatus) -> ColoredString {
65 match status {
66 SpecStatus::Failed | SpecStatus::NeedsAttention => "✗".red(),
67 SpecStatus::Blocked => "◌".yellow(),
68 _ => "?".normal(),
69 }
70}
71
72pub mod colors {
74 use colored::{Color, ColoredString, Colorize};
75
76 pub fn success(text: &str) -> ColoredString {
78 text.green()
79 }
80
81 pub fn warning(text: &str) -> ColoredString {
83 text.yellow()
84 }
85
86 pub fn error(text: &str) -> ColoredString {
88 text.red()
89 }
90
91 pub fn identifier(text: &str) -> ColoredString {
93 text.cyan()
94 }
95
96 pub fn info(text: &str) -> ColoredString {
98 text.blue()
99 }
100
101 pub fn secondary(text: &str) -> ColoredString {
103 text.dimmed()
104 }
105
106 pub fn heading(text: &str) -> ColoredString {
108 text.bold()
109 }
110
111 pub fn markdown_heading(text: &str, level: usize) -> ColoredString {
113 match level {
114 1 => text.bold(),
115 2 => text.bold().cyan(),
116 3 => text.bold().blue(),
117 4 => text.bold().magenta(),
118 _ => text.bold(),
119 }
120 }
121
122 pub fn colored(text: &str, color: Color) -> ColoredString {
124 text.color(color)
125 }
126}
127
128pub mod format {
130 pub fn truncate_title(title: &str, max_len: usize) -> String {
132 if title.len() <= max_len {
133 title.to_string()
134 } else {
135 format!("{}...", &title[..max_len.saturating_sub(3)])
136 }
137 }
138
139 pub fn elapsed_minutes(minutes: i64) -> String {
141 if minutes < 1 {
142 "just now".to_string()
143 } else if minutes < 60 {
144 format!("{}m", minutes)
145 } else if minutes < 1440 {
146 let hours = minutes / 60;
148 let mins = minutes % 60;
149 if mins == 0 {
150 format!("{}h", hours)
151 } else {
152 format!("{}h {}m", hours, mins)
153 }
154 } else {
155 let days = minutes / 1440;
157 format!("{}d", days)
158 }
159 }
160
161 pub fn separator(width: usize) -> String {
163 "─".repeat(width)
164 }
165}
166
167#[cfg(test)]
168mod tests {
169 use super::*;
170
171 #[test]
172 fn test_status_icon_all_statuses() {
173 status_icon(&SpecStatus::Pending);
174 status_icon(&SpecStatus::InProgress);
175 status_icon(&SpecStatus::Completed);
176 status_icon(&SpecStatus::Failed);
177 status_icon(&SpecStatus::NeedsAttention);
178 status_icon(&SpecStatus::Ready);
179 status_icon(&SpecStatus::Blocked);
180 status_icon(&SpecStatus::Cancelled);
181 }
182
183 #[test]
184 fn test_attention_symbol() {
185 attention_symbol(&SpecStatus::Failed);
186 attention_symbol(&SpecStatus::NeedsAttention);
187 attention_symbol(&SpecStatus::Blocked);
188 attention_symbol(&SpecStatus::Pending);
189 }
190
191 #[test]
192 fn test_truncate_title() {
193 assert_eq!(format::truncate_title("short", 10), "short");
194 assert_eq!(format::truncate_title("exactly ten", 11), "exactly ten");
195 assert_eq!(
196 format::truncate_title("this is a very long title", 10),
197 "this is..."
198 );
199 }
200
201 #[test]
202 fn test_elapsed_minutes() {
203 assert_eq!(format::elapsed_minutes(0), "just now");
204 assert_eq!(format::elapsed_minutes(30), "30m");
205 assert_eq!(format::elapsed_minutes(60), "1h");
206 assert_eq!(format::elapsed_minutes(90), "1h 30m");
207 assert_eq!(format::elapsed_minutes(120), "2h");
208 assert_eq!(format::elapsed_minutes(1440), "1d");
209 assert_eq!(format::elapsed_minutes(2880), "2d");
210 }
211
212 #[test]
213 fn test_separator() {
214 assert_eq!(format::separator(5), "─────");
215 assert_eq!(format::separator(10), "──────────");
216 }
217}