Skip to content

Commit

Permalink
fasta/async/io: Add async writer
Browse files Browse the repository at this point in the history
  • Loading branch information
zaeleus committed Oct 16, 2024
1 parent fdf0655 commit 2833123
Show file tree
Hide file tree
Showing 6 changed files with 261 additions and 1 deletion.
4 changes: 4 additions & 0 deletions noodles-fasta/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

### Added

* fasta/async/io: Add async writer (`fasta::r#async::io::Writer`).

### Deprecated

* fasta: Deprecate async re-export (`AsyncReader`).
Expand Down
3 changes: 2 additions & 1 deletion noodles-fasta/src/async/io.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! Async FASTA I/O.

pub(crate) mod reader;
mod writer;

pub use self::reader::Reader;
pub use self::{reader::Reader, writer::Writer};
101 changes: 101 additions & 0 deletions noodles-fasta/src/async/io/writer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
//! Async FASTA writer.

mod record;

use tokio::io::{self, AsyncWrite};

use self::record::write_record;
use crate::Record;

/// An async FASTA writer.
pub struct Writer<W> {
inner: W,
}

impl<W> Writer<W> {
/// Returns a reference to the underlying writer.
///
/// # Examples
///
/// ```
/// use noodles_fasta as fasta;
/// use tokio::io;
/// let writer = fasta::r#async::io::Writer::new(io::sink());
/// let _inner = writer.get_ref();
/// ```
pub fn get_ref(&self) -> &W {
&self.inner
}

/// Returns a mutable reference to the underlying writer.
///
/// # Examples
///
/// ```
/// use noodles_fasta as fasta;
/// use tokio::io;
/// let mut writer = fasta::r#async::io::Writer::new(io::sink());
/// let _inner = writer.get_mut();
/// ```
pub fn get_mut(&mut self) -> &mut W {
&mut self.inner
}

/// Unwraps and returns the underlying writer.
///
/// # Examples
///
/// ```
/// use noodles_fasta as fasta;
/// use tokio::io;
/// let writer = fasta::r#async::io::Writer::new(io::sink());
/// let _inner = writer.into_inner();
/// ```
pub fn into_inner(self) -> W {
self.inner
}
}

impl<W> Writer<W>
where
W: AsyncWrite + Unpin,
{
/// Creates a FASTA writer.
///
/// # Examples
///
/// ```
/// use noodles_fasta as fasta;
/// use tokio::io;
/// let writer = fasta::r#async::io::Writer::new(io::sink());
/// ```
pub fn new(inner: W) -> Self {
Self { inner }
}

/// Writes a FASTA record.
///
/// Sequence lines are hard wrapped at 80 bases.
///
/// # Examples
///
/// ```
/// # #[tokio::main]
/// # async fn main() -> tokio::io::Result<()> {
/// use noodles_fasta::{self as fasta, record::{Definition, Sequence}};
/// use tokio::io;
///
/// let mut writer = fasta::r#async::io::Writer::new(io::sink());
///
/// let definition = Definition::new("sq0", None);
/// let sequence = Sequence::from(b"ACGT".to_vec());
/// let record = fasta::Record::new(definition, sequence);
///
/// writer.write_record(&record).await?;
/// # Ok(())
/// # }
/// ```
pub async fn write_record(&mut self, record: &Record) -> io::Result<()> {
write_record(&mut self.inner, record).await
}
}
29 changes: 29 additions & 0 deletions noodles-fasta/src/async/io/writer/record.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
mod definition;
mod sequence;

use tokio::io::{self, AsyncWrite, AsyncWriteExt};

use self::{definition::write_definition, sequence::write_sequence};
use crate::Record;

const LINE_BASES: usize = 80;

pub(super) async fn write_record<W>(writer: &mut W, record: &Record) -> io::Result<()>
where
W: AsyncWrite + Unpin,
{
write_definition(writer, record.definition()).await?;
write_newline(writer).await?;

write_sequence(writer, record.sequence(), LINE_BASES).await?;

Ok(())
}

async fn write_newline<W>(writer: &mut W) -> io::Result<()>
where
W: AsyncWrite + Unpin,
{
const LINE_FEED: u8 = b'\n';
writer.write_all(&[LINE_FEED]).await
}
75 changes: 75 additions & 0 deletions noodles-fasta/src/async/io/writer/record/definition.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
use tokio::io::{self, AsyncWrite, AsyncWriteExt};

use crate::record::Definition;

pub(super) async fn write_definition<W>(writer: &mut W, definition: &Definition) -> io::Result<()>
where
W: AsyncWrite + Unpin,
{
write_prefix(writer).await?;
write_name(writer, definition.name()).await?;

if let Some(description) = definition.description() {
write_separator(writer).await?;
write_description(writer, description).await?;
}

Ok(())
}

async fn write_prefix<W>(writer: &mut W) -> io::Result<()>
where
W: AsyncWrite + Unpin,
{
const PREFIX: u8 = b'>';
writer.write_all(&[PREFIX]).await
}

async fn write_name<W>(writer: &mut W, name: &[u8]) -> io::Result<()>
where
W: AsyncWrite + Unpin,
{
writer.write_all(name).await
}

async fn write_separator<W>(writer: &mut W) -> io::Result<()>
where
W: AsyncWrite + Unpin,
{
const SEPARATOR: u8 = b' ';
writer.write_all(&[SEPARATOR]).await
}

async fn write_description<W>(writer: &mut W, description: &[u8]) -> io::Result<()>
where
W: AsyncWrite + Unpin,
{
writer.write_all(description).await
}

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

#[tokio::test]
async fn test_write_definition() -> io::Result<()> {
async fn t(buf: &mut Vec<u8>, definition: &Definition, expected: &[u8]) -> io::Result<()> {
buf.clear();
write_definition(buf, definition).await?;
assert_eq!(buf, expected);
Ok(())
}

let mut buf = Vec::new();

t(&mut buf, &Definition::new("sq0", None), b">sq0").await?;
t(
&mut buf,
&Definition::new("sq0", Some(Vec::from("LN:8"))),
b">sq0 LN:8",
)
.await?;

Ok(())
}
}
50 changes: 50 additions & 0 deletions noodles-fasta/src/async/io/writer/record/sequence.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
use tokio::io::{self, AsyncWrite, AsyncWriteExt};

use super::write_newline;
use crate::record::Sequence;

pub(super) async fn write_sequence<W>(
writer: &mut W,
sequence: &Sequence,
line_bases: usize,
) -> io::Result<()>
where
W: AsyncWrite + Unpin,
{
for bases in sequence.as_ref().chunks(line_bases) {
writer.write_all(bases).await?;
write_newline(writer).await?;
}

Ok(())
}

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

#[tokio::test]
async fn test_write_sequence() -> io::Result<()> {
let mut writer = Vec::new();
let sequence = Sequence::from(b"AC".to_vec());
write_sequence(&mut writer, &sequence, 4).await?;
assert_eq!(writer, b"AC\n");

writer.clear();
let sequence = Sequence::from(b"ACGT".to_vec());
write_sequence(&mut writer, &sequence, 4).await?;
assert_eq!(writer, b"ACGT\n");

writer.clear();
let sequence = Sequence::from(b"ACGTACGT".to_vec());
write_sequence(&mut writer, &sequence, 4).await?;
assert_eq!(writer, b"ACGT\nACGT\n");

writer.clear();
let sequence = Sequence::from(b"ACGTACGTAC".to_vec());
write_sequence(&mut writer, &sequence, 4).await?;
assert_eq!(writer, b"ACGT\nACGT\nAC\n");

Ok(())
}
}

0 comments on commit 2833123

Please sign in to comment.