lean_ctx/core/stats/format/
util.rs1use crate::core::theme::{self, Theme};
4
5use super::super::model::{CommandStats, CostModel, DayStats};
6
7pub(super) fn active_theme() -> Theme {
8 let cfg = crate::core::config::Config::load();
9 theme::load_theme(&cfg.theme)
10}
11
12pub(super) fn format_usd(amount: f64) -> String {
13 if amount >= 0.01 {
14 format!("${amount:.2}")
15 } else {
16 format!("${amount:.3}")
17 }
18}
19
20pub(super) fn usd_estimate(tokens: u64) -> String {
21 let model = CostModel::default();
22 let cost = tokens as f64 * model.input_price_per_m / 1_000_000.0;
23 format_usd(cost)
24}
25
26pub(super) fn format_pct_1dp(val: f64) -> String {
27 if val == 0.0 {
28 "0.0%".to_string()
29 } else if val > 0.0 && val < 0.1 {
30 "<0.1%".to_string()
31 } else {
32 format!("{val:.1}%")
33 }
34}
35
36pub(super) fn format_big(n: u64) -> String {
37 if n >= 1_000_000_000_000 {
38 format!("{:.2}T", n as f64 / 1_000_000_000_000.0)
39 } else if n >= 1_000_000_000 {
40 format!("{:.2}B", n as f64 / 1_000_000_000.0)
42 } else if n >= 1_000_000 {
43 format!("{:.1}M", n as f64 / 1_000_000.0)
44 } else if n >= 1_000 {
45 format!("{:.1}K", n as f64 / 1_000.0)
46 } else {
47 format!("{n}")
48 }
49}
50
51pub(super) fn format_num(n: u64) -> String {
52 if n >= 1_000_000_000_000 {
53 format!("{:.2}T", n as f64 / 1_000_000_000_000.0)
54 } else if n >= 1_000_000_000 {
55 format!("{:.2}B", n as f64 / 1_000_000_000.0)
56 } else if n >= 1_000_000 {
57 format!("{:.1}M", n as f64 / 1_000_000.0)
58 } else if n >= 1_000 {
59 format!("{},{:03}", n / 1_000, n % 1_000)
60 } else {
61 format!("{n}")
62 }
63}
64
65pub(super) fn truncate_cmd(cmd: &str, max: usize) -> String {
66 if cmd.len() <= max {
67 return cmd.to_string();
68 }
69 let mut end = max.saturating_sub(1);
72 while end > 0 && !cmd.is_char_boundary(end) {
73 end -= 1;
74 }
75 format!("{}…", &cmd[..end])
76}
77
78pub(super) fn cmd_total_saved(s: &CommandStats, _cm: &CostModel) -> u64 {
79 s.input_tokens.saturating_sub(s.output_tokens)
80}
81
82pub(super) fn day_total_saved(d: &DayStats, _cm: &CostModel) -> u64 {
83 d.input_tokens.saturating_sub(d.output_tokens)
84}
85
86pub(super) fn measured_compression_rate(input: u64, output: u64) -> Option<f64> {
89 (input > 0).then(|| input.saturating_sub(output) as f64 / input as f64 * 100.0)
90}
91
92pub fn normalize_command(command: &str) -> String {
93 let parts: Vec<&str> = command.split_whitespace().collect();
94 if parts.is_empty() {
95 return command.to_string();
96 }
97
98 let base = std::path::Path::new(parts[0])
99 .file_name()
100 .and_then(|n| n.to_str())
101 .unwrap_or(parts[0]);
102
103 match base {
104 "git" => {
105 if parts.len() > 1 {
106 format!("git {}", parts[1])
107 } else {
108 "git".to_string()
109 }
110 }
111 "cargo" => {
112 if parts.len() > 1 {
113 format!("cargo {}", parts[1])
114 } else {
115 "cargo".to_string()
116 }
117 }
118 "npm" | "yarn" | "pnpm" => {
119 if parts.len() > 1 {
120 format!("{} {}", base, parts[1])
121 } else {
122 base.to_string()
123 }
124 }
125 "docker" => {
126 if parts.len() > 1 {
127 format!("docker {}", parts[1])
128 } else {
129 "docker".to_string()
130 }
131 }
132 _ => base.to_string(),
133 }
134}
135
136#[cfg(test)]
137mod tests {
138 use super::{format_big, format_num, format_pct_1dp, measured_compression_rate};
139
140 #[test]
141 fn format_big_scales_through_billions() {
142 assert_eq!(format_big(900), "900");
143 assert_eq!(format_big(2_500), "2.5K");
144 assert_eq!(format_big(4_200_000), "4.2M");
145 assert_eq!(format_big(1_310_000_000), "1.31B");
147 assert_eq!(format_big(3_000_000_000_000), "3.00T");
148 }
149
150 #[test]
151 fn format_num_scales_through_billions() {
152 assert_eq!(format_num(950), "950");
153 assert_eq!(format_num(12_345), "12,345");
154 assert_eq!(format_num(7_800_000), "7.8M");
155 assert_eq!(format_num(1_310_000_000), "1.31B");
156 assert_eq!(format_num(2_500_000_000_000), "2.50T");
157 }
158
159 #[test]
160 fn format_pct_1dp_normal() {
161 assert_eq!(format_pct_1dp(50.0), "50.0%");
162 assert_eq!(format_pct_1dp(100.0), "100.0%");
163 assert_eq!(format_pct_1dp(33.333), "33.3%");
164 }
165
166 #[test]
167 fn format_pct_1dp_small_values() {
168 assert_eq!(format_pct_1dp(0.0), "0.0%");
169 assert_eq!(format_pct_1dp(0.05), "<0.1%");
170 assert_eq!(format_pct_1dp(0.09), "<0.1%");
171 assert_eq!(format_pct_1dp(0.1), "0.1%");
172 assert_eq!(format_pct_1dp(0.5), "0.5%");
173 }
174
175 #[test]
176 fn compression_rate_distinguishes_unmetered_from_zero_savings() {
177 assert_eq!(measured_compression_rate(0, 0), None);
178 assert_eq!(measured_compression_rate(100, 100), Some(0.0));
179 assert_eq!(measured_compression_rate(100, 25), Some(75.0));
180 }
181}