parent
b0532856d5
commit
3df49ca9a9
@ -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<Box<dyn AsyncReadSeek>>,
|
||||
}
|
||||
|
||||
impl InputReader {
|
||||
pub fn new<T: AsyncReadSeek + 'static>(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<char> {
|
||||
self.read_next().await
|
||||
}
|
||||
|
||||
/// Returns the next char without forwarding
|
||||
pub async fn peek(&mut self) -> TapeResult<char> {
|
||||
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<bool> {
|
||||
let char = self.read_next().await?;
|
||||
|
||||
Ok(char == '\x00')
|
||||
}
|
||||
|
||||
/// Reads the next char returning \x00 for EOF
|
||||
async fn read_next(&mut self) -> TapeResult<char> {
|
||||
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()),
|
||||
}
|
||||
}
|
||||
}
|
@ -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<dyn AsyncBufRead + Unpin>,
|
||||
buf: String,
|
||||
index: usize,
|
||||
}
|
||||
|
||||
impl InputReader {
|
||||
pub fn new<T: AsyncBufRead + Unpin + 'static>(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<char> {
|
||||
self.read_next().await
|
||||
}
|
||||
|
||||
/// Returns the next char without forwarding
|
||||
#[inline]
|
||||
pub async fn peek(&mut self) -> TapeResult<char> {
|
||||
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<char> {
|
||||
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(())
|
||||
}
|
||||
}
|
@ -1,3 +1,5 @@
|
||||
pub mod error;
|
||||
pub mod input;
|
||||
mod traits;
|
||||
pub mod input_reader;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
@ -0,0 +1 @@
|
||||
mod test_input;
|
@ -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,
|
||||
}
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
use tokio::io::{AsyncRead, AsyncSeek};
|
||||
|
||||
pub trait AsyncReadSeek: AsyncRead + AsyncSeek + Unpin {}
|
Loading…
Reference in New Issue