diff --git a/Cargo.toml b/Cargo.toml index 83af892..7fd123b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,8 +17,8 @@ log = "0.4.14" [dependencies.tokio] version = "1.5.0" -features = ["io-util", "io-std"] +features = ["io-util", "io-std", "fs"] [dev-dependencies.tokio] version = "1.5.0" -features = ["rt-multi-thread", "test-util"] \ No newline at end of file +features = ["rt-multi-thread", "macros"] \ No newline at end of file diff --git a/src/error.rs b/src/error.rs index 4a21335..a0902fe 100644 --- a/src/error.rs +++ b/src/error.rs @@ -6,4 +6,7 @@ pub type TapeResult = Result; pub enum TapeError { #[error("IO Error: {0}")] TokioIoError(#[from] tokio::io::Error), + + #[error("Unexpected EOF")] + EOF, } diff --git a/src/input.rs b/src/input.rs deleted file mode 100644 index a1ee8df..0000000 --- a/src/input.rs +++ /dev/null @@ -1,50 +0,0 @@ -use crate::error::TapeResult; -use crate::traits::AsyncReadSeek; -use std::io::SeekFrom; -use tokio::io::{AsyncReadExt, BufReader}; -use tokio::io::{AsyncSeekExt, ErrorKind}; - -/// An Input reader to asynchronously read a type -/// that implements AsyncBufRead and AsyncSeek. -pub struct InputReader { - inner: BufReader>, -} - -impl InputReader { - pub fn new(inner: T) -> Self { - Self { - inner: BufReader::new(Box::new(inner)), - } - } - - /// Reads the next char consuming it in the process - pub async fn consume(&mut self) -> TapeResult { - self.read_next().await - } - - /// Returns the next char without forwarding - pub async fn peek(&mut self) -> TapeResult { - let char = self.read_next().await?; - self.inner.get_mut().seek(SeekFrom::Current(-1)).await?; - - Ok(char) - } - - /// Returns if EOF has been reached - pub async fn check_eof(&mut self) -> TapeResult { - let char = self.read_next().await?; - - Ok(char == '\x00') - } - - /// Reads the next char returning \x00 for EOF - async fn read_next(&mut self) -> TapeResult { - let mut value = String::with_capacity(1); - - match self.inner.read_to_string(&mut value).await { - Ok(_) => Ok(value.chars().next().unwrap()), - Err(e) if e.kind() == ErrorKind::UnexpectedEof => Ok('\x00'), - Err(e) => Err(e.into()), - } - } -} diff --git a/src/input_reader.rs b/src/input_reader.rs new file mode 100644 index 0000000..824cb1e --- /dev/null +++ b/src/input_reader.rs @@ -0,0 +1,81 @@ +use crate::error::{TapeError, TapeResult}; +use std::io::ErrorKind; +use tokio::io::{AsyncBufRead, AsyncBufReadExt}; + +/// An Input reader to asynchronously read a type +/// that implements AsyncBufRead and AsyncSeek. +pub struct InputReader { + inner: Box, + buf: String, + index: usize, +} + +impl InputReader { + pub fn new(inner: T) -> Self { + Self { + inner: Box::new(inner), + buf: String::new(), + index: 0, + } + } + + /// Reads the next char consuming it in the process + #[inline] + pub async fn consume(&mut self) -> TapeResult { + self.read_next().await + } + + /// Returns the next char without forwarding + #[inline] + pub async fn peek(&mut self) -> TapeResult { + let char = self.read_next().await?; + self.seek_to(self.index - 1).await?; + + Ok(char) + } + + /// Returns if EOF has been reached + #[inline] + pub async fn check_eof(&mut self) -> bool { + if let Err(TapeError::EOF) = self.read_next().await { + true + } else { + false + } + } + + /// Reads the next char returning \x00 for EOF + async fn read_next(&mut self) -> TapeResult { + self.seek_to(self.index + 1).await?; + let result = self + .buf + .get(self.index - 1..self.index) + .ok_or(TapeError::EOF)? + .chars() + .next() + .ok_or(TapeError::EOF); + + result + } + + /// Seeks to a given index + pub async fn seek_to(&mut self, to_index: usize) -> TapeResult<()> { + while to_index >= self.buf.len() { + let mut line = String::new(); + self.inner.read_line(&mut line).await.map_err(|e| { + if e.kind() == ErrorKind::UnexpectedEof { + TapeError::EOF + } else { + TapeError::TokioIoError(e) + } + })?; + if line.is_empty() { + break; + } + self.buf.push_str(&line); + } + self.index = to_index; + + Ok(()) + } +} diff --git a/src/lib.rs b/src/lib.rs index b8c2773..40bc48b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,5 @@ pub mod error; -pub mod input; -mod traits; +pub mod input_reader; + +#[cfg(test)] +mod tests; diff --git a/src/tests/mod.rs b/src/tests/mod.rs new file mode 100644 index 0000000..b993b10 --- /dev/null +++ b/src/tests/mod.rs @@ -0,0 +1 @@ +mod test_input; diff --git a/src/tests/test_input.rs b/src/tests/test_input.rs new file mode 100644 index 0000000..703c43e --- /dev/null +++ b/src/tests/test_input.rs @@ -0,0 +1,41 @@ +use crate::error::{TapeError, TapeResult}; +use crate::input_reader::InputReader; +use std::io::Cursor; + +fn get_reader() -> InputReader { + let data = "ABCDEFG HIJKLMNOP 12345567890\nSecond Line\n\n"; + InputReader::new(Cursor::new(data)) +} + +#[tokio::test] +async fn it_peeks() { + let mut reader = get_reader(); + assert_eq!(reader.peek().await.unwrap(), 'A'); + assert_eq!(reader.peek().await.unwrap(), 'A'); + assert_eq!(reader.peek().await.unwrap(), 'A'); +} + +#[tokio::test] +async fn it_consumes() { + let mut reader = get_reader(); + assert_eq!(reader.consume().await.unwrap(), 'A'); + assert_eq!(reader.consume().await.unwrap(), 'B'); + assert_eq!(reader.consume().await.unwrap(), 'C'); +} + +#[tokio::test] +async fn it_checks_for_eof() { + let mut reader = get_reader(); + assert!(!is_eof(reader.seek_to(29).await)); + assert!(!reader.check_eof().await); + assert!(!is_eof(reader.seek_to(47).await)); + assert!(is_eof(reader.consume().await.map(|_| ()))); + assert!(reader.check_eof().await); +} + +fn is_eof(result: TapeResult<()>) -> bool { + match result { + Err(TapeError::EOF) => true, + _ => false, + } +} diff --git a/src/traits.rs b/src/traits.rs deleted file mode 100644 index 7294f59..0000000 --- a/src/traits.rs +++ /dev/null @@ -1,3 +0,0 @@ -use tokio::io::{AsyncRead, AsyncSeek}; - -pub trait AsyncReadSeek: AsyncRead + AsyncSeek + Unpin {}