use std::marker::Copy;
use std::ops::Deref;
use bio_types::annot;
use bio_types::annot::loc::Loc;
use bio_types::strand;
use getset::{CloneGetters, CopyGetters, Getters, MutGetters, Setters, WithSetters};
use crate::io::common;
pub type Writer<W> = common::Writer<W>;
pub type Reader<R> = common::Reader<R, Record>;
#[derive(
Clone,
Eq,
PartialEq,
Ord,
PartialOrd,
Hash,
Debug,
Default,
Serialize,
Deserialize,
Getters,
Setters,
WithSetters,
MutGetters,
CopyGetters,
CloneGetters,
)]
pub struct Record {
#[getset(get_mut = "pub", set_with = "pub")]
chrom1: String,
#[getset(get_copy = "pub", set = "pub", get_mut = "pub", set_with = "pub")]
start1: u64,
#[getset(get_copy = "pub", set = "pub", get_mut = "pub", set_with = "pub")]
end1: u64,
#[getset(get_mut = "pub", set_with = "pub")]
chrom2: String,
#[getset(get_copy = "pub", set = "pub", get_mut = "pub", set_with = "pub")]
start2: u64,
#[getset(get_copy = "pub", set = "pub", get_mut = "pub", set_with = "pub")]
end2: u64,
#[serde(default)]
aux: Vec<String>,
}
impl Record {
pub fn new() -> Self {
Self::default()
}
pub fn chrom1(&self) -> &str {
&self.chrom1
}
pub fn chrom2(&self) -> &str {
&self.chrom2
}
pub fn aux(&self, i: usize) -> Option<&str> {
let j = i - 6;
if j < self.aux.len() {
Some(&self.aux[j])
} else {
None
}
}
pub fn name(&self) -> Option<&str> {
self.aux(6)
}
pub fn score(&self) -> Option<&str> {
self.aux(7)
}
pub fn strand1(&self) -> Option<strand::Strand> {
match self.aux(8) {
Some("+") => Some(strand::Strand::Forward),
Some("-") => Some(strand::Strand::Reverse),
_ => None,
}
}
pub fn strand2(&self) -> Option<strand::Strand> {
match self.aux(9) {
Some("+") => Some(strand::Strand::Forward),
Some("-") => Some(strand::Strand::Reverse),
_ => None,
}
}
}
impl Record {
pub fn set_chrom1(&mut self, chrom: &str) {
self.chrom1 = chrom.into()
}
pub fn set_chrom2(&mut self, chrom: &str) {
self.chrom2 = chrom.into()
}
pub fn set_aux(&mut self, i: usize, field: &str) {
let j = i - 6;
if j < self.aux.len() {
self.aux[j] = field.into()
} else {
for _ in self.aux.len()..j {
self.aux.push("".to_owned())
}
self.aux.push(field.into())
}
}
pub fn set_name(&mut self, name: &str) {
self.set_aux(6, name)
}
pub fn set_score(&mut self, score: &str) {
self.set_aux(7, score)
}
pub fn set_strand1(&mut self, strand1: strand::Strand) {
self.set_aux(8, strand1.strand_symbol());
}
pub fn set_strand2(&mut self, strand2: strand::Strand) {
self.set_aux(9, strand2.strand_symbol());
}
pub fn push_aux(&mut self, field: &str) {
self.aux.push(field.into())
}
}
impl<R, S> From<(annot::pos::Pos<R, S>, annot::pos::Pos<R, S>)> for Record
where
R: Deref<Target = str>,
S: Into<strand::Strand> + Copy,
{
fn from((pos1, pos2): (annot::pos::Pos<R, S>, annot::pos::Pos<R, S>)) -> Self {
let mut bedpe = Record::new();
bedpe.set_chrom1(pos1.refid());
bedpe.set_start1(pos1.pos() as u64);
bedpe.set_end1((pos1.pos() + 1) as u64);
bedpe.set_chrom2(pos2.refid());
bedpe.set_start2(pos2.pos() as u64);
bedpe.set_end2((pos2.pos() + 1) as u64);
bedpe.set_name("");
bedpe.set_score("0");
bedpe.set_strand1(pos1.strand().into());
bedpe.set_strand2(pos2.strand().into());
bedpe
}
}
#[cfg(test)]
mod tests {
use super::*;
use bio_types::annot::pos::Pos;
use bio_types::strand::{ReqStrand, Strand};
use std::path::Path;
const BEDPE_FILE: &[u8] = b"\
1\t5\t5000\t1\t6000\t10000\tname1\tup\t-\t+
2\t3\t5005\t2\t10\t5500\tname2\tup\t.\t.
";
const BEDPE_FILE_COMMENT: &[u8] = b"\
# this line should be ignored
1\t5\t5000\t1\t6000\t10000\tname1\tup
# and this one as well
2\t3\t5005\t2\t10\t5500\tname2\tup
";
const BEDPE_FILE_COMPACT: &[u8] = b"1\t5\t5000\t1\t6000\t10000\n2\t3\t5005\t2\t10\t5500\n";
#[test]
fn test_reader() {
let chroms = [("1", "1"), ("2", "2")];
let starts = [(5, 6000), (3, 10)];
let ends = [(5000, 10000), (5005, 5500)];
let names = ["name1", "name2"];
let scores = ["up", "up"];
let strands = [(Some(Strand::Reverse), Some(Strand::Forward)), (None, None)];
let mut reader = Reader::new(BEDPE_FILE);
for (i, r) in reader.records().enumerate() {
let record = r.expect("Error reading record");
assert_eq!((record.chrom1(), record.chrom2()), chroms[i]);
assert_eq!((record.start1(), record.start2()), starts[i]);
assert_eq!((record.end1(), record.end2()), ends[i]);
assert_eq!(record.name().expect("Error reading name"), names[i]);
assert_eq!(record.score().expect("Error reading score"), scores[i]);
assert_eq!((record.strand1(), record.strand2()), strands[i]);
}
}
#[test]
fn test_reader_with_comment() {
let chroms = [("1", "1"), ("2", "2")];
let starts = [(5, 6000), (3, 10)];
let ends = [(5000, 10000), (5005, 5500)];
let names = ["name1", "name2"];
let scores = ["up", "up"];
let mut reader = Reader::new(BEDPE_FILE_COMMENT);
for (i, r) in reader.records().enumerate() {
let record = r.expect("Error reading record");
assert_eq!((record.chrom1(), record.chrom2()), chroms[i]);
assert_eq!((record.start1(), record.start2()), starts[i]);
assert_eq!((record.end1(), record.end2()), ends[i]);
assert_eq!(record.name().expect("Error reading name"), names[i]);
assert_eq!(record.score().expect("Error reading score"), scores[i]);
}
}
#[test]
fn test_reader_compact() {
let chroms = [("1", "1"), ("2", "2")];
let starts = [(5, 6000), (3, 10)];
let ends = [(5000, 10000), (5005, 5500)];
let mut reader = Reader::new(BEDPE_FILE_COMPACT);
for (i, r) in reader.records().enumerate() {
let record = r.unwrap();
assert_eq!((record.chrom1(), record.chrom2()), chroms[i]);
assert_eq!((record.start1(), record.start2()), starts[i]);
assert_eq!((record.end1(), record.end2()), ends[i]);
}
}
#[test]
fn test_reader_from_file_path_doesnt_exist_returns_err() {
let path = Path::new("/I/dont/exist.bedpe");
let error = Reader::from_file(path)
.unwrap_err()
.downcast::<String>()
.unwrap();
assert_eq!(&error, "Failed to read from \"/I/dont/exist.bedpe\"")
}
#[test]
fn test_writer() {
let mut reader = Reader::new(BEDPE_FILE);
let mut output: Vec<u8> = vec![];
{
let mut writer = Writer::new(&mut output);
for r in reader.records() {
writer
.write(&r.expect("Error reading record"))
.expect("Error writing record");
}
}
assert_eq!(&output[..], BEDPE_FILE);
}
#[test]
fn test_bed_from_pos() {
let pos1 = Pos::new("chrXI".to_owned(), 334412, ReqStrand::Forward);
let pos2 = Pos::new("chrXI".to_owned(), 300000, ReqStrand::Reverse);
let record = Record::from((pos1, pos2));
assert_eq!(record.chrom1(), String::from("chrXI"));
assert_eq!(record.chrom2(), String::from("chrXI"));
assert_eq!(record.start1(), 334412);
assert_eq!(record.end1(), 334412 + 1);
assert_eq!(record.start2(), 300000);
assert_eq!(record.end2(), 300000 + 1);
assert_eq!(record.name(), Some(""));
assert_eq!(record.score(), Some("0"));
assert_eq!(record.strand1(), Some(Strand::Forward));
assert_eq!(record.strand2(), Some(Strand::Reverse));
}
}