meansd-cli 1.4.0

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

use clap::builder::EnumValueParser;
use clap::{crate_description, crate_name, crate_version};
use clap::{value_parser, Arg, ArgAction, Command};

use crate::config::ErrorHandler;

pub fn build() -> Command {
    // ------------------------------------------------------------------------
    // arguments
    // ------------------------------------------------------------------------

    let bin_breaks = Arg::new("bin_breaks")
        .value_delimiter(',')
        .action(ArgAction::Append)
        .short('b')
        .long("bin-breaks")
        .value_name("break,...")
        .help("group in bins with given boundaries")
        .long_help(
"Group output in bins with given breaks/boundaries. Specify comma-separated \
 values to define the boundaries of the bins. Empty bins will not be shown."
        )
        .conflicts_with("bin_width")
        .value_parser(value_parser!(f64));

    let bin_width = Arg::new("bin_width")
        .short('w')
        .long("bin-width")
        .value_name("width")
        .help("group in bins of width size")
        .long_help(
"Group output in bins of width size. Empty bins will not be shown."
        )
        .conflicts_with("bin_breaks")
        .value_parser(value_parser!(NonZeroU32));

    let handler = Arg::new("handler")
        .long("error-handler")
        .hide_short_help(true)
        .long_help(
"Specify error handler when input line is not a number. Panic will report \
 the error and quit with failure. Skip will completely ignore the line \
 without printing anything. Warn will ignore just like Skip but show a \
 warning for each problematic line.",
        )
        .ignore_case(true)
        .value_parser(EnumValueParser::<ErrorHandler>::new())
        .default_value("warn");

    let progress = Arg::new("progress")
        .long("progress")
        .value_name("n")
        .hide_short_help(true)
        .long_help("Print progress every n numbers to STDERR.")
        .value_parser(value_parser!(u32));

    // ------------------------------------------------------------------------
    // population vs sample
    // ------------------------------------------------------------------------

    let population = Arg::new("population")
        .long("population")
        .action(ArgAction::SetTrue)
        .hide_short_help(true)
        .long_help("Show population standard deviation.")
        .overrides_with("sample")
        .display_order(0);

    let sample = Arg::new("sample")
        .long("sample")
        .action(ArgAction::SetTrue)
        .hide_short_help(true)
        .long_help("Show sample standard deviation (default).")
        .overrides_with("population")
        .display_order(0);

    // ------------------------------------------------------------------------
    // put it all together
    // ------------------------------------------------------------------------

    let help = Arg::new("help")
        .short('?')
        .long("help")
        .help("print help (use --help to see all options)")
        .long_help("Print help.")
        .action(ArgAction::Help);

    let version = Arg::new("version")
        .long("version")
        .help("print version")
        .long_help("Print version.")
        .action(ArgAction::Version);

    Command::new(crate_name!())
        .version(crate_version!())
        .about(crate_description!())
        .disable_help_flag(true)
        .disable_version_flag(true)
        .arg(bin_breaks)
        .arg(bin_width)
        .arg(handler)
        .arg(progress)
        .arg(population)
        .arg(sample)
        .arg(help)
        .arg(version)
}

// ----------------------------------------------------------------------------
// tests
// ----------------------------------------------------------------------------

#[cfg(test)]
mod test {
    #[test]
    fn verify_cli() {
        super::build().debug_assert();
    }
}