meansd-cli 1.3.0

calculate mean and standard deviation (CLI)
use atty::Stream;
use clap::{crate_description, crate_name, crate_version};
use clap::{App, AppSettings, Arg};

use crate::config::ErrorHandler;

pub fn build() -> App<'static, 'static> {
    let color = if atty::is(Stream::Stdout) {
        AppSettings::ColoredHelp
    } else {
        AppSettings::ColorNever
    };

    // ------------------------------------------------------------------------
    // arguments
    // ------------------------------------------------------------------------

    let bin_breaks = Arg::with_name("bin_breaks")
        .takes_value(true)
        .multiple(true)
        .min_values(2)
        .use_delimiter(true)
        .short("b")
        .long("bin-breaks")
        .value_name("breaks")
        .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")
        .validator(is_f64);

    let bin_width = Arg::with_name("bin_width")
        .takes_value(true)
        .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")
        .validator(is_non_zero_u32);

    let handler = Arg::with_name("handler")
        .long("error-handler")
        .hidden_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.",
        )
        .takes_value(true)
        .case_insensitive(true)
        .possible_values(&ErrorHandler::variants())
        .default_value("Warn");

    let progress = Arg::with_name("progress")
        .takes_value(true)
        .long("progress")
        .value_name("n")
        .hidden_short_help(true)
        .long_help("Print progress every n numbers to STDERR.")
        .validator(is_u32);

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

    let population = Arg::with_name("population")
        .takes_value(false)
        .multiple(true)
        .long("population")
        .hidden_short_help(true)
        .long_help("Show population standard deviation.")
        .overrides_with("sample")
        .display_order(0);

    let sample = Arg::with_name("sample")
        .takes_value(false)
        .multiple(true)
        .long("sample")
        .hidden_short_help(true)
        .long_help("Show sample standard deviation (default).")
        .overrides_with("population")
        .display_order(0);

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

    App::new(crate_name!())
        .version(crate_version!())
        .about(crate_description!())
        .global_setting(color)
        .help_short("?")
        .help_message("show this help output")
        .version_message("show version")
        .arg(bin_breaks)
        .arg(bin_width)
        .arg(handler)
        .arg(progress)
        .arg(population)
        .arg(sample)
}

// [clippy] can't fix, due to clap validator API, will change with clap 3
#[allow(clippy::needless_pass_by_value)]
fn is_non_zero_u32(s: String) -> Result<(), String> {
    if s.parse::<std::num::NonZeroU32>().is_ok() {
        Ok(())
    } else {
        Err(format!("not non-zero u32: {}", s))
    }
}

// [clippy] can't fix, due to clap validator API, will change with clap 3
#[allow(clippy::needless_pass_by_value)]
fn is_f64(s: String) -> Result<(), String> {
    if s.parse::<f64>().is_ok() {
        Ok(())
    } else {
        Err(format!("not f64: {}", s))
    }
}

// [clippy] can't fix, due to clap validator API, will change with clap 3
#[allow(clippy::needless_pass_by_value)]
fn is_u32(s: String) -> Result<(), String> {
    if s.parse::<u32>().is_ok() {
        Ok(())
    } else {
        Err(format!("not u32: {}", s))
    }
}