envelope_cli/display/
report.rs1use crate::models::Money;
6
7pub fn format_money_colored(amount: Money) -> String {
9 if amount.is_negative() {
10 format!("\x1b[31m{}\x1b[0m", amount) } else if amount.is_positive() {
12 format!("\x1b[32m{}\x1b[0m", amount) } else {
14 amount.to_string()
15 }
16}
17
18pub fn format_percentage(pct: f64) -> String {
20 if pct < 0.1 && pct > 0.0 {
21 format!("{:.2}%", pct)
22 } else if pct < 10.0 {
23 format!("{:.1}%", pct)
24 } else {
25 format!("{:.0}%", pct)
26 }
27}
28
29pub fn format_bar(value: f64, max_value: f64, width: usize) -> String {
31 if max_value <= 0.0 || value <= 0.0 {
32 return " ".repeat(width);
33 }
34
35 let filled = ((value / max_value) * width as f64).round() as usize;
36 let filled = filled.min(width);
37
38 format!("{}{}", "█".repeat(filled), "░".repeat(width - filled))
39}
40
41pub fn format_header(title: &str, width: usize) -> String {
43 let padding = if title.len() >= width {
44 0
45 } else {
46 (width - title.len()) / 2
47 };
48 format!("{}{}", " ".repeat(padding), title)
49}
50
51pub fn separator(width: usize) -> String {
53 "─".repeat(width)
54}
55
56pub fn double_separator(width: usize) -> String {
58 "═".repeat(width)
59}
60
61pub fn truncate(s: &str, max_len: usize) -> String {
63 if s.len() <= max_len {
64 s.to_string()
65 } else if max_len <= 3 {
66 "...".chars().take(max_len).collect()
67 } else {
68 format!("{}...", &s[..max_len - 3])
69 }
70}
71
72pub fn right_align(s: &str, width: usize) -> String {
74 if s.len() >= width {
75 s.to_string()
76 } else {
77 format!("{:>width$}", s, width = width)
78 }
79}
80
81pub fn left_align(s: &str, width: usize) -> String {
83 if s.len() >= width {
84 s.to_string()
85 } else {
86 format!("{:<width$}", s, width = width)
87 }
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93
94 #[test]
95 fn test_format_percentage() {
96 assert_eq!(format_percentage(0.05), "0.05%");
97 assert_eq!(format_percentage(5.5), "5.5%");
98 assert_eq!(format_percentage(50.0), "50%");
99 }
100
101 #[test]
102 fn test_format_bar() {
103 let bar = format_bar(50.0, 100.0, 10);
104 assert_eq!(bar.chars().filter(|c| *c == '█').count(), 5);
105 }
106
107 #[test]
108 fn test_truncate() {
109 assert_eq!(truncate("Hello World", 5), "He...");
110 assert_eq!(truncate("Hi", 5), "Hi");
111 assert_eq!(truncate("Test", 4), "Test");
112 }
113
114 #[test]
115 fn test_alignment() {
116 assert_eq!(right_align("abc", 5), " abc");
117 assert_eq!(left_align("abc", 5), "abc ");
118 }
119}