meansd-cli 1.4.0

calculate mean and standard deviation (CLI)
use std::num::NonZeroU32;
use std::str::FromStr;

use anyhow::anyhow;
use clap::builder::PossibleValue;
use clap::{ArgMatches, ValueEnum};
use ordered_float::OrderedFloat;

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ErrorHandler {
    Panic,
    Skip,
    Warn,
}

impl FromStr for ErrorHandler {
    type Err = anyhow::Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s.to_lowercase().as_str() {
            "panic" => Ok(Self::Panic),
            "skip" => Ok(Self::Skip),
            "warn" => Ok(Self::Warn),
            _ => Err(anyhow!("invalid value for error handler")),
        }
    }
}

impl ValueEnum for ErrorHandler {
    fn value_variants<'a>() -> &'a [Self] {
        &[Self::Panic, Self::Skip, Self::Warn]
    }

    fn to_possible_value(&self) -> Option<PossibleValue> {
        Some(match self {
            Self::Panic => PossibleValue::new("panic"),
            Self::Skip => PossibleValue::new("skip"),
            Self::Warn => PossibleValue::new("warn"),
        })
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum StandardDeviationMode {
    Population,
    Sample,
}

#[derive(Clone, Debug, PartialEq)]
pub struct Config {
    pub error_handler: ErrorHandler,
    pub progress: Option<f64>,
    pub bin_width: Option<NonZeroU32>,
    pub bin_breaks: Option<Vec<f64>>,
    pub sd_mode: StandardDeviationMode,
}

impl Config {
    pub fn from_args(args: &ArgMatches) -> Self {
        // UNWRAP has default
        let error_handler = *args.get_one::<ErrorHandler>("handler").unwrap();

        let progress = args.get_one::<u32>("progress").copied().map(f64::from);

        let bin_width = args.get_one::<NonZeroU32>("bin_width").copied();

        let bin_breaks = args.get_many::<f64>("bin_breaks").map(|breaks| {
            let mut breaks: Vec<OrderedFloat<f64>> =
                breaks.map(|value| OrderedFloat(*value)).collect();
            breaks.sort_unstable();
            breaks.into_iter().map(|value| value.0).collect()
        });

        let population = args.get_flag("population");
        let sample = args.get_flag("sample");

        let sd_mode = match (population, sample) {
            (true, false) => StandardDeviationMode::Population,
            (false, _) => StandardDeviationMode::Sample,
            (true, true) => unreachable!("flags override each other"),
        };

        Self {
            error_handler,
            progress,
            bin_width,
            bin_breaks,
            sd_mode,
        }
    }
}