sysinfo-cli 0.1.1

A lightweight, efficient, and professional CLI wrapper around the sysinfo crate.
use colored::*;
use comfy_table::Table;
use crate::models::*;

pub fn format_system_info(info: &SystemInfo) -> String {
    let mut s = String::new();
    s.push_str(&format!("{:<25} {:?}\n", "System name:".yellow(), info.name.as_deref().unwrap_or_default()));
    s.push_str(&format!("{:<25} {:?}\n", "Kernel version:".yellow(), info.kernel_version.as_deref().unwrap_or_default()));
    s.push_str(&format!("{:<25} {:?}\n", "OS version:".yellow(), info.os_version.as_deref().unwrap_or_default()));
    s.push_str(&format!("{:<25} {:?}\n", "Host name:".yellow(), info.host_name.as_deref().unwrap_or_default()));
    s
}

pub fn format_cpu_info(info: &CpuInfo) -> String {
    let mut s = String::new();
    s.push_str(&format!("{}\n", "=> CPUs:".bright_green().bold()));
    s.push_str(&format!("{:<25} {}\n", "Total CPUs:".yellow(), info.nb_cpus));
    s.push_str(&format!("{:<25} {:.1}%\n", "Global usage:".yellow(), info.total_usage));
    
    let mut table = Table::new();
    table.set_header(vec!["ID", "Usage %", "Vendor", "Brand"]);
    for cpu in &info.cpus {
        table.add_row(vec![
            cpu.id.to_string(),
            format!("{:.1}", cpu.usage),
            cpu.vendor.clone(),
            cpu.brand.clone(),
        ]);
    }
    s.push_str(&format!("{}\n", table));
    s
}

pub fn format_memory_info(info: &MemoryInfo) -> String {
    let mut s = String::new();
    s.push_str(&format!("{:<25} {}\n", "Total memory:".yellow(), format_bytes(info.total_memory)));
    s.push_str(&format!("{:<25} {}\n", "Used memory:".yellow(), format_bytes(info.used_memory)));
    s.push_str(&format!("{:<25} {}\n", "Total swap:".yellow(), format_bytes(info.total_swap)));
    s.push_str(&format!("{:<25} {}\n", "Used swap:".yellow(), format_bytes(info.used_swap)));
    s
}

pub fn format_disks_info(info: &[DiskInfo]) -> String {
    let mut s = String::new();
    s.push_str(&format!("{}\n", "=> Disks:".bright_green().bold()));
    let mut table = Table::new();
    table.set_header(vec!["Name", "Kind", "FS", "Available", "Total"]);
    for disk in info {
        table.add_row(vec![
            disk.name.cyan().to_string(),
            disk.kind.blue().to_string(),
            disk.file_system.yellow().to_string(),
            format_bytes(disk.available_space),
            format_bytes(disk.total_space),
        ]);
    }
    s.push_str(&format!("{}\n", table));
    s
}

pub fn format_network_info(info: &[NetworkInfo]) -> String {
    let mut s = String::new();
    s.push_str(&format!("{}\n", "=> Networks:".bright_green().bold()));
    let mut table = Table::new();
    table.set_header(vec!["Interface", "Received", "Transmitted"]);
    for net in info {
        table.add_row(vec![
            net.interface.cyan().to_string(),
            format_bytes(net.received).yellow().to_string(),
            format_bytes(net.transmitted).yellow().to_string(),
        ]);
    }
    s.push_str(&format!("{}\n", table));
    s
}

pub fn format_components_info(info: &[ComponentInfo]) -> String {
    let mut s = String::new();
    s.push_str(&format!("{}\n", "=> Components:".bright_green().bold()));
    let mut table = Table::new();
    table.set_header(vec!["Label", "Temp", "Max"]);
    for c in info {
        table.add_row(vec![
            c.label.cyan().to_string(),
            format!("{}°C", c.temperature.map(|t| format!("{:.1}", t)).unwrap_or_else(|| "N/A".to_string())),
            format!("{}°C", c.max.map(|t| format!("{:.1}", t)).unwrap_or_else(|| "N/A".to_string())),
        ]);
    }
    s.push_str(&format!("{}\n", table));
    s
}

pub fn format_processes_info(info: &[ProcessInfo]) -> String {
    let mut s = String::new();
    s.push_str(&format!("{}\n", "=> Processes:".bright_green().bold()));
    let mut table = Table::new();
    table.set_header(vec!["PID", "Name", "CPU %", "Memory"]);
    for p in info {
        let name = if p.name.len() > 30 { format!("{}...", &p.name[..27]) } else { p.name.clone() };
        table.add_row(vec![
            p.pid.cyan().to_string(),
            name,
            format!("{:>5.1}", p.cpu_usage),
            format_bytes(p.memory),
        ]);
    }
    s.push_str(&format!("{}\n", table));
    s
}

pub fn format_bytes(bytes: u64) -> String {
    if bytes == 0 {
        return "0 B".to_string();
    }
    let units = ["B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"];
    let i = (bytes as f64).log(1024.0).floor() as usize;
    let i = i.min(units.len() - 1);
    let value = bytes as f64 / 1024.0f64.powi(i as i32);
    format!("{:.2} {}", value, units[i])
}