devpulse 1.0.0

Developer diagnostics: HTTP timing, build artifact cleanup, environment health checks, port scanning, PATH analysis, and config format conversion
//! Shared utility functions used across DevPulse modules.

/// Format a byte count into human-readable form (B, KB, MB, GB).
///
/// Uses 2 decimal places for GB and MB, 1 decimal place for KB.
pub fn format_size(bytes: u64) -> String {
    if bytes >= 1_073_741_824 {
        format!("{:.2} GB", bytes as f64 / 1_073_741_824.0)
    } else if bytes >= 1_048_576 {
        format!("{:.1} MB", bytes as f64 / 1_048_576.0)
    } else if bytes >= 1024 {
        format!("{:.1} KB", bytes as f64 / 1024.0)
    } else {
        format!("{bytes} B")
    }
}

/// Safely truncate a string to at most `max_chars` characters,
/// appending "..." if truncation occurs.
///
/// This is UTF-8 safe — it never slices in the middle of a character.
pub fn safe_truncate(s: &str, max_chars: usize) -> String {
    if s.chars().count() <= max_chars {
        s.to_string()
    } else {
        let truncated: String = s.chars().take(max_chars.saturating_sub(3)).collect();
        format!("{truncated}...")
    }
}

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

    #[test]
    fn test_format_size_bytes() {
        assert_eq!(format_size(500), "500 B");
    }

    #[test]
    fn test_format_size_kb() {
        assert_eq!(format_size(2048), "2.0 KB");
    }

    #[test]
    fn test_format_size_mb() {
        assert_eq!(format_size(5_242_880), "5.0 MB");
    }

    #[test]
    fn test_format_size_gb() {
        assert_eq!(format_size(2_147_483_648), "2.00 GB");
    }

    #[test]
    fn test_safe_truncate_short() {
        assert_eq!(safe_truncate("hello", 10), "hello");
    }

    #[test]
    fn test_safe_truncate_exact() {
        assert_eq!(safe_truncate("hello", 5), "hello");
    }

    #[test]
    fn test_safe_truncate_long() {
        let result = safe_truncate("hello world this is long", 10);
        assert!(result.ends_with("..."));
        assert!(result.chars().count() <= 10);
    }

    #[test]
    fn test_safe_truncate_unicode() {
        // Multi-byte characters: "日本語テスト" = 6 chars
        let result = safe_truncate("日本語テスト", 5);
        assert!(result.ends_with("..."));
        // Should not panic
    }
}