pub struct CliFormatter;
impl CliFormatter {
pub fn print_section_header(title: &str) {
println!("\n{}", ansi_color("clean_blue", title, true));
println!("{}", "─".repeat(title.len()).dimmed());
}
pub fn print_field(label: &str, value: &str, color: Option<&str>) {
let colored_value = match color {
Some(c) => ansi_color(c, value, false),
None => value.to_string(),
};
println!(
"{} {:<20} {}",
"│".dimmed(),
format!("{}:", label).dimmed(),
colored_value
);
}
pub fn print_field_bold(label: &str, value: &str, color: Option<&str>) {
let colored_value = match color {
Some(c) => ansi_color(c, value, true),
None => bold(value),
};
println!(
"{} {:<20} {}",
"│".dimmed(),
format!("{}:", label).dimmed(),
colored_value
);
}
pub fn print_status(status: &str, is_active: bool) {
let (symbol, color) = if is_active {
("●", "accent")
} else {
("○", "red")
};
println!(
"{} {:<20} {} {}",
"│".dimmed(),
"Status:".dimmed(),
ansi_color(color, symbol, false),
ansi_color(color, status, true)
);
}
pub fn print_summary(title: &str, total: &str) {
println!("\n{}", ansi_color("white", title, true));
println!(" {}", ansi_color("green", total, true));
}
pub fn print_project_entry(name: &str, duration: &str) {
println!(
" {:<25} {}",
ansi_color("clean_blue", &truncate_string(name, 25), true),
ansi_color("accent", duration, true)
);
}
pub fn print_context_entry(context: &str, duration: &str) {
let color = get_context_color(context);
println!(
" {:<20} {}",
ansi_color(color, &format!("├─ {}", context), false),
ansi_color("accent", duration, false)
);
}
pub fn print_session_entry(
_session_id: Option<i64>,
project: &str,
duration: &str,
status: &str,
timestamp: &str,
) {
let status_symbol = if status == "active" { "●" } else { "○" };
let status_color = if status == "active" { "accent" } else { "gray" };
println!(
" {} {:<20} {:<15} {}",
ansi_color(status_color, status_symbol, false),
ansi_color("clean_blue", &truncate_string(project, 20), false),
ansi_color("accent", duration, false),
timestamp.dimmed()
);
}
pub fn print_empty_state(message: &str) {
println!("\n {}", message.dimmed());
}
pub fn print_error(message: &str) {
println!(
" {} {}",
ansi_color("red", "✗", true),
ansi_color("red", message, false)
);
}
pub fn print_success(message: &str) {
println!(
" {} {}",
ansi_color("accent", "✓", true),
ansi_color("accent", message, false)
);
}
pub fn print_warning(message: &str) {
println!(
" {} {}",
ansi_color("yellow", "⚠", true),
ansi_color("yellow", message, false)
);
}
pub fn print_info(message: &str) {
println!(" {} {}", ansi_color("clean_blue", "ℹ", true), message);
}
pub fn print_block_line(label: &str, value: &str) {
println!(
"{} {:<20} {}",
"│".dimmed(),
format!("{}:", label).dimmed(),
value
);
}
pub fn print_daemon_start(version: &str) {
println!(
"{}",
format!("Starting Tempo Daemon (v{})...", version).dimmed()
);
}
pub fn print_daemon_success(pid: u32, info: &str) {
let msg = format!("Daemon active. PID: {} [{}]", pid, info);
println!(
"{} {}",
ansi_color("accent", "✓", true),
ansi_color("accent", &msg, false)
);
}
}
pub fn ansi_color(color: &str, text: &str, bold: bool) -> String {
let color_code = match color {
"red" => "38;2;243;139;168", "green" => "38;2;166;227;161", "yellow" => "38;2;249;226;175", "blue" => "38;2;137;180;250", "magenta" => "38;2;203;166;247", "cyan" => "38;2;148;226;213", "white" => "38;2;217;224;238", "gray" => "38;2;108;112;134", "accent" => "38;2;51;255;153", "neon_cyan" => "38;2;0;255;255", "neon_green" => "38;2;57;255;20", "clean_blue" => "38;2;100;150;255", _ => "38;2;217;224;238", };
if bold {
format!("\x1b[1;{}m{}\x1b[0m", color_code, text)
} else {
format!("\x1b[{}m{}\x1b[0m", color_code, text)
}
}
fn bold(text: &str) -> String {
format!("\x1b[1m{}\x1b[0m", text)
}
pub trait StringFormat {
fn dimmed(&self) -> String;
}
impl StringFormat for str {
fn dimmed(&self) -> String {
format!("\x1b[2m{}\x1b[0m", self)
}
}
fn get_context_color(context: &str) -> &str {
match context {
"terminal" => "neon_cyan",
"ide" => "magenta",
"linked" => "yellow",
"manual" => "clean_blue",
_ => "white",
}
}
pub fn truncate_string(s: &str, max_len: usize) -> String {
if s.len() <= max_len {
s.to_string()
} else {
format!("{}…", &s[..max_len.saturating_sub(1)])
}
}
pub fn format_duration_clean(seconds: i64) -> String {
let hours = seconds / 3600;
let minutes = (seconds % 3600) / 60;
let secs = seconds % 60;
if hours > 0 {
format!("{}h {}m", hours, minutes)
} else if minutes > 0 {
format!("{}m {}s", minutes, secs)
} else {
format!("{}s", secs)
}
}