use std::fmt::Display; use num::{Integer, Zero, cast::AsPrimitive}; /// Pretty-print a quantity of bytes pub fn pp_bytes_si>(bytes: T) -> String { const UNITS: &[&str] = &["B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"]; const THRESHOLD: f64 = 1000.0; if bytes.is_zero() { return "0 B".to_owned(); } let bytes_f: f64 = bytes.as_(); let unit_index = (bytes_f.log10() / THRESHOLD.log10()).floor() as usize; let unit_index = unit_index.min(UNITS.len() - 1); let value = bytes_f / THRESHOLD.powi(unit_index as i32); if unit_index == 0 { format!("{} {}", bytes, UNITS[unit_index]) } else if value >= 100.0 { format!("{:.0} {}", value, UNITS[unit_index]) } else if value >= 10.0 { format!("{:.1} {}", value, UNITS[unit_index]) } else { format!("{:.2} {}", value, UNITS[unit_index]) } } /// Pretty-print a quantity of bytes pub fn pp_bytes_b2>(bytes: T) -> String { const UNITS: &[&str] = &["B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"]; const THRESHOLD: f64 = 1024.0; if bytes.is_zero() { return "0 B".to_owned(); } let bytes_f: f64 = bytes.as_(); let unit_index = (bytes_f.log2() / THRESHOLD.log2()).floor() as usize; let unit_index = unit_index.min(UNITS.len() - 1); let value = bytes_f / THRESHOLD.powi(unit_index as i32); if unit_index == 0 { format!("{} {}", bytes, UNITS[unit_index]) } else if value >= 100.0 { format!("{:.0} {}", value, UNITS[unit_index]) } else if value >= 10.0 { format!("{:.1} {}", value, UNITS[unit_index]) } else { format!("{:.2} {}", value, UNITS[unit_index]) } } /// Pretty-print an integer with comma separators pub fn pp_int_commas(value: T) -> String { let s = value.to_string(); let mut result = String::new(); let bytes = s.as_bytes(); for (i, &byte) in bytes.iter().enumerate() { if i > 0 && (bytes.len() - i).is_multiple_of(3) { result.push(','); } result.push(byte as char); } result } /// Convert bytes to a hex string #[inline] pub fn to_hex(bytes: &[u8]) -> String { bytes.iter().map(|b| format!("{:02x}", b)).collect() } /// Parse a hex string as bytes pub fn from_hex(hex_str: &str) -> Result, &'static str> { if !hex_str.len().is_multiple_of(2) { return Err("hex string must have even length"); } let mut bytes = Vec::with_capacity(hex_str.len() / 2); for chunk in hex_str.as_bytes().chunks(2) { let hex_byte = std::str::from_utf8(chunk).map_err(|_err| "invalid UTF-8 in hex string")?; let byte = u8::from_str_radix(hex_byte, 16).map_err(|_err| "invalid hex character")?; bytes.push(byte); } Ok(bytes) } /// Truncate a string from the left, replacing the first character with "…" if truncated pub fn truncate_left(length: usize, string: &str) -> String { if string.chars().count() <= length { return string.to_owned(); } if length == 0 { return String::new(); } if length == 1 { return "…".to_owned(); } let chars: Vec = string.chars().collect(); let start_index = chars.len() - (length - 1); let mut result = String::from("…"); result.extend(&chars[start_index..]); result } /// Truncate a string from the right, replacing the last character with "…" if truncated pub fn truncate_right(length: usize, string: &str) -> String { if string.chars().count() <= length { return string.to_owned(); } if length == 0 { return String::new(); } if length == 1 { return "…".to_owned(); } let chars: Vec = string.chars().collect(); let mut result = String::new(); result.extend(&chars[0..length - 1]); result.push('…'); result }