simplestats 0.1.3

Generate simple statistics on line delimited numbers
use atty::Stream;
use clap::Clap;
use cli_table::{print_stdout, Color, Table, WithTitle};
use std::fs::read_to_string;

const VERSION: &'static str = env!("CARGO_PKG_VERSION");

#[derive(Clap, Debug)]
#[clap(version = VERSION, author = "Takashi I. <mail@takashiidobe.com>")]
struct Opts {
    file_path: String,
}

fn stddev(nums: &[i64]) -> Option<f64> {
    match (mean(nums), nums.len()) {
        (Some(nums_mean), count) if count > 0 => {
            let variance = nums
                .iter()
                .map(|value| {
                    let diff = nums_mean - (*value as f64);

                    diff * diff
                })
                .sum::<f64>()
                / count as f64;

            Some(variance.sqrt())
        }
        _ => None,
    }
}

fn mean(nums: &[i64]) -> Option<f64> {
    let sum = nums.iter().sum::<i64>() as f64;
    let count = nums.len();

    match count {
        positive if positive > 0 => Some(sum / count as f64),
        _ => None,
    }
}

#[derive(Debug, Table)]
struct Stats {
    #[table(color = "Color::Red")]
    max: i64,
    #[table(color = "Color::Green")]
    min: i64,
    #[table(color = "Color::Cyan")]
    total: i64,
    #[table(color = "Color::Magenta")]
    count: usize,
    #[table(color = "Color::Yellow")]
    mean: f64,
    #[table(color = "Color::Rgb(173, 215, 230)")]
    stddev: f64,
}

impl Stats {
    fn new(nums: &[i64]) -> Stats {
        let total: i64 = nums.iter().sum();
        let min = *nums.iter().min().unwrap();
        let max = *nums.iter().max().unwrap();
        let count = nums.len();
        let mean = mean(nums).unwrap();
        let stddev = stddev(nums).unwrap();
        Stats {
            max,
            min,
            total,
            count,
            mean,
            stddev,
        }
    }
}

fn print_simple(stats: &Stats) {
    println!("max min total count mean stddev");
    println!(
        "{} {} {} {} {} {}",
        stats.max, stats.min, stats.total, stats.count, stats.mean, stats.stddev
    );
}

fn main() {
    let opts = Opts::parse();

    let str_file = read_to_string(&opts.file_path).unwrap();

    let nums: Vec<i64> = str_file.lines().map(|s| s.parse().unwrap()).collect();

    let stats = Stats::new(&nums);
    if atty::is(Stream::Stdout) {
        let _ = print_stdout(vec![stats].with_title());
    } else {
        print_simple(&stats);
    }
}