use std::fs::File;
use std::io::Write;
use std::path::Path;
use crate::{PafRecord, Result, Tag, Type};
pub struct Writer<W: Write> {
writer: W,
}
impl Writer<File> {
pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Writer<File>> {
let file = File::create(path)?;
Ok(Writer::new(file))
}
}
impl<W: Write> Writer<W> {
pub fn new(writer: W) -> Self {
Writer { writer }
}
pub fn write_record(&mut self, record: &PafRecord) -> Result<()> {
write!(
self.writer,
"{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}",
record.query_name(),
record.query_len(),
record.query_start(),
record.query_end(),
record.strand(),
record.target_name(),
record.target_len(),
record.target_start(),
record.target_end(),
record.residue_matches(),
record.alignment_block_len(),
record.mapping_quality(),
)?;
for (key, tag) in record.optional_fields() {
match tag {
Tag::tp(value) => write_optional_field(&mut self.writer, key, value)?,
Tag::cm(value) => write_optional_field(&mut self.writer, key, value)?,
Tag::s1(value) => write_optional_field(&mut self.writer, key, value)?,
Tag::s2(value) => write_optional_field(&mut self.writer, key, value)?,
Tag::NM(value) => write_optional_field(&mut self.writer, key, value)?,
Tag::MD(value) => write_optional_field(&mut self.writer, key, value)?,
Tag::AS(value) => write_optional_field(&mut self.writer, key, value)?,
Tag::SA(value) => write_optional_field(&mut self.writer, key, value)?,
Tag::ms(value) => write_optional_field(&mut self.writer, key, value)?,
Tag::nn(value) => write_optional_field(&mut self.writer, key, value)?,
Tag::ts(value) => write_optional_field(&mut self.writer, key, value)?,
Tag::cg(value) => write_optional_field(&mut self.writer, key, value)?,
Tag::cs(value) => write_optional_field(&mut self.writer, key, value)?,
Tag::dv(value) => write_optional_field(&mut self.writer, key, value)?,
Tag::de(value) => write_optional_field(&mut self.writer, key, value)?,
Tag::rl(value) => write_optional_field(&mut self.writer, key, value)?,
Tag::zd(value) => write_optional_field(&mut self.writer, key, value)?,
}
}
writeln!(self.writer).map_err(Into::into)
}
}
fn write_optional_field<W: Write>(writer: &mut W, tag: &str, value: &Type) -> Result<()> {
match value {
Type::Int(v) => write!(writer, "\t{}:i:{}", tag, v).map_err(Into::into),
Type::Float(v) => write!(writer, "\t{}:f:{:.4}", tag, v).map_err(Into::into),
Type::String(v) => write!(writer, "\t{}:Z:{}", tag, v).map_err(Into::into),
Type::Char(v) => write!(writer, "\t{}:A:{}", tag, v).map_err(Into::into),
}
}
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use super::*;
use crate::{PafRecord, Tag, Type};
#[test]
fn test_write_record_mandatory_fields() {
let mut buffer = Vec::new();
let mut writer = Writer::new(&mut buffer);
let record = PafRecord::new(
"query1".to_owned(),
1000,
100,
500,
'+',
"target1".to_owned(),
1500,
200,
600,
300,
400,
60,
HashMap::new(),
);
writer.write_record(&record).unwrap();
let output = String::from_utf8(buffer).unwrap();
assert_eq!(
output,
"query1\t1000\t100\t500\t+\ttarget1\t1500\t200\t600\t300\t400\t60\n"
);
}
#[test]
fn test_write_record_with_optional_fields() {
let mut buffer = Vec::new();
let mut writer = Writer::new(&mut buffer);
let mut optional_fields = HashMap::new();
optional_fields.insert("tp".to_string(), Tag::tp(Type::Char('P')));
optional_fields.insert("cm".to_string(), Tag::cm(Type::Int(42)));
optional_fields.insert("s1".to_string(), Tag::s1(Type::Int(99)));
let record = PafRecord::new(
"query2".to_owned(),
2000,
150,
900,
'-',
"target2".to_owned(),
2500,
300,
1000,
400,
800,
70,
optional_fields,
);
writer.write_record(&record).unwrap();
let output = String::from_utf8(buffer).unwrap();
assert!(
output.contains("query2\t2000\t150\t900\t-\ttarget2\t2500\t300\t1000\t400\t800\t70")
);
assert!(output.contains("\ttp:A:P"));
assert!(output.contains("\tcm:i:42"));
assert!(output.contains("\ts1:i:99"));
}
}