#![forbid(unsafe_code)]
#![deny(clippy::all)]
#![warn(clippy::pedantic, clippy::nursery, clippy::cargo)]
mod cli;
mod config;
mod log;
use std::io::{self, IsTerminal};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use prettytable::format::FormatBuilder;
use prettytable::{cell, Row, Table};
use smooth::Smooth;
use meansd::binned::{self, Bin, Binned};
use meansd::MeanSD;
use crate::config::Config;
use crate::config::ErrorHandler;
use crate::config::StandardDeviationMode;
fn main() {
let args = cli::build().get_matches();
let config = Config::from_args(&args);
if io::stdin().is_terminal() {
log::warn("input is read from the terminal");
log::warn("pipe data into this app");
log::warn("press CTRL-D to exit");
};
let numbers = io::stdin()
.lines()
.filter_map(|line| line_to_number(line, &config));
if let Some(breaks) = &config.bin_breaks {
let mut binned = binned::Breaks::new(breaks);
binned.update_all(numbers, &config);
binned.print(&config);
} else if let Some(width) = &config.bin_width {
let mut binned = binned::Width::new(*width);
binned.update_all(numbers, &config);
binned.print(&config);
} else {
meansd(numbers, &config);
};
}
fn line_to_number(line: io::Result<String>, config: &Config) -> Option<f64> {
let line = line.unwrap();
let line = line.trim();
match line.parse::<f64>() {
Ok(v) => Some(v),
Err(e) => match config.error_handler {
ErrorHandler::Panic => {
log::error(format!("{e}: {line}"));
std::process::exit(1);
}
ErrorHandler::Skip => None,
ErrorHandler::Warn => {
log::warn(format!("{e}: {line}"));
None
}
},
}
}
fn meansd<I>(numbers: I, config: &Config)
where
I: Iterator<Item = f64>,
{
let mut meansd = MeanSD::default();
let interrupt = Arc::new(AtomicBool::new(false));
let interrupt_setter = interrupt.clone();
ctrlc::set_handler(move || {
interrupt_setter.store(true, Ordering::SeqCst);
})
.expect("error setting interrupt handler");
for x in numbers {
meansd.update(x);
if interrupt.load(Ordering::SeqCst) {
println!();
break;
}
if config.progress.map_or(false, |p| meansd.size() % p == 0.0) {
log::progress(
meansd.size(),
meansd.formatted(StandardDeviationMode::Sample),
);
}
}
println!("{}", meansd.formatted(config.sd_mode));
}
trait MeanSDExt {
fn formatted(&self, sd_mode: StandardDeviationMode) -> String;
fn stdev(&self, sd_mode: StandardDeviationMode) -> f64;
}
impl MeanSDExt for MeanSD {
fn formatted(&self, sd_mode: StandardDeviationMode) -> String {
format!(
"n={} \u{2205} {} \u{b1} {}",
self.size(),
self.mean().smooth(),
self.stdev(sd_mode).smooth(),
)
}
fn stdev(&self, sd_mode: StandardDeviationMode) -> f64 {
match sd_mode {
StandardDeviationMode::Population => self.pstdev(),
StandardDeviationMode::Sample => self.sstdev(),
}
}
}
trait BinExt {
fn bottom_str(&self) -> String;
fn top_str(&self) -> String;
}
impl BinExt for Bin {
fn bottom_str(&self) -> String {
if self.bottom() == f64::NEG_INFINITY {
"-\u{221e}".to_string()
} else {
format!("{}", self.bottom())
}
}
fn top_str(&self) -> String {
if self.top() == f64::INFINITY {
"\u{221e}".to_string()
} else {
format!("{}", self.top())
}
}
}
trait BinnedExt: Binned {
fn update_all<I>(&mut self, numbers: I, config: &Config)
where
I: Iterator<Item = f64>,
{
let interrupt = Arc::new(AtomicBool::new(false));
let interrupt_setter = interrupt.clone();
ctrlc::set_handler(move || {
interrupt_setter.store(true, Ordering::SeqCst);
})
.expect("error setting interrupt handler");
for x in numbers {
self.update(x);
if interrupt.load(Ordering::SeqCst) {
println!();
break;
}
if config
.progress
.map_or(false, |p| self.all().size() % p == 0.0)
{
for (bin, meansd) in self.data() {
let size = self.all().size();
log::progress(
size,
format!(
"{} - {} \u{2192} {}",
bin.bottom_str(),
bin.top_str(),
meansd.formatted(StandardDeviationMode::Sample),
),
);
}
}
}
}
fn print(&self, config: &Config) {
let mut table = Table::new();
let format = FormatBuilder::new().column_separator(' ').build();
table.set_format(format);
let mut titles = Row::empty();
titles.add_cell(cell!(bu->"From"));
titles.add_cell(cell!(bur->"To"));
titles.add_cell(cell!(bur->"Size"));
titles.add_cell(cell!(bur->"Mean"));
titles.add_cell(cell!(bur->"SD"));
table.set_titles(titles);
for (bin, meansd) in self.data() {
let size = meansd.size();
let mean = meansd.mean().smooth();
let stdev = meansd.stdev(config.sd_mode).smooth();
let mut row = Row::empty();
row.add_cell(cell!(r->bin.bottom_str()));
row.add_cell(cell!(r->bin.top_str()));
row.add_cell(cell!(r->size));
row.add_cell(cell!(r->mean));
row.add_cell(cell!(r->stdev));
table.add_row(row);
}
println!();
table.printstd();
println!();
println!("{}", self.all().formatted(config.sd_mode));
}
}
impl BinnedExt for meansd::binned::Breaks {}
impl BinnedExt for meansd::binned::Width {}