use std::io::Write;
use std::path::Path;
use std::time::Duration;
pub fn format_duration(duration: Duration) -> String {
let total_secs = duration.as_secs();
let millis = duration.subsec_millis();
if total_secs == 0 {
if millis == 0 {
let micros = duration.as_micros();
if micros < 1000 {
format!("{}μs", micros)
} else {
format!("{:.1}ms", micros as f64 / 1000.0)
}
} else {
format!("{}ms", millis)
}
} else if total_secs < 60 {
format!("{:.2}s", duration.as_secs_f64())
} else if total_secs < 3600 {
let mins = total_secs / 60;
let secs = total_secs % 60;
format!("{}m {}s", mins, secs)
} else {
let hours = total_secs / 3600;
let mins = (total_secs % 3600) / 60;
let secs = total_secs % 60;
format!("{}h {}m {}s", hours, mins, secs)
}
}
pub fn format_bytes(bytes: u64) -> String {
const UNITS: &[&str] = &["B", "KB", "MB", "GB", "TB", "PB"];
if bytes < 1024 {
return format!("{} B", bytes);
}
let mut size = bytes as f64;
let mut unit_idx = 0;
while size >= 1024.0 && unit_idx < UNITS.len() - 1 {
size /= 1024.0;
unit_idx += 1;
}
if size >= 100.0 {
format!("{:.0} {}", size, UNITS[unit_idx])
} else if size >= 10.0 {
format!("{:.1} {}", size, UNITS[unit_idx])
} else {
format!("{:.2} {}", size, UNITS[unit_idx])
}
}
pub fn format_number(n: u64) -> String {
let s = n.to_string();
let mut result = String::new();
let chars: Vec<char> = s.chars().collect();
for (i, c) in chars.iter().enumerate() {
if i > 0 && (chars.len() - i) % 3 == 0 {
result.push(',');
}
result.push(*c);
}
result
}
pub fn truncate_str(s: &str, max_len: usize) -> String {
if s.len() <= max_len {
s.to_string()
} else if max_len <= 3 {
"...".to_string()
} else {
format!("{}...", &s[..max_len - 3])
}
}
pub fn file_size(path: &Path) -> std::io::Result<u64> {
let metadata = std::fs::metadata(path)?;
Ok(metadata.len())
}
pub fn percentage(part: u64, total: u64) -> f64 {
if total == 0 {
0.0
} else {
(part as f64 / total as f64) * 100.0
}
}
pub fn format_percentage(part: u64, total: u64) -> String {
let pct = percentage(part, total);
if pct >= 10.0 {
format!("{:.1}%", pct)
} else if pct >= 1.0 {
format!("{:.2}%", pct)
} else if pct > 0.0 {
format!("{:.3}%", pct)
} else {
"0.0%".to_string()
}
}
pub fn print_progress_bar(current: u64, total: u64, width: usize) {
let pct = percentage(current, total);
let filled = ((pct / 100.0) * width as f64) as usize;
let empty = width.saturating_sub(filled);
print!("\r[");
for _ in 0..filled {
print!("=");
}
if filled < width {
print!(">");
}
for _ in 0..empty {
print!(" ");
}
print!(
"] {:.1}% ({}/{})",
pct,
format_number(current),
format_number(total)
);
std::io::stdout().flush().unwrap_or(());
}
pub fn clear_line() {
print!("\r\x1B[K");
std::io::stdout().flush().unwrap_or(());
}
pub fn is_terminal() -> bool {
use std::io::IsTerminal;
std::io::stdout().is_terminal()
}
pub fn terminal_width() -> usize {
terminal_size::terminal_size()
.map(|(w, _)| w.0 as usize)
.unwrap_or(80)
}
pub fn wrap_text(text: &str, width: usize) -> Vec<String> {
textwrap::wrap(text, width)
.into_iter()
.map(|s| s.to_string())
.collect()
}
pub fn format_table_header(columns: &[&str]) -> String {
let mut result = String::new();
result.push('┌');
for (i, col) in columns.iter().enumerate() {
let width = col.len() + 2;
result.push_str(&"─".repeat(width));
if i < columns.len() - 1 {
result.push('┬');
}
}
result.push_str("┐\n│");
for (i, col) in columns.iter().enumerate() {
result.push(' ');
result.push_str(col);
result.push(' ');
if i < columns.len() - 1 {
result.push('│');
}
}
result.push_str("│\n├");
for (i, col) in columns.iter().enumerate() {
let width = col.len() + 2;
result.push_str(&"─".repeat(width));
if i < columns.len() - 1 {
result.push('┼');
}
}
result.push('┤');
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_format_duration() {
assert_eq!(format_duration(Duration::from_millis(1)), "1ms");
assert_eq!(format_duration(Duration::from_millis(500)), "500ms");
assert_eq!(format_duration(Duration::from_secs(1)), "1.00s");
assert_eq!(format_duration(Duration::from_secs(65)), "1m 5s");
assert_eq!(format_duration(Duration::from_secs(3661)), "1h 1m 1s");
}
#[test]
fn test_format_bytes() {
assert_eq!(format_bytes(512), "512 B");
assert_eq!(format_bytes(1024), "1.00 KB");
assert_eq!(format_bytes(1536), "1.50 KB");
assert_eq!(format_bytes(1048576), "1.00 MB");
assert_eq!(format_bytes(1073741824), "1.00 GB");
}
#[test]
fn test_format_number() {
assert_eq!(format_number(1000), "1,000");
assert_eq!(format_number(1234567), "1,234,567");
assert_eq!(format_number(999), "999");
}
#[test]
fn test_truncate_str() {
assert_eq!(truncate_str("hello", 10), "hello");
assert_eq!(truncate_str("hello world", 8), "hello...");
assert_eq!(truncate_str("hi", 2), "hi");
}
#[test]
fn test_percentage() {
assert_eq!(percentage(50, 100), 50.0);
assert_eq!(percentage(1, 4), 25.0);
assert_eq!(percentage(0, 100), 0.0);
assert_eq!(percentage(10, 0), 0.0); }
#[test]
fn test_format_percentage() {
assert_eq!(format_percentage(50, 100), "50.0%");
assert_eq!(format_percentage(1, 100), "1.00%");
assert_eq!(format_percentage(1, 1000), "0.100%");
}
}