use std::fs::{self, File};
use std::io::Write;
use std::path::Path;
use crate::binning::{FinalResult, Histogram, PerBaseHistogram};
fn write_histogram_csv<const N: usize>(
out_dir: impl AsRef<Path>,
name: &str,
hist: &Histogram<N>,
) -> anyhow::Result<()> {
let path = out_dir.as_ref().join(format!("{name}.csv"));
let mut f = File::create(path)?;
writeln!(f, "bin_idx,dw,ar")?;
for i in 0..N {
writeln!(f, "{},{},{}", i, hist.dw[i], hist.ar[i])?;
}
Ok(())
}
fn write_per_base_csv<const N: usize>(
out_dir: &Path,
name: &str,
pb: &PerBaseHistogram<N>,
) -> anyhow::Result<()> {
let path = out_dir.join(format!("{name}.csv"));
let mut f = File::create(path)?;
writeln!(f, "base,bin_idx,dw,ar")?;
for base in "ACGT".chars() {
let hist = pb.hist_grams.get(&base).unwrap();
for i in 0..N {
writeln!(f, "{},{},{},{}", base, i, hist.dw[i], hist.ar[i])?;
}
}
Ok(())
}
pub fn write_final_result_csv<const N: usize>(
out_dir: &Path,
result: &FinalResult<N>,
) -> anyhow::Result<()> {
fs::create_dir_all(out_dir)?;
if let Some(hist) = &result.total_hist {
write_histogram_csv(out_dir, "total_hist", hist)?;
}
if let Some(pb) = &result.total_per_base_hist {
write_per_base_csv(out_dir, "total_per_base_hist", pb)?;
}
if let Some(hist) = &result.first_n_total_hist {
write_histogram_csv(out_dir, "first_n_total_hist", hist)?;
}
if let Some(pb) = &result.first_n_per_base_hist {
write_per_base_csv(out_dir, "first_n_per_base_hist", pb)?;
}
if let Some(hist) = &result.last_n_total_hist {
write_histogram_csv(out_dir, "last_n_total_hist", hist)?;
}
if let Some(pb) = &result.last_n_per_base_hist {
write_per_base_csv(out_dir, "last_n_per_base_hist", pb)?;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::binning::{FinalResult, Histogram, PerBaseHistogram};
fn setup_temp(name: &str) -> std::path::PathBuf {
let dir = std::env::temp_dir().join(format!("md_analysis_test_writer/{name}"));
let _ = fs::remove_dir_all(&dir);
fs::create_dir_all(&dir).unwrap();
dir
}
#[test]
fn writes_some_fields_skips_none() {
let out = setup_temp("some_none");
let r: FinalResult<5> = FinalResult::new_first_n_last_n(); write_final_result_csv(&out, &r).unwrap();
assert!(out.join("first_n_total_hist.csv").exists());
assert!(out.join("first_n_per_base_hist.csv").exists());
assert!(out.join("last_n_total_hist.csv").exists());
assert!(out.join("last_n_per_base_hist.csv").exists());
assert!(!out.join("total_hist.csv").exists());
}
#[test]
fn histogram_csv_format() {
let out = setup_temp("histogram_format");
let mut h: Histogram<5> = Histogram::new();
h.dw[1] = 42;
h.ar[3] = 7;
write_histogram_csv(&out, "test_hist", &h).unwrap();
let content = fs::read_to_string(out.join("test_hist.csv")).unwrap();
assert!(content.starts_with("bin_idx,dw,ar\n"));
assert!(content.contains("1,42,0"));
}
#[test]
fn per_base_csv_format() {
let out = setup_temp("per_base_format");
let mut pb: PerBaseHistogram<5> = PerBaseHistogram::new();
for base in "ACGT".chars() {
pb.hist_grams.get_mut(&base).unwrap().dw[2] = 7;
}
write_per_base_csv(&out, "test_pb", &pb).unwrap();
let content = fs::read_to_string(out.join("test_pb.csv")).unwrap();
assert!(content.starts_with("base,bin_idx,dw,ar\n"));
assert!(content.contains("A,2,7,0"));
assert!(content.contains("T,2,7,0"));
}
#[test]
fn skips_none_fields() {
let out = setup_temp("skips_none");
let r: FinalResult<5> = FinalResult::new_all(); write_final_result_csv(&out, &r).unwrap();
assert!(out.join("total_hist.csv").exists());
assert!(!out.join("first_n_total_hist.csv").exists());
}
}