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