Skip to content

Commit

Permalink
sam/async/io/reader: Add record reader
Browse files Browse the repository at this point in the history
  • Loading branch information
zaeleus committed Jan 24, 2024
1 parent 40cff7e commit c227d30
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 4 deletions.
2 changes: 2 additions & 0 deletions noodles-sam/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
The alignment record buffer is renamed to `RecordBuf`. This also introduces
traits for all fields.

* sam/async/io/reader: Add record reader (`Reader::read_record`).

* sam/io/reader: Add records iterator (`Reader::records`).

* sam/record: Add wrappers for read name (`record::ReadName`) and template
Expand Down
41 changes: 39 additions & 2 deletions noodles-sam/src/async/io/reader.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
mod header;
mod record;
mod record_buf;

use futures::{stream, Stream};
use tokio::io::{self, AsyncBufRead, AsyncBufReadExt};

use self::{header::read_header, record_buf::read_record_buf};
use crate::{alignment::RecordBuf, Header};
use self::{header::read_header, record::read_record, record_buf::read_record_buf};
use crate::{alignment::RecordBuf, Header, Record};

/// An async SAM reader.
pub struct Reader<R> {
Expand Down Expand Up @@ -188,6 +189,42 @@ where
},
))
}

/// Reads a record.
///
/// This reads SAM fields from the underlying stream into the given record's buffer until a
/// newline is reached. No fields are decoded, meaning the record is not necessarily valid.
/// However, the structure of the buffer is guaranteed to be record-like.
///
/// The stream is expected to be directly after the header or at the start of another record.
///
/// If successful, the number of bytes read is returned. If the number of bytes read is 0, the
/// stream reached EOF.
///
/// # Examples
///
/// ```
/// # #[tokio::main]
/// # async fn main() -> std::io::Result<()> {
/// use noodles_sam as sam;
///
/// let data = b"@HD\tVN:1.6
/// *\t4\t*\t0\t255\t*\t*\t0\t0\t*\t*
/// ";
///
/// let mut reader = sam::r#async::io::Reader::new(&data[..]);
/// let header = reader.read_header().await?;
///
/// let mut record = sam::Record::default();
/// reader.read_record(&mut record).await?;
///
/// assert_eq!(record, sam::Record::default());
/// # Ok(())
/// # }
/// ```
pub async fn read_record(&mut self, record: &mut Record) -> io::Result<usize> {
read_record(&mut self.inner, &mut self.buf, record).await
}
}

async fn read_line<R>(reader: &mut R, buf: &mut Vec<u8>) -> io::Result<usize>
Expand Down
54 changes: 54 additions & 0 deletions noodles-sam/src/async/io/reader/record.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
use tokio::io::{self, AsyncBufRead, AsyncBufReadExt};

use crate::Record;

pub(super) async fn read_record<R>(
reader: &mut R,
buf: &mut Vec<u8>,
record: &mut Record,
) -> io::Result<usize>
where
R: AsyncBufRead + Unpin,
{
const LINE_FEED: u8 = b'\n';

buf.clear();

if reader.read_until(LINE_FEED, buf).await? == 0 {
return Ok(0);
}

let mut src = &buf[..];
crate::io::reader::read_record(&mut src, record)
}

#[cfg(test)]
mod tests {
use super::*;
use crate::record::Bounds;

#[tokio::test]
async fn test_read_record() -> io::Result<()> {
let mut buf = Vec::new();

let mut src = &b"*\t4\t*\t0\t255\t*\t*\t0\t0\t*\t*\n"[..];
let mut record = Record::default();
read_record(&mut src, &mut buf, &mut record).await?;
assert_eq!(record.fields().buf, b"*4*0255**00**");
assert_eq!(record.fields().bounds, Bounds::default());

let mut src = &b"*\t4\t*\t0\t255\t*\t*\t0\t0\t*\t*\r\n"[..];
let mut record = Record::default();
read_record(&mut src, &mut buf, &mut record).await?;
assert_eq!(record.fields().buf, b"*4*0255**00**");
assert_eq!(record.fields().bounds, Bounds::default());

let mut src = &b"\n"[..];
assert!(matches!(
read_record(&mut src, &mut buf, &mut record).await,
Err(e) if e.kind() == io::ErrorKind::InvalidData,
));

Ok(())
}
}
3 changes: 2 additions & 1 deletion noodles-sam/src/io/reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ use noodles_bgzf as bgzf;
use noodles_core::Region;
use noodles_csi::BinningIndex;

pub(crate) use self::record::read_record;
pub use self::{builder::Builder, record_bufs::RecordBufs};
use self::{header::read_header, record::read_record, record_buf::read_record_buf};
use self::{header::read_header, record_buf::read_record_buf};
use crate::{alignment::RecordBuf, header::ReferenceSequences, Header, Record};

/// A SAM reader.
Expand Down
2 changes: 1 addition & 1 deletion noodles-sam/src/io/reader/record.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::io::{self, BufRead};
use super::read_line;
use crate::Record;

pub(super) fn read_record<R>(reader: &mut R, record: &mut Record) -> io::Result<usize>
pub(crate) fn read_record<R>(reader: &mut R, record: &mut Record) -> io::Result<usize>
where
R: BufRead,
{
Expand Down

0 comments on commit c227d30

Please sign in to comment.