1use console::Term;
4use owo_colors::OwoColorize;
5use std::time::Duration;
6
7pub fn format_size(bytes: u64) -> String {
30 const UNITS: &[&str] = &["B", "KB", "MB", "GB"];
31
32 if bytes == 0 {
33 return "0 B".to_string();
34 }
35
36 let mut size = bytes as f64;
37 let mut unit_idx = 0;
38
39 while size >= 1024.0 && unit_idx < UNITS.len() - 1 {
40 size /= 1024.0;
41 unit_idx += 1;
42 }
43
44 if unit_idx == 0 {
45 format!("{} {}", size as u64, UNITS[unit_idx])
46 } else {
47 format!("{:.2} {}", size, UNITS[unit_idx])
48 }
49}
50
51pub fn format_duration(duration: Duration) -> String {
74 let total_ms = duration.as_millis();
75
76 if total_ms < 1000 {
77 format!("{}ms", total_ms)
78 } else if total_ms < 60_000 {
79 format!("{:.2}s", duration.as_secs_f64())
80 } else {
81 let secs = duration.as_secs();
82 let mins = secs / 60;
83 let secs = secs % 60;
84 format!("{}m {}s", mins, secs)
85 }
86}
87
88pub fn print_build_summary(entries: &[(String, u64, Duration)]) {
109 let term = Term::stderr();
110 let width = term.size().1 as usize;
111
112 eprintln!("\n{}", "Build Summary".bold().underline());
114 eprintln!("{}", "─".repeat(width.min(80)));
115
116 for (name, size, duration) in entries {
118 let size_str = format_size(*size);
119 let dur_str = format_duration(*duration);
120
121 eprintln!(
122 " {} {} {} {}",
123 "▸".blue(),
124 name.bright_white().bold(),
125 size_str.dimmed(),
126 format!("({})", dur_str).dimmed()
127 );
128 }
129
130 eprintln!("{}", "─".repeat(width.min(80)));
132
133 let total_size: u64 = entries.iter().map(|(_, s, _)| s).sum();
134 let total_time: Duration = entries.iter().map(|(_, _, d)| d).sum();
135
136 eprintln!(
137 " {} {} in {}",
138 "Total:".bold(),
139 format_size(total_size).green(),
140 format_duration(total_time).green()
141 );
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147
148 #[test]
149 fn test_format_size_zero() {
150 assert_eq!(format_size(0), "0 B");
151 }
152
153 #[test]
154 fn test_format_size_bytes() {
155 assert_eq!(format_size(1), "1 B");
156 assert_eq!(format_size(500), "500 B");
157 assert_eq!(format_size(1023), "1023 B");
158 }
159
160 #[test]
161 fn test_format_size_kilobytes() {
162 assert_eq!(format_size(1024), "1.00 KB");
163 assert_eq!(format_size(1536), "1.50 KB");
164 assert_eq!(format_size(10_240), "10.00 KB");
165 }
166
167 #[test]
168 fn test_format_size_megabytes() {
169 assert_eq!(format_size(1_048_576), "1.00 MB");
170 assert_eq!(format_size(1_572_864), "1.50 MB");
171 assert_eq!(format_size(10_485_760), "10.00 MB");
172 }
173
174 #[test]
175 fn test_format_size_gigabytes() {
176 assert_eq!(format_size(1_073_741_824), "1.00 GB");
177 assert_eq!(format_size(2_147_483_648), "2.00 GB");
178 }
179
180 #[test]
181 fn test_format_duration_milliseconds() {
182 assert_eq!(format_duration(Duration::from_millis(0)), "0ms");
183 assert_eq!(format_duration(Duration::from_millis(50)), "50ms");
184 assert_eq!(format_duration(Duration::from_millis(999)), "999ms");
185 }
186
187 #[test]
188 fn test_format_duration_seconds() {
189 assert_eq!(format_duration(Duration::from_millis(1000)), "1.00s");
190 assert_eq!(format_duration(Duration::from_millis(1500)), "1.50s");
191 assert_eq!(format_duration(Duration::from_millis(59_999)), "60.00s");
192 }
193
194 #[test]
195 fn test_format_duration_minutes() {
196 assert_eq!(format_duration(Duration::from_secs(60)), "1m 0s");
197 assert_eq!(format_duration(Duration::from_secs(90)), "1m 30s");
198 assert_eq!(format_duration(Duration::from_secs(125)), "2m 5s");
199 assert_eq!(format_duration(Duration::from_secs(3661)), "61m 1s");
200 }
201
202 #[test]
203 fn test_print_build_summary() {
204 let entries = vec![
205 ("index.js".to_string(), 15_234, Duration::from_millis(450)),
206 (
207 "vendor.js".to_string(),
208 234_567,
209 Duration::from_millis(1200),
210 ),
211 ];
212
213 print_build_summary(&entries);
215 }
216
217 #[test]
218 fn test_print_build_summary_empty() {
219 print_build_summary(&[]);
221 }
222}