envelope_cli/display/
report.rs

1//! Report formatting utilities for terminal output
2//!
3//! Provides formatting helpers for various report types.
4
5use crate::models::Money;
6
7/// Format a money amount with color hints for terminal display
8pub fn format_money_colored(amount: Money) -> String {
9    if amount.is_negative() {
10        format!("\x1b[31m{}\x1b[0m", amount) // Red for negative
11    } else if amount.is_positive() {
12        format!("\x1b[32m{}\x1b[0m", amount) // Green for positive
13    } else {
14        amount.to_string()
15    }
16}
17
18/// Format a percentage with appropriate precision
19pub 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
29/// Create a simple bar chart representation
30pub 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
41/// Format a header line with padding
42pub 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
51/// Format a separator line
52pub fn separator(width: usize) -> String {
53    "─".repeat(width)
54}
55
56/// Format a double separator line
57pub fn double_separator(width: usize) -> String {
58    "═".repeat(width)
59}
60
61/// Truncate a string to a maximum length with ellipsis
62pub 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
72/// Right-align text in a field of given width
73pub 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
81/// Left-align text in a field of given width
82pub 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}