loaders 0.0.0

A fully-featured, customisable progress bar and loading indicator library for Rust CLI and terminal applications
Documentation
//! Human-friendly formatting for bytes, rates, counts, and percentages.

/// Formats a byte count using binary units.
///
/// Values below one kilobyte are rendered as whole bytes. Larger values use two
/// decimal places.
///
/// # Examples
///
/// ```rust
/// assert_eq!(loaders::utils::format::format_bytes(1024), "1.00 KB");
/// ```
pub fn format_bytes(bytes: u64) -> String {
    const UNITS: [&str; 6] = ["B", "KB", "MB", "GB", "TB", "PB"];
    if bytes < 1024 {
        return format!("{bytes} B");
    }

    let mut value = bytes as f64;
    let mut unit = 0usize;
    while value >= 1024.0 && unit + 1 < UNITS.len() {
        value /= 1024.0;
        unit += 1;
    }
    format!("{value:.2} {}", UNITS[unit])
}

/// Formats a byte throughput value.
///
/// The value is treated as bytes per second.
///
/// # Examples
///
/// ```rust
/// assert_eq!(loaders::utils::format::format_bytes_rate(1024.0), "1.00 KB/s");
/// ```
pub fn format_bytes_rate(bytes_per_sec: f64) -> String {
    if !bytes_per_sec.is_finite() || bytes_per_sec <= 0.0 {
        return "0 B/s".to_string();
    }
    format!("{}/s", format_bytes(bytes_per_sec.round() as u64))
}

/// Formats a large count using compact metric suffixes.
///
/// # Examples
///
/// ```rust
/// assert_eq!(loaders::utils::format::format_count(1_500_000), "1.5M");
/// ```
pub fn format_count(n: u64) -> String {
    match n {
        0..=999 => n.to_string(),
        1_000..=999_999 => format!("{:.1}k", n as f64 / 1_000.0),
        1_000_000..=999_999_999 => format!("{:.1}M", n as f64 / 1_000_000.0),
        _ => format!("{:.1}B", n as f64 / 1_000_000_000.0),
    }
}

/// Formats a per-second rate with a unit label.
///
/// # Examples
///
/// ```rust
/// assert_eq!(loaders::utils::format::format_rate(1500.0, "items"), "1.50k items/s");
/// ```
pub fn format_rate(per_sec: f64, unit: &str) -> String {
    let value = if per_sec.is_finite() {
        per_sec.max(0.0)
    } else {
        0.0
    };

    let formatted = if value >= 1_000_000_000.0 {
        format!("{:.2}B", value / 1_000_000_000.0)
    } else if value >= 1_000_000.0 {
        format!("{:.2}M", value / 1_000_000.0)
    } else if value >= 1_000.0 {
        format!("{:.2}k", value / 1_000.0)
    } else {
        format!("{value:.2}")
    };
    format!("{formatted} {unit}/s")
}

/// Formats a fraction as a percentage.
///
/// The input fraction is clamped to the inclusive `0.0..=1.0` range.
///
/// # Examples
///
/// ```rust
/// assert_eq!(loaders::utils::format::format_percent(0.5, 1), "50.0%");
/// ```
pub fn format_percent(fraction: f64, decimals: usize) -> String {
    let clamped = if fraction.is_finite() {
        fraction.clamp(0.0, 1.0)
    } else {
        0.0
    };
    format!("{:.*}%", decimals, clamped * 100.0)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_format_bytes_zero() {
        assert_eq!(format_bytes(0), "0 B");
    }

    #[test]
    fn test_format_bytes_999() {
        assert_eq!(format_bytes(999), "999 B");
    }

    #[test]
    fn test_format_bytes_kb() {
        assert_eq!(format_bytes(1024), "1.00 KB");
    }

    #[test]
    fn test_format_bytes_mb() {
        assert_eq!(format_bytes(1_048_576), "1.00 MB");
    }

    #[test]
    fn test_format_bytes_gb() {
        assert_eq!(format_bytes(1_073_741_824), "1.00 GB");
    }

    #[test]
    fn test_format_bytes_tb() {
        assert_eq!(format_bytes(1_099_511_627_776), "1.00 TB");
    }

    #[test]
    fn test_format_count_hundreds() {
        assert_eq!(format_count(999), "999");
    }

    #[test]
    fn test_format_count_thousands() {
        assert_eq!(format_count(1200), "1.2k");
    }

    #[test]
    fn test_format_count_millions() {
        assert_eq!(format_count(1_500_000), "1.5M");
    }

    #[test]
    fn test_format_rate_basic() {
        assert_eq!(format_rate(1500.0, "items"), "1.50k items/s");
    }

    #[test]
    fn test_format_percent_zero() {
        assert_eq!(format_percent(0.0, 0), "0%");
    }

    #[test]
    fn test_format_percent_half() {
        assert_eq!(format_percent(0.5, 0), "50%");
    }

    #[test]
    fn test_format_percent_full() {
        assert_eq!(format_percent(1.0, 0), "100%");
    }

    #[test]
    fn test_format_percent_decimals() {
        assert_eq!(format_percent(0.1234, 2), "12.34%");
    }

    #[test]
    fn test_format_bytes_rate() {
        assert_eq!(format_bytes_rate(1024.0), "1.00 KB/s");
    }
}