pub mod interval;
use bstr::{BStr, BString};
pub use self::interval::Interval;
use std::{
error, fmt,
ops::{Bound, RangeBounds},
str::FromStr,
};
use super::Position;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Region {
name: BString,
interval: Interval,
}
impl Region {
pub fn new<N, I>(name: N, interval: I) -> Self
where
N: Into<BString>,
I: Into<Interval>,
{
Self {
name: name.into(),
interval: interval.into(),
}
}
pub fn name(&self) -> &BStr {
self.name.as_ref()
}
pub fn start(&self) -> Bound<Position> {
self.interval.start_bound().cloned()
}
pub fn end(&self) -> Bound<Position> {
self.interval.end_bound().cloned()
}
pub fn interval(&self) -> Interval {
self.interval
}
}
impl fmt::Display for Region {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.name())?;
match (self.interval.start_bound(), self.interval.end_bound()) {
(Bound::Unbounded, Bound::Unbounded) => {}
(_, _) => write!(f, ":{}", self.interval)?,
}
Ok(())
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ParseError {
Empty,
Ambiguous,
Invalid,
InvalidInterval(interval::ParseError),
}
impl error::Error for ParseError {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match self {
Self::InvalidInterval(e) => Some(e),
_ => None,
}
}
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Empty => f.write_str("empty input"),
Self::Ambiguous => f.write_str("ambiguous input"),
Self::Invalid => f.write_str("invalid input"),
Self::InvalidInterval(_) => f.write_str("invalid interval"),
}
}
}
impl FromStr for Region {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.is_empty() {
return Err(ParseError::Empty);
}
if let Some((name, suffix)) = s.rsplit_once(':') {
let interval: Interval = suffix.parse().map_err(ParseError::InvalidInterval)?;
Ok(Self::new(name, interval))
} else {
Ok(Self::new(s, ..))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_fmt() -> Result<(), crate::position::TryFromIntError> {
let start = Position::try_from(5)?;
let end = Position::try_from(8)?;
assert_eq!(Region::new("sq0", ..).to_string(), "sq0");
assert_eq!(Region::new("sq0", ..=end).to_string(), "sq0:1-8");
assert_eq!(Region::new("sq0", start..).to_string(), "sq0:5");
assert_eq!(Region::new("sq0", start..=end).to_string(), "sq0:5-8");
Ok(())
}
#[test]
fn test_from_str() -> Result<(), crate::position::TryFromIntError> {
assert_eq!("sq0".parse(), Ok(Region::new("sq0", ..)));
assert_eq!("sq1:".parse(), Ok(Region::new("sq1", ..)));
let start = Position::try_from(5)?;
assert_eq!("sq2:5".parse(), Ok(Region::new("sq2", start..)));
let end = Position::try_from(8)?;
assert_eq!("sq3:5-8".parse(), Ok(Region::new("sq3", start..=end)));
assert_eq!("".parse::<Region>(), Err(ParseError::Empty));
Ok(())
}
}