use std::convert::Into;
use std::fmt::{self, Display, Formatter};
use std::ops::AddAssign;
use std::ops::Neg;
use std::ops::SubAssign;
use std::str::FromStr;
use regex::Regex;
use crate::annot::contig::Contig;
use crate::annot::loc::Loc;
use crate::annot::*;
use crate::strand::*;
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct Pos<R, S> {
refid: R,
pos: isize,
strand: S,
}
impl<R, S> Pos<R, S> {
pub fn new(refid: R, pos: isize, strand: S) -> Self {
Pos { refid, pos, strand }
}
pub fn pos(&self) -> isize {
self.pos
}
pub fn into_stranded(self, strand: ReqStrand) -> Pos<R, ReqStrand> {
Pos {
refid: self.refid,
pos: self.pos,
strand,
}
}
}
impl<R, T> AddAssign<T> for Pos<R, ReqStrand>
where
isize: AddAssign<T>,
isize: SubAssign<T>,
{
fn add_assign(&mut self, dist: T) {
match self.strand {
ReqStrand::Forward => self.pos += dist,
ReqStrand::Reverse => self.pos -= dist,
}
}
}
impl<R, T> SubAssign<T> for Pos<R, ReqStrand>
where
isize: AddAssign<T>,
isize: SubAssign<T>,
{
fn sub_assign(&mut self, dist: T) {
match self.strand {
ReqStrand::Forward => self.pos -= dist,
ReqStrand::Reverse => self.pos += dist,
}
}
}
impl<R, S> Loc for Pos<R, S> {
type RefID = R;
type Strand = S;
fn refid(&self) -> &R {
&self.refid
}
fn start(&self) -> isize {
self.pos
}
fn length(&self) -> usize {
1
}
fn strand(&self) -> S
where
S: Copy,
{
self.strand
}
fn pos_into<T>(&self, pos: &Pos<Self::RefID, T>) -> Option<Pos<(), T>>
where
Self::RefID: Eq,
Self::Strand: Into<ReqStrand> + Copy,
T: Neg<Output = T> + Copy,
{
if (self.refid != pos.refid) || (self.pos != pos.pos) {
None
} else {
Some(Pos::new(
(),
0,
self.strand().into().on_strand(pos.strand()),
))
}
}
fn pos_outof<Q, T>(&self, pos: &Pos<Q, T>) -> Option<Pos<Self::RefID, T>>
where
Self::RefID: Clone,
Self::Strand: Into<ReqStrand> + Copy,
T: Neg<Output = T> + Copy,
{
if pos.pos == 0 {
Some(Pos::new(
self.refid.clone(),
self.pos,
self.strand().into().on_strand(pos.strand()),
))
} else {
None
}
}
fn contig_intersection<T>(&self, contig: &Contig<Self::RefID, T>) -> Option<Self>
where
Self::RefID: PartialEq + Clone,
Self::Strand: Copy,
{
if self.refid() != contig.refid() {
return None;
}
if (self.pos >= contig.start()) && (self.pos < (contig.start() + contig.length() as isize))
{
Some(self.clone())
} else {
None
}
}
}
impl<R, S> Same for Pos<R, S>
where
R: Eq,
S: Same,
{
fn same(&self, p: &Self) -> bool {
self.pos == p.pos && self.refid == p.refid && self.strand.same(&p.strand)
}
}
impl<R, S> Display for Pos<R, S>
where
R: Display,
S: Display + Clone + Into<Strand>,
{
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let strand: Strand = self.strand.clone().into();
if strand.is_unknown() {
write!(f, "{}:{}", self.refid, self.pos)
} else {
write!(f, "{}:{}({})", self.refid, self.pos, strand)
}
}
}
impl<R, S> FromStr for Pos<R, S>
where
R: From<String>,
S: FromStr<Err = StrandError>,
{
type Err = ParseAnnotError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
lazy_static! {
static ref POS_RE: Regex = Regex::new(r"^(.*):(\d+)(\([+-]\))?$").unwrap();
}
let cap = POS_RE.captures(s).ok_or(ParseAnnotError::BadAnnot)?;
let strand = cap
.get(3)
.map_or("", |m| m.as_str())
.parse::<S>()
.map_err(ParseAnnotError::ParseStrand)?;
Ok(Pos::new(
R::from(cap[1].to_owned()),
cap[2].parse::<isize>().map_err(ParseAnnotError::ParseInt)?,
strand,
))
}
}
impl<R> From<Pos<R, ReqStrand>> for Pos<R, Strand> {
fn from(x: Pos<R, ReqStrand>) -> Self {
Pos {
refid: x.refid,
pos: x.pos,
strand: match x.strand {
ReqStrand::Forward => Strand::Forward,
ReqStrand::Reverse => Strand::Reverse,
},
}
}
}
impl<R> From<Pos<R, NoStrand>> for Pos<R, Strand> {
fn from(x: Pos<R, NoStrand>) -> Self {
Pos {
refid: x.refid,
pos: x.pos,
strand: Strand::Unknown,
}
}
}
impl<R> From<Pos<R, Strand>> for Pos<R, NoStrand> {
fn from(x: Pos<R, Strand>) -> Self {
Pos {
refid: x.refid,
pos: x.pos,
strand: NoStrand::Unknown,
}
}
}
impl<R> From<Pos<R, ReqStrand>> for Pos<R, NoStrand> {
fn from(x: Pos<R, ReqStrand>) -> Self {
Pos {
refid: x.refid,
pos: x.pos,
strand: NoStrand::Unknown,
}
}
}
pub type SeqPosStranded = Pos<String, ReqStrand>;
pub type SeqPosUnstranded = Pos<String, NoStrand>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pos_accessors() {
let start = Pos::new("chrIV".to_owned(), 683946, Strand::Unknown);
assert_eq!(start.refid(), "chrIV");
assert_eq!(start.pos(), 683946);
assert!(start.strand().same(&Strand::Unknown));
let start = Pos::new("chrIV".to_owned(), 683946, Strand::Reverse);
assert_eq!(start.refid(), "chrIV");
assert_eq!(start.pos(), 683946);
assert!(start.strand().same(&Strand::Reverse));
let start = Pos::new("chrXV".to_owned(), 493433, Strand::Forward);
assert_eq!(start.refid(), "chrXV");
assert_eq!(start.pos(), 493433);
assert!(start.strand().same(&Strand::Forward));
}
#[test]
fn strand_conversion() {
let start = "chrIV:683946(-)".parse::<Pos<String, Strand>>().unwrap();
let start_un: Pos<String, NoStrand> = start.into();
assert!(start_un.same(&"chrIV:683946".parse::<Pos<String, NoStrand>>().unwrap()));
let start_re = start_un.into_stranded(ReqStrand::Reverse);
assert!(start_re.same(&"chrIV:683946(-)".parse::<Pos<String, ReqStrand>>().unwrap()));
let start = "chrXV:493433(+)".parse::<Pos<String, Strand>>().unwrap();
let start_un: Pos<String, NoStrand> = start.into();
assert!(start_un.same(&"chrXV:493433".parse::<Pos<String, NoStrand>>().unwrap()));
let start_re = start_un.into_stranded(ReqStrand::Forward);
assert!(start_re.same(&"chrXV:493433(+)".parse::<Pos<String, ReqStrand>>().unwrap()));
}
#[test]
fn string_representation() {
let start = Pos::new("chrIV".to_owned(), 683946, NoStrand::Unknown);
assert_eq!(start.to_string(), "chrIV:683946");
assert!(start.same(&"chrIV:683946".parse::<Pos<String, NoStrand>>().unwrap()));
let start = Pos::new("chrIV".to_owned(), 683946, Strand::Unknown);
assert_eq!(start.to_string(), "chrIV:683946");
assert!(start.same(&"chrIV:683946".parse::<Pos<String, Strand>>().unwrap()));
let start = Pos::new("chrIV".to_owned(), 683946, Strand::Reverse);
assert_eq!(start.to_string(), "chrIV:683946(-)");
assert!(start.same(&"chrIV:683946(-)".parse::<Pos<String, Strand>>().unwrap()));
let start = Pos::new("chrXV".to_owned(), 493433, Strand::Forward);
assert_eq!(start.to_string(), "chrXV:493433(+)");
assert!(start.same(&"chrXV:493433(+)".parse::<Pos<String, Strand>>().unwrap()));
let start = Pos::new("chrIV".to_owned(), 683946, ReqStrand::Reverse);
assert_eq!(start.to_string(), "chrIV:683946(-)");
assert!(start.same(&"chrIV:683946(-)".parse::<Pos<String, ReqStrand>>().unwrap()));
let start = Pos::new("chrXV".to_owned(), 493433, ReqStrand::Forward);
assert_eq!(start.to_string(), "chrXV:493433(+)");
assert!(start.same(&"chrXV:493433(+)".parse::<Pos<String, ReqStrand>>().unwrap()));
}
#[test]
fn loc_impl() {
let start = Pos::new("chrIV".to_owned(), 683946, ReqStrand::Forward);
assert_eq!(
None,
start.contig_intersection(&Contig::new(
"chrIV".to_owned(),
683900,
40,
ReqStrand::Forward
))
);
assert_eq!(
None,
start.contig_intersection(&Contig::new(
"chrV".to_owned(),
683900,
100,
ReqStrand::Forward
))
);
assert_eq!(
None,
start.contig_intersection(&Contig::new(
"chrIV".to_owned(),
683950,
40,
ReqStrand::Forward
))
);
assert_eq!(
Some(start.clone()),
start.contig_intersection(&Contig::new(
"chrIV".to_owned(),
683900,
100,
ReqStrand::Forward
))
);
assert_eq!(
Some(start.clone()),
start.contig_intersection(&Contig::new(
"chrIV".to_owned(),
683900,
100,
ReqStrand::Reverse
))
);
let rstart = Pos::new("chrIV".to_owned(), 683946, ReqStrand::Reverse);
assert_eq!(
Some(rstart.clone()),
rstart.contig_intersection(&Contig::new(
"chrIV".to_owned(),
683900,
100,
ReqStrand::Forward
))
);
assert_eq!(
Some(rstart.clone()),
rstart.contig_intersection(&Contig::new(
"chrIV".to_owned(),
683900,
100,
ReqStrand::Reverse
))
);
}
}