1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166
//! SAM record CIGAR operation and kind.
pub mod kind;
use std::{error, fmt, num, str::FromStr};
pub use self::kind::Kind;
/// A SAM record CIGAR operation.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Op {
kind: Kind,
len: usize,
}
impl Op {
/// Creates a CIGAR operation.
///
/// # Examples
///
/// ```
/// use noodles_sam::record::cigar::{op::Kind, Op};
///
/// let op = Op::new(Kind::Match, 13);
///
/// assert_eq!(op.kind(), Kind::Match);
/// assert_eq!(op.len(), 13);
/// ```
pub fn new(kind: Kind, len: usize) -> Self {
Self { kind, len }
}
/// Returns the kind of the operation.
///
/// # Examples
///
/// ```
/// use noodles_sam::record::cigar::{op::Kind, Op};
/// let op = Op::new(Kind::Match, 13);
/// assert_eq!(op.kind(), Kind::Match);
/// ```
pub fn kind(self) -> Kind {
self.kind
}
/// Returns the length of the operation.
///
/// # Examples
///
/// ```
/// use noodles_sam::record::cigar::{op::Kind, Op};
/// let op = Op::new(Kind::Match, 13);
/// assert_eq!(op.len(), 13);
/// ```
pub fn len(self) -> usize {
self.len
}
/// Returns whether the operation is a no-op.
///
/// That is, whether the operation has a length of 0.
///
/// # Examples
///
/// ```
/// use noodles_sam::record::cigar::{op::Kind, Op};
///
/// let op = Op::new(Kind::Match, 0);
/// assert!(op.is_empty());
///
/// let op = Op::new(Kind::Match, 13);
/// assert!(!op.is_empty());
/// ```
pub fn is_empty(self) -> bool {
self.len == 0
}
}
impl fmt::Display for Op {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}{}", self.len(), self.kind())
}
}
/// An error returned when a raw CIGAR operation fails to parse.
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ParseError {
/// The input is empty.
Empty,
/// The raw operation has an invalid length.
InvalidLength(num::ParseIntError),
/// The raw operation has an invalid kind.
InvalidKind(kind::ParseError),
}
impl error::Error for ParseError {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match self {
Self::Empty => None,
Self::InvalidLength(e) => Some(e),
Self::InvalidKind(e) => Some(e),
}
}
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Empty => write!(f, "empty input"),
Self::InvalidLength(_) => f.write_str("invalid length"),
Self::InvalidKind(_) => f.write_str("invalid kind"),
}
}
}
impl FromStr for Op {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.is_empty() {
return Err(ParseError::Empty);
}
let (raw_len, raw_kind) = s.split_at(s.len() - 1);
let len = raw_len.parse().map_err(ParseError::InvalidLength)?;
let kind = raw_kind.parse().map_err(ParseError::InvalidKind)?;
Ok(Self::new(kind, len))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_fmt() {
let op = Op::new(Kind::Match, 5);
assert_eq!(op.to_string(), "5M");
let op = Op::new(Kind::SoftClip, 13);
assert_eq!(op.to_string(), "13S");
}
#[test]
fn test_from_str() -> Result<(), ParseError> {
assert_eq!("1M".parse(), Ok(Op::new(Kind::Match, 1)));
assert_eq!("13N".parse(), Ok(Op::new(Kind::Skip, 13)));
assert_eq!("144S".parse(), Ok(Op::new(Kind::SoftClip, 144)));
assert_eq!("".parse::<Op>(), Err(ParseError::Empty));
assert!(matches!(
"Z".parse::<Op>(),
Err(ParseError::InvalidLength(_))
));
assert!(matches!(
"21".parse::<Op>(),
Err(ParseError::InvalidKind(_))
));
Ok(())
}
}