use crate::{
data::{
cigar::Cigar,
err::ResultWithErrorContext,
sam::{SamData, SamOptRaw},
},
unwrap_or_return_some_err,
};
use std::{
fs::File,
io::{BufRead, Error as IOError, ErrorKind},
path::Path,
};
#[derive(Clone, Eq, PartialEq, Hash, Debug)]
pub enum SamRow {
Header(String),
Data(SamData),
}
impl SamRow {
#[inline]
#[must_use]
pub fn header(self) -> Option<String> {
match self {
SamRow::Header(header) => Some(header),
SamRow::Data(_) => None,
}
}
#[inline]
#[must_use]
pub fn data(self) -> Option<SamData> {
match self {
SamRow::Header(_) => None,
SamRow::Data(data) => Some(data),
}
}
}
impl std::fmt::Display for SamRow {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
SamRow::Data(d) => write!(f, "{d}"),
SamRow::Header(h) => write!(f, "{h}"),
}
}
}
#[derive(Debug)]
pub struct SAMReader<R: std::io::Read, const OPT: bool> {
sam_reader: std::io::Lines<std::io::BufReader<R>>,
}
impl<R: std::io::Read> SAMReader<R, true> {
pub fn new(inner: R) -> Self {
SAMReader {
sam_reader: std::io::BufReader::new(inner).lines(),
}
}
pub fn from_readable(read: R) -> std::io::Result<Self> {
let mut sam_reader = std::io::BufReader::new(read);
if sam_reader.fill_buf()?.is_empty() {
return Err(IOError::new(ErrorKind::InvalidData, "No SAM data was found!"));
}
Ok(SAMReader {
sam_reader: sam_reader.lines(),
})
}
}
impl<R: std::io::Read> SAMReader<R, false> {
#[deprecated(
since = "0.0.29",
note = "please use `new_ignore_opt` instead. This function will be removed in v0.0.31"
)]
pub fn new_ignore_tags(inner: R) -> Self {
SAMReader::new_ignore_opt(inner)
}
pub fn new_ignore_opt(inner: R) -> Self {
SAMReader {
sam_reader: std::io::BufReader::new(inner).lines(),
}
}
#[allow(clippy::missing_errors_doc)]
#[deprecated(
since = "0.0.29",
note = "please use `from_readable_ignore_opt` instead. This function will be removed in v0.0.31"
)]
pub fn from_readable_ignore_tags(read: R) -> std::io::Result<Self> {
SAMReader::from_readable_ignore_opt(read)
}
pub fn from_readable_ignore_opt(read: R) -> std::io::Result<Self> {
let mut sam_reader = std::io::BufReader::new(read);
if sam_reader.fill_buf()?.is_empty() {
return Err(IOError::new(ErrorKind::InvalidData, "No SAM data was found!"));
}
Ok(SAMReader {
sam_reader: sam_reader.lines(),
})
}
}
impl<R: std::io::Read, const OPT: bool> Iterator for SAMReader<R, OPT> {
type Item = std::io::Result<SamRow>;
fn next(&mut self) -> Option<Self::Item> {
let line = unwrap_or_return_some_err!(self.sam_reader.next()?);
if line.starts_with('@') {
Some(Ok(SamRow::Header(line)))
} else {
let parts = line.split('\t').collect::<Vec<_>>();
if parts.len() < 11 {
return Some(Err(IOError::new(
ErrorKind::InvalidData,
"SAM file did not have at least 11 fields!",
)));
}
let qname = parts[0].to_string();
let flag = {
match parts[1].parse::<u16>() {
Ok(f) => f,
Err(e) => {
return Some(Err(IOError::new(
ErrorKind::InvalidData,
format!("Error: {e}, invalid SAM bit flag '{}'", parts[1]),
)));
}
}
};
let rname = parts[2].to_string();
let pos = {
match parts[3].parse::<usize>() {
Ok(p) => p,
Err(e) => {
return Some(Err(IOError::new(
ErrorKind::InvalidData,
format!("Error: {e}, invalid SAM reference position '{}'", parts[3]),
)));
}
}
};
let mapq = {
match parts[4].parse::<u8>() {
Ok(q) => q,
Err(e) => {
return Some(Err(IOError::new(
ErrorKind::InvalidData,
format!("Error: {e}, invalid SAM mapq '{}'", parts[4]),
)));
}
}
};
let cigar = {
let cigar_str = parts[5].trim_ascii().as_bytes();
if cigar_str == b"*" {
Cigar::new()
} else {
Cigar::from_slice_unchecked(cigar_str)
}
};
let seq = parts[9].into();
let qual = unwrap_or_return_some_err!(parts[10].as_bytes().try_into());
let opt_fields = if OPT {
parts[11..].iter().map(ToString::to_string).collect::<SamOptRaw>()
} else {
SamOptRaw::new()
};
let row = SamData {
qname,
flag,
rname,
pos,
mapq,
cigar,
rnext: '*',
pnext: 0,
tlen: 0,
seq,
qual,
opt_fields,
};
Some(Ok(SamRow::Data(row)))
}
}
}
impl SAMReader<File, true> {
pub fn from_path<P>(path: P) -> Result<Self, std::io::Error>
where
P: AsRef<Path>, {
let path = path.as_ref();
let file = File::open(path).with_path_context("Failed to open path", path)?;
Ok(Self::from_readable(file).with_path_context("Failed to read data at path", path)?)
}
}
impl SAMReader<File, false> {
#[allow(clippy::missing_errors_doc)]
#[deprecated(
since = "0.0.29",
note = "please use `from_path_ignore_opt` instead. This function will be removed in v0.0.31"
)]
pub fn from_path_ignore_tags<P>(path: P) -> Result<Self, std::io::Error>
where
P: AsRef<Path>, {
Self::from_path_ignore_opt(path)
}
pub fn from_path_ignore_opt<P>(path: P) -> Result<Self, std::io::Error>
where
P: AsRef<Path>, {
let path = path.as_ref();
let file = File::open(path).with_path_context("Failed to open path", path)?;
Ok(Self::from_readable_ignore_opt(file).with_path_context("Failed to read data at path", path)?)
}
}