ntw 0.2.1

A macOS network toolkit providing live speed metrics and simple interface management.
Documentation
use clap::ValueEnum;
use std::{fmt, str::FromStr};

#[derive(Copy, Clone, Debug, PartialEq, Eq, ValueEnum)]
pub enum Unit {
    Auto,
    Bps,
    Kbps,
    Mbps,
    Gbps,
}

impl Unit {
    pub fn divisor(self) -> f64 {
        match self {
            Unit::Auto => 1.0,
            Unit::Bps => 1.0,
            Unit::Kbps => 1_000.0,
            Unit::Mbps => 1_000_000.0,
            Unit::Gbps => 1_000_000_000.0,
        }
    }

    pub fn suffix(self) -> &'static str {
        match self {
            Unit::Auto => "auto",
            Unit::Bps => "bps",
            Unit::Kbps => "Kbps",
            Unit::Mbps => "Mbps",
            Unit::Gbps => "Gbps",
        }
    }

    pub fn from_bits(self, bits: f64) -> f64 {
        bits / self.divisor()
    }
}

impl FromStr for Unit {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s.to_lowercase().as_str() {
            "auto" => Ok(Unit::Auto),
            "bps" | "b" | "bit" | "bits" => Ok(Unit::Bps),
            "kbps" | "k" | "kbit" => Ok(Unit::Kbps),
            "mbps" | "m" | "mbit" => Ok(Unit::Mbps),
            "gbps" | "g" | "gbit" => Ok(Unit::Gbps),
            other => Err(format!("unknown unit: {other}")),
        }
    }
}

impl fmt::Display for Unit {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.suffix())
    }
}

pub fn choose_unit_from_bits(bits_per_sec: f64) -> Unit {
    if bits_per_sec >= 1_000_000_000.0 {
        Unit::Gbps
    } else if bits_per_sec >= 1_000_000.0 {
        Unit::Mbps
    } else if bits_per_sec >= 1_000.0 {
        Unit::Kbps
    } else {
        Unit::Bps
    }
}

pub fn parse_unit(s: &str) -> Option<Unit> {
    s.parse().ok()
}

pub fn bytes_to_unit(bytes: u64, unit: Unit) -> f64 {
    let bits = bytes as f64 * 8.0;
    unit.from_bits(bits)
}

fn format_value_by_unit(value: f64, unit: Unit) -> String {
    let number = match unit {
        Unit::Bps => format!("{:.0}", value),
        Unit::Kbps => format!("{:.0}", value),
        Unit::Mbps => {
            if value < 10.0 {
                format!("{:.2}", value)
            } else if value < 100.0 {
                format!("{:.1}", value)
            } else {
                format!("{:.0}", value)
            }
        }
        Unit::Gbps => {
            if value < 10.0 {
                format!("{:.2}", value)
            } else if value < 100.0 {
                format!("{:.1}", value)
            } else {
                format!("{:.2}", value)
            }
        }
        Unit::Auto => format!("{:.2}", value),
    };

    format!("{} {}", number, unit)
}

pub fn format_speed(bytes: u64, unit: Unit) -> String {
    let bits = bytes as f64 * 8.0;

    if unit == Unit::Auto {
        let chosen = choose_unit_from_bits(bits);
        let val = chosen.from_bits(bits);
        return format_value_by_unit(val, chosen);
    }

    let val = bytes_to_unit(bytes, unit);
    format_value_by_unit(val, unit)
}

pub fn format_speed_normalized(bytes: u64, unit: Unit, interval_ms: u64) -> String {
    if interval_ms == 0 {
        return format_speed(bytes, unit);
    }

    let bits_per_sec = (bytes as f64 * 8.0) * (1000.0 / interval_ms as f64);

    if unit == Unit::Auto {
        let chosen = choose_unit_from_bits(bits_per_sec);
        return format_value_by_unit(chosen.from_bits(bits_per_sec), chosen);
    }

    format_value_by_unit(unit.from_bits(bits_per_sec), unit)
}