speedtest-cli 0.1.0

A fast and simple CLI tool to test internet connection speed with real-time progress indicators
use clap::{Parser, Subcommand, ValueEnum};
use crate::error::Result;
use crate::output::{print_error, print_progress, print_success, SpeedTestResult};
use crate::speedtest::SpeedTest;

#[derive(Parser, Debug)]
#[command(name = "speedtest-cli")]
#[command(about = "A CLI tool to test internet speed", long_about = None)]
pub struct Cli {
    #[command(subcommand)]
    pub command: Commands,

    #[arg(short, long, value_enum, default_value_t = SpeedUnit::Mbps, global = true)]
    pub unit: SpeedUnit,

    #[arg(short, long, global = true)]
    pub verbose: bool,
}

impl Cli {
    pub async fn execute(&self) -> Result<()> {
        let speed_test = SpeedTest::new();
        let result = match self.command {
            Commands::Start => {
                print_progress("Running full speed test (download + upload)...");
                run_full_test(&speed_test).await
            }
            Commands::Download => {
                print_progress("Running download speed test...");
                run_download_test(&speed_test).await
            }
            Commands::Upload => {
                print_progress("Running upload speed test...");
                run_upload_test(&speed_test).await
            }
        };

        match result {
            Ok(test_result) => {
                print_success(test_result, self.unit);
                log::info!("Speed test completed successfully");
                Ok(())
            }
            Err(e) => {
                print_error(&e);
                Err(e)
            }
        }
    }
}

#[derive(Subcommand, Debug)]
pub enum Commands {
    #[command(about = "Run both download and upload speed tests")]
    Start,

    #[command(about = "Run download speed test only")]
    Download,

    #[command(about = "Run upload speed test only")]
    Upload,
}

#[derive(Debug, Clone, Copy, ValueEnum)]
pub enum SpeedUnit {
    #[value(name = "bps")]
    Bps,
    #[value(name = "kbps")]
    Kbps,
    #[value(name = "mbps")]
    Mbps,
    #[value(name = "gbps")]
    Gbps,
}

impl SpeedUnit {
    pub fn convert(&self, bytes_per_second: f64) -> f64 {
        let bits_per_second = bytes_per_second * 8.0;
        match self {
            SpeedUnit::Bps => bits_per_second,
            SpeedUnit::Kbps => bits_per_second / 1_000.0,
            SpeedUnit::Mbps => bits_per_second / 1_000_000.0,
            SpeedUnit::Gbps => bits_per_second / 1_000_000_000.0,
        }
    }

    pub fn as_str(&self) -> &str {
        match self {
            SpeedUnit::Bps => "bps",
            SpeedUnit::Kbps => "Kbps",
            SpeedUnit::Mbps => "Mbps",
            SpeedUnit::Gbps => "Gbps",
        }
    }
}

async fn run_full_test(speed_test: &SpeedTest) -> Result<SpeedTestResult> {
    let (download, upload) = speed_test.test_both().await?;
    Ok(SpeedTestResult::new(Some(download), Some(upload)))
}

async fn run_download_test(speed_test: &SpeedTest) -> Result<SpeedTestResult> {
    let download = speed_test.test_download().await?;
    Ok(SpeedTestResult::new(Some(download), None))
}

async fn run_upload_test(speed_test: &SpeedTest) -> Result<SpeedTestResult> {
    let upload = speed_test.test_upload().await?;
    Ok(SpeedTestResult::new(None, Some(upload)))
}