use cgats::*; use deltae::DEMethod; use std::{ fmt, fs::File, io::Write, path::{Path, PathBuf}, process::exit, }; mod cli; macro_rules! err { ($e:expr) => { Err($e.into()) } } fn files_cgats(paths: I) -> Vec<(PathBuf, Cgats)> where I: Iterator, P: AsRef, { let mut map = Vec::new(); for arg in paths { let path = Path::new(arg.as_ref()).file_name().unwrap().to_str().unwrap(); match Cgats::from_file(&arg) { Ok(cgats) => { map.push((PathBuf::from(arg.as_ref()).canonicalize().unwrap(), cgats)); } Err(e) => eprintln!("{path}: Error\n\t{e}"), } } map } fn write_it(data: D, places: usize, file: &mut Option) -> Result<()> { match file { Some(file) => { writeln!(file, "{data:places$}")?; } None => println!("{data:places$}"), } Ok(()) } fn main() -> Result<()> { let matches = cli::app().get_matches(); let places = matches.value_of("precision") .expect("invalid decimal places") .parse::()?; let output_file = matches.value_of("output-file"); match matches.subcommand() { Some((subcmd, matches)) => { let mut map = files_cgats(matches.values_of("files").unwrap_or_default()); let output_file = &mut matches.value_of("output-file") .and_then(|file| File::create(file).ok()) .or_else(|| File::create(output_file?).ok()); match subcmd { "average" => { let avg = map.iter() .map(|(_path, cgats)| cgats).partial_avg() .ok_or(format!("unable to average {} files", map.len()))?; write_it(&avg, places, output_file)?; } "delta" => { let method = matches.value_of("method") .expect("delta method") .parse::() .expect("delta method parse"); match (map.get(0), map.get(1)) { (Some((_path0, cgats0)), Some((_path1, cgats1))) => { let pct = matches.value_of("percentile").unwrap().parse::().unwrap() / 100.0; if matches.is_present("report") { let report = cgats0.de_report(cgats1, method, pct)?; write_it(&report, places, output_file)?; } else { let delta = cgats0.delta(cgats1, method)?; write_it(&delta, places, output_file)?; } } _ => return err!("unable to parse 2 files for delta comparison"), } } "concatenate" => { match (map.get(0), map.get(1..)) { (Some((_path0, root)), Some(the_rest)) => { let cgats = Cgats::concatenate(root, the_rest.iter().map(|(_, cgats)| cgats))?; write_it(&cgats, places, output_file)?; } _ => return err!("unable to parse multiple files for concatenation") } } "print" => for (path, cgats) in map.iter_mut() { if matches.is_present("reindex") { cgats.reindex_sample_id_at(1); } let display = format!("==> {} <==\n{}", path.display(), cgats); write_it(display, places, output_file)?; } "info" => for (path, cgats) in map.iter() { write_it(info(path, cgats), places, output_file)?; } "colorburst" => { let avg = map.iter() .map(|(_path, cgats)| cgats).partial_avg() .ok_or(format!("unable to average {} files", map.len()))? .to_colorburst()?; if matches.is_present("to-cgats") { let cgats = avg.colorburst_to_cgats()?; write_it(cgats, places, output_file)?; } else { write_it(avg, places, output_file)?; } } "transpose" => { match map.get(0) { Some((_path, cgats)) => { if let Some(Ok(width)) = matches.value_of("width") .or_else(|| cgats.get_metadata("LGOROWLENGTH")) .map(|width| width.parse::()) { let mut cgats = cgats.clone(); cgats.transpose_chart(width); if matches.is_present("reindex") { cgats.reindex_sample_id_at(1); } write_it(cgats, places, output_file)?; } else { return err!("unable to determine row length"); } } None => return err!("unable to parse file for transpose") } } sub => unreachable!("subcommand: {sub}") } } None => { if matches.values_of("files").is_none() { cli::app().print_help()?; exit(1); } for (path, cgats) in files_cgats(matches.values_of("files") .unwrap_or_default()) .iter() { let output_file = &mut output_file.and_then(|file| File::create(file).ok()); write_it(info(path, cgats), places, output_file)?; } } } Ok(()) } fn info>(path: P, cgats: &Cgats) -> String { let display: String = path.as_ref().file_name() .map(|os_str| os_str.to_string_lossy().to_string()) .unwrap_or_else(|| path.as_ref().display().to_string()); format!("{}: {}", display, cgats.summary()) }