#![doc = include_str!("../README.md")]
use flate2::read::MultiGzDecoder;
use std::{
fs::File,
io::{stdin, BufRead, BufReader, Cursor, Error, ErrorKind, Read, Result},
path::Path,
};
pub mod record;
use record::{Fastx, Reader, Readers, Result as ParseResult};
pub enum Paths<'a> {
Reader(Reader<'a>),
Readers(Readers<'a>),
}
impl<'a> Paths<'a> {
fn new(mut reader: Box<dyn BufRead + 'a>, path: &Path) -> Result<Self> {
let mut format_bytes = [0u8; 4];
reader.read_exact(&mut format_bytes)?;
reader = Box::new(Cursor::new(format_bytes.to_vec()).chain(reader));
if &format_bytes[..2] == b"\x1f\x8b" {
reader = Box::new(BufReader::with_capacity(65536, MultiGzDecoder::new(reader)));
format_bytes.iter_mut().for_each(|m| *m = 0);
reader.read_exact(&mut format_bytes)?;
reader = Box::new(Cursor::new(format_bytes.to_vec()).chain(reader));
}
match format_bytes[0] {
b'@' | b'>' => Ok(Paths::Reader(Reader::new(reader))),
_ => {
let mut paths = Readers::new();
let parent = path.parent().unwrap_or_else(|| Path::new(""));
for _line in reader.lines() {
let _line = _line?;
let line = _line.trim();
if line.starts_with('#') || line.is_empty() {
continue;
}
let path = parent.join(line); if path.exists() {
match parse_path(path)? {
Paths::Reader(reader) => paths.readers.push(reader),
Paths::Readers(readers) => paths.readers.extend(readers.readers),
}
} else {
return Err(Error::new(
ErrorKind::InvalidData,
format!("{:?} is not a valid fastq/fasta/fofn file", path),
));
}
}
Ok(Paths::Readers(paths))
}
}
}
pub fn iter_record(&mut self) -> ParseResult<Option<Fastx>> {
match self {
Paths::Reader(t) => t.iter_record(),
Paths::Readers(t) => t.iter_record(),
}
}
}
pub fn parse_path<'a, P: AsRef<Path> + 'a>(path: P) -> Result<Paths<'a>> {
let path = path.as_ref();
let reader: Box<dyn BufRead> = if path == Path::new("-") {
if atty::is(atty::Stream::Stdin) {
return Err(Error::new(ErrorKind::InvalidInput, "Missing input"));
}
Box::new(BufReader::with_capacity(65536, stdin()))
} else {
Box::new(BufReader::with_capacity(65536, File::open(path)?))
};
Paths::new(reader, path)
}
pub fn parse_reader<'a, R: Read + 'a>(reader: R) -> Result<Paths<'a>> {
Paths::new(
Box::new(BufReader::with_capacity(65536, reader)),
Path::new(""),
)
}