use omics_core::VARIANT_SEPARATOR;
use thiserror::Error;
use crate::Contig;
use crate::Position;
use crate::Strand;
use crate::System;
use crate::contig;
use crate::position;
use crate::position::Number;
use crate::strand;
pub mod base;
pub mod interbase;
#[derive(Error, Debug, PartialEq, Eq)]
pub enum ParseError {
#[error("invalid coordinate format: {value}")]
Format {
value: String,
},
}
pub type ParseResult<T> = std::result::Result<T, ParseError>;
#[derive(Error, Debug, PartialEq, Eq)]
pub enum Error {
#[error("contig error: {0}")]
Contig(#[from] contig::Error),
#[error("strand error: {0}")]
Strand(#[from] strand::Error),
#[error("parse error: {0}")]
Parse(#[from] ParseError),
#[error("position error: {0}")]
Position(#[from] position::Error),
}
pub type Result<T> = std::result::Result<T, Error>;
pub mod r#trait {
use super::*;
pub trait Coordinate<S: System>:
std::fmt::Display
+ std::fmt::Debug
+ PartialEq
+ Eq
+ PartialOrd
+ Ord
+ std::str::FromStr<Err = Error>
where
Self: Sized,
{
fn try_new(
contig: impl TryInto<Contig, Error = contig::Error>,
strand: impl TryInto<Strand, Error = strand::Error>,
position: Number,
) -> Result<Self>;
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct Coordinate<S: System> {
system: S,
contig: Contig,
strand: Strand,
position: Position<S>,
}
impl<S: System> Coordinate<S>
where
Position<S>: position::r#trait::Position<S>,
{
pub fn new(
contig: impl Into<Contig>,
strand: impl Into<Strand>,
position: impl Into<Position<S>>,
) -> Self {
let contig = contig.into();
let strand = strand.into();
let position = position.into();
Self {
system: Default::default(),
contig,
strand,
position,
}
}
pub fn try_new(
contig: impl TryInto<Contig, Error = contig::Error>,
strand: impl TryInto<Strand, Error = strand::Error>,
position: Number,
) -> Result<Self>
where
Self: r#trait::Coordinate<S>,
{
<Self as r#trait::Coordinate<S>>::try_new(contig, strand, position)
}
pub fn contig(&self) -> &Contig {
&self.contig
}
pub fn into_contig(self) -> Contig {
self.contig
}
pub fn strand(&self) -> Strand {
self.strand
}
pub fn position(&self) -> &Position<S> {
&self.position
}
pub fn into_position(self) -> Position<S> {
self.position
}
pub fn into_parts(self) -> (Contig, Strand, Position<S>) {
(self.contig, self.strand, self.position)
}
pub fn move_forward(&mut self, magnitude: Number) -> bool {
if magnitude == 0 {
return true;
}
let result = match self.strand {
Strand::Positive => self.position.checked_add(magnitude),
Strand::Negative => self.position.checked_sub(magnitude),
};
match result {
Some(position) => {
self.position = position;
true
}
None => false,
}
}
#[must_use = "this method returns a new coordinate"]
pub fn into_move_forward(self, magnitude: Number) -> Option<Coordinate<S>> {
if magnitude == 0 {
return Some(self);
}
match self.strand {
Strand::Positive => self.position.checked_add(magnitude),
Strand::Negative => self.position.checked_sub(magnitude),
}
.map(|position| Self::new(self.contig, self.strand, position))
}
pub fn move_backward(&mut self, magnitude: Number) -> bool {
if magnitude == 0 {
return true;
}
let result = match self.strand {
Strand::Positive => self.position.checked_sub(magnitude),
Strand::Negative => self.position.checked_add(magnitude),
};
match result {
Some(position) => {
self.position = position;
true
}
None => false,
}
}
#[must_use = "this method returns a new coordinate"]
pub fn into_move_backward(self, magnitude: Number) -> Option<Coordinate<S>> {
if magnitude == 0 {
return Some(self);
}
match self.strand {
Strand::Positive => self.position.checked_sub(magnitude),
Strand::Negative => self.position.checked_add(magnitude),
}
.map(|position| Self::new(self.contig, self.strand, position))
}
#[must_use = "this method returns a new coordinate"]
pub fn swap_strand(self) -> Coordinate<S> {
let (contig, strand, position) = self.into_parts();
Coordinate::new(contig, strand.complement(), position)
}
}
impl<S: System> std::fmt::Display for Coordinate<S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if !f.alternate() {
write!(f, "{}:{}:{}", self.contig, self.strand, self.position)
} else {
write!(
f,
"{}:{}:{} ({})",
self.contig, self.strand, self.position, self.system
)
}
}
}
impl<S: System> std::str::FromStr for Coordinate<S>
where
Position<S>: position::r#trait::Position<S>,
{
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
let parts = s.split(VARIANT_SEPARATOR).collect::<Vec<_>>();
if parts.len() != 3 {
return Err(Error::Parse(ParseError::Format {
value: s.to_owned(),
}));
}
let mut parts = parts.iter();
let contig = parts.next().unwrap().parse::<Contig>().map_err(|_| {
Error::Parse(ParseError::Format {
value: s.to_string(),
})
})?;
let strand = parts
.next()
.unwrap()
.parse::<Strand>()
.map_err(Error::Strand)?;
let position = parts
.next()
.unwrap()
.parse::<Position<S>>()
.map_err(Error::Position)?;
Ok(Self::new(contig, strand, position))
}
}