1pub struct CliFormatter;
2
3impl CliFormatter {
4 pub fn print_section_header(title: &str) {
5 println!("\n{}", ansi_color("cyan", title, true));
6 println!("{}", "─".repeat(title.len()).dimmed());
7 }
8
9 pub fn print_field(label: &str, value: &str, color: Option<&str>) {
10 let colored_value = match color {
11 Some(c) => ansi_color(c, value, false),
12 None => value.to_string(),
13 };
14 println!(" {:<12} {}", format!("{}:", label).dimmed(), colored_value);
15 }
16
17 pub fn print_field_bold(label: &str, value: &str, color: Option<&str>) {
18 let colored_value = match color {
19 Some(c) => ansi_color(c, value, true),
20 None => bold(value),
21 };
22 println!(" {:<12} {}", format!("{}:", label).dimmed(), colored_value);
23 }
24
25 pub fn print_status(status: &str, is_active: bool) {
26 let (symbol, color) = if is_active {
27 ("●", "green")
28 } else {
29 ("○", "red")
30 };
31 println!(
32 " {:<12} {} {}",
33 "Status:".dimmed(),
34 ansi_color(color, symbol, false),
35 ansi_color(color, status, true)
36 );
37 }
38
39 pub fn print_summary(title: &str, total: &str) {
40 println!("\n{}", ansi_color("white", title, true));
41 println!(" {}", ansi_color("green", total, true));
42 }
43
44 pub fn print_project_entry(name: &str, duration: &str) {
45 println!(
46 " {:<25} {}",
47 ansi_color("yellow", &truncate_string(name, 25), true),
48 ansi_color("green", duration, true)
49 );
50 }
51
52 pub fn print_context_entry(context: &str, duration: &str) {
53 let color = get_context_color(context);
54 println!(
55 " {:<20} {}",
56 ansi_color(color, &format!("├─ {}", context), false),
57 ansi_color("green", duration, false)
58 );
59 }
60
61 pub fn print_session_entry(
62 session_id: Option<i64>,
63 project: &str,
64 duration: &str,
65 status: &str,
66 timestamp: &str,
67 ) {
68 let status_symbol = if status == "active" { "●" } else { "○" };
69 let status_color = if status == "active" { "green" } else { "gray" };
70
71 println!(
72 " {} {:<20} {:<15} {}",
73 ansi_color(status_color, status_symbol, false),
74 ansi_color("yellow", &truncate_string(project, 20), false),
75 ansi_color("green", duration, false),
76 timestamp.dimmed()
77 );
78 }
79
80 pub fn print_empty_state(message: &str) {
81 println!("\n {}", message.dimmed());
82 }
83
84 pub fn print_error(message: &str) {
85 println!(
86 " {} {}",
87 ansi_color("red", "✗", true),
88 ansi_color("red", message, false)
89 );
90 }
91
92 pub fn print_success(message: &str) {
93 println!(
94 " {} {}",
95 ansi_color("green", "✓", true),
96 ansi_color("green", message, false)
97 );
98 }
99
100 pub fn print_warning(message: &str) {
101 println!(
102 " {} {}",
103 ansi_color("yellow", "⚠", true),
104 ansi_color("yellow", message, false)
105 );
106 }
107
108 pub fn print_info(message: &str) {
109 println!(" {} {}", ansi_color("cyan", "ℹ", true), message);
110 }
111}
112
113pub fn ansi_color(color: &str, text: &str, bold: bool) -> String {
115 let color_code = match color {
116 "red" => "31",
117 "green" => "32",
118 "yellow" => "33",
119 "blue" => "34",
120 "magenta" => "35",
121 "cyan" => "36",
122 "white" => "37",
123 "gray" => "90",
124 _ => "37", };
126
127 if bold {
128 format!("\x1b[1;{}m{}\x1b[0m", color_code, text)
129 } else {
130 format!("\x1b[{}m{}\x1b[0m", color_code, text)
131 }
132}
133
134fn bold(text: &str) -> String {
135 format!("\x1b[1m{}\x1b[0m", text)
136}
137
138pub trait StringFormat {
139 fn dimmed(&self) -> String;
140}
141
142impl StringFormat for str {
143 fn dimmed(&self) -> String {
144 format!("\x1b[2m{}\x1b[0m", self)
145 }
146}
147
148fn get_context_color(context: &str) -> &str {
149 match context {
150 "terminal" => "cyan",
151 "ide" => "magenta",
152 "linked" => "yellow",
153 "manual" => "blue",
154 _ => "white",
155 }
156}
157
158pub fn truncate_string(s: &str, max_len: usize) -> String {
159 if s.len() <= max_len {
160 s.to_string()
161 } else {
162 format!("{}…", &s[..max_len.saturating_sub(1)])
163 }
164}
165
166pub fn format_duration_clean(seconds: i64) -> String {
167 let hours = seconds / 3600;
168 let minutes = (seconds % 3600) / 60;
169 let secs = seconds % 60;
170
171 if hours > 0 {
172 format!("{}h {}m", hours, minutes)
173 } else if minutes > 0 {
174 format!("{}m {}s", minutes, secs)
175 } else {
176 format!("{}s", secs)
177 }
178}