137 lines
3.6 KiB
Rust
137 lines
3.6 KiB
Rust
use std::fmt::Display;
|
|
|
|
use num::{Integer, Zero, cast::AsPrimitive};
|
|
|
|
/// Pretty-print a quantity of bytes
|
|
pub fn pp_bytes_si<T: Integer + Display + Zero + Clone + AsPrimitive<f64>>(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<T: Integer + Display + Zero + Clone + AsPrimitive<f64>>(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<T: Integer + Display>(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<Vec<u8>, &'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<char> = 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<char> = string.chars().collect();
|
|
let mut result = String::new();
|
|
result.extend(&chars[0..length - 1]);
|
|
result.push('…');
|
|
result
|
|
}
|