tempo_cli/cli/
formatter.rs

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
89// Helper functions
90fn 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", // default to white
101    };
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}