netscli 0.3.2

Interactive TUI and CLI for network discovery, scanning, and diagnostics
use netscli_core::PcapParseResult;

use super::style::{cyan, dim, green, yellow};
use super::table::column_width;
use super::CliFormatter;

impl CliFormatter {
    pub fn format_pcap_parse_result(result: &PcapParseResult) -> String {
        let shown = result.packets.len();
        let mut header = format!(
            "{} from {}",
            green(&format!(
                "Parsed {shown} packet{}",
                if shown == 1 { "" } else { "s" }
            )),
            cyan(&result.file_path.display().to_string())
        );
        if result.truncated {
            header.push_str(&format!(
                " {}",
                yellow(&format!("showing {shown} of {}", result.total_packets))
            ));
        }

        if result.packets.is_empty() {
            return format!("{header}\n\n{}", dim("No packets found."));
        }

        format!("{header}\n\n{}", Self::format_pcap_packet_table(result))
    }

    fn format_pcap_packet_table(result: &PcapParseResult) -> String {
        let time_width = column_width(
            result.packets.iter().map(|packet| packet.timestamp.len()),
            "Time".len(),
            18,
        );
        let source_width = column_width(
            result.packets.iter().map(|packet| packet.source.len()),
            "Source".len(),
            24,
        );
        let destination_width = column_width(
            result.packets.iter().map(|packet| packet.destination.len()),
            "Destination".len(),
            24,
        );

        let mut rows = vec![
            dim(&format!(
                "{:<6} {:<time_width$} {:<source_width$} {:<destination_width$} {:<8} {:<7} {}",
                "No.", "Time", "Source", "Destination", "Proto", "Length", "Info",
            )),
            dim(&"-".repeat(96)),
        ];

        for packet in &result.packets {
            rows.push(format!(
                "{:<6} {:<time_width$} {:<source_width$} {:<destination_width$} {:<8} {:<7} {}",
                packet.index,
                dim(&fit_cell(&packet.timestamp, time_width)),
                fit_cell(&packet.source, source_width),
                fit_cell(&packet.destination, destination_width),
                packet.protocol,
                packet.length,
                dim(&packet.info),
            ));
        }

        rows.join("\n")
    }
}

fn fit_cell(value: &str, width: usize) -> String {
    if value.chars().count() <= width {
        return value.to_string();
    }

    if width <= 3 {
        return ".".repeat(width);
    }

    let mut fitted = value.chars().take(width - 3).collect::<String>();
    fitted.push_str("...");
    fitted
}