diff options
author | Neal H. Walfield <neal@pep.foundation> | 2018-04-11 14:41:11 +0200 |
---|---|---|
committer | Neal H. Walfield <neal@pep.foundation> | 2018-04-11 14:43:52 +0200 |
commit | 26b7598e27c7730ad0b88b1d7c8370b2a600b68a (patch) | |
tree | ddbc8fa0e9475293c30ec050ffd6152d4384ed0b /buffered-reader | |
parent | 947e04f310775e0acc4c9214a5b81921c5593097 (diff) |
buffered-reader: Add BufferedReaderDup.
- Add a new BufferedReader implementation that allows using a
BufferedReader as usual, but without actually consuming the data.
Diffstat (limited to 'buffered-reader')
-rw-r--r-- | buffered-reader/src/dup.rs | 197 | ||||
-rw-r--r-- | buffered-reader/src/lib.rs | 5 |
2 files changed, 202 insertions, 0 deletions
diff --git a/buffered-reader/src/dup.rs b/buffered-reader/src/dup.rs new file mode 100644 index 00000000..79f8c6da --- /dev/null +++ b/buffered-reader/src/dup.rs @@ -0,0 +1,197 @@ +use std::io; +use std::fmt; +use std::cmp; + +use super::*; + +/// A `BufferedReader` that duplicates the underlying `BufferedReader` +/// without consuming any of the data. +/// +/// Note: this will likely cause the underlying stream to buffer as +/// much data as you read. Thus, it should only be used for peeking +/// at the underlying `BufferedReader`. +pub struct BufferedReaderDup<R: BufferedReader<C>, C> { + reader: R, + + // The number of bytes that have been consumed. + cursor: usize, + + // The user settable cookie. + cookie: C, +} + +impl<R: BufferedReader<C>, C> fmt::Debug for BufferedReaderDup<R, C> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("BufferedReaderDup") + .field("reader", &self.reader) + .field("cursor", &self.cursor) + .finish() + } +} + +impl<R: BufferedReader<()>> BufferedReaderDup<R, ()> { + /// Instantiate a new memory-based reader. `buffer` contains the + /// reader's contents. + pub fn new(reader: R) -> Self { + Self::with_cookie(reader, ()) + } +} + +impl<R: BufferedReader<C>, C> BufferedReaderDup<R, C> { + /// Like `new()`, but sets a cookie, which can be retrieved using + /// the `cookie_ref` and `cookie_mut` methods, and set using + /// the `cookie_set` method. + pub fn with_cookie(reader: R, cookie: C) -> Self { + BufferedReaderDup { + reader: reader, + cursor: 0, + cookie: cookie, + } + } + + /// Returns the number of bytes that have been consumed by this + /// reader. + pub fn total_out(&self) -> usize { + return self.cursor; + } +} + +impl<R: BufferedReader<C>, C> io::Read for BufferedReaderDup<R, C> { + fn read(&mut self, buf: &mut [u8]) -> Result<usize, io::Error> { + let data = self.reader.data(self.cursor + buf.len())?; + assert!(data.len() >= self.cursor); + let data = &data[self.cursor..]; + + let amount = cmp::min(buf.len(), data.len()); + buf.copy_from_slice(&data[..amount]); + + self.cursor += amount; + + Ok(amount) + } +} + +impl<R: BufferedReader<C>, C> BufferedReader<C> for BufferedReaderDup<R, C> { + fn buffer(&self) -> &[u8] { + let data = self.reader.buffer(); + assert!(data.len() >= self.cursor); + &data[self.cursor..] + } + + fn data(&mut self, amount: usize) -> Result<&[u8], io::Error> { + let data = self.reader.data(self.cursor + amount)?; + assert!(data.len() >= self.cursor); + Ok(&data[self.cursor..]) + } + + fn consume(&mut self, amount: usize) -> &[u8] { + let data = self.reader.buffer(); + assert!(data.len() >= self.cursor + amount); + let data = &data[self.cursor..]; + self.cursor += amount; + data + } + + fn data_consume(&mut self, amount: usize) -> Result<&[u8], io::Error> { + let data = self.reader.data(self.cursor + amount)?; + assert!(data.len() >= self.cursor); + let data = &data[self.cursor..]; + self.cursor += cmp::min(data.len(), amount); + Ok(data) + } + + fn data_consume_hard(&mut self, amount: usize) -> Result<&[u8], io::Error> { + let data = self.reader.data_hard(self.cursor + amount)?; + assert!(data.len() >= self.cursor + amount); + let data = &data[self.cursor..]; + self.cursor += amount; + Ok(data) + } + + fn get_mut(&mut self) -> Option<&mut BufferedReader<C>> { + Some(&mut self.reader) + } + + fn get_ref(&self) -> Option<&BufferedReader<C>> { + Some(&self.reader) + } + + fn into_inner<'b>(self: Box<Self>) -> Option<Box<BufferedReader<C> + 'b>> + where Self: 'b { + Some(Box::new(self.reader)) + } + + fn cookie_set(&mut self, cookie: C) -> C { + use std::mem; + + mem::replace(&mut self.cookie, cookie) + } + + fn cookie_ref(&self) -> &C { + &self.cookie + } + + fn cookie_mut(&mut self) -> &mut C { + &mut self.cookie + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn buffered_reader_memory_test () { + let data : &[u8] = include_bytes!("buffered-reader-test.txt"); + let reader = BufferedReaderMemory::new(data); + let mut reader = BufferedReaderDup::new(reader); + + buffered_reader_test_data_check(&mut reader); + + let consumed = reader.total_out(); + assert_eq!(consumed, data.len()); + + // Since we haven't consumed the inner buffer, this should + // still work. + let mut reader = Box::new(reader).into_inner().unwrap(); + + // Try to read consumed + 1 bytes (which shouldn't be + // possible). + assert_eq!(consumed, reader.data(consumed + 1).unwrap().len()); + + buffered_reader_test_data_check(&mut reader); + } + + // Test that buffer() returns the same data as data(). + #[test] + fn buffer_test() { + // Test vector. A BufferedReaderDup returns all unconsumed + // data. So, use a relatively small buffer size. + let size = DEFAULT_BUF_SIZE; + let mut input = Vec::with_capacity(size); + let mut v = 0u8; + for _ in 0..size { + input.push(v); + if v == std::u8::MAX { + v = 0; + } else { + v += 1; + } + } + + let reader = BufferedReaderMemory::new(&input[..]); + let mut reader = BufferedReaderDup::new(reader); + + for i in 0..input.len() { + let data = reader.data(DEFAULT_BUF_SIZE + 1).unwrap().to_vec(); + assert!(data.len() > 0); + assert_eq!(data, reader.buffer()); + // And, we may as well check to make sure we read the + // right data. + assert_eq!(data, &input[i..i+data.len()]); + + // Consume one byte and see what happens. + reader.consume(1); + } + } +} diff --git a/buffered-reader/src/lib.rs b/buffered-reader/src/lib.rs index ae2d44ed..ab19fa37 100644 --- a/buffered-reader/src/lib.rs +++ b/buffered-reader/src/lib.rs @@ -11,12 +11,14 @@ use std::fmt; mod generic; mod memory; mod limitor; +mod dup; mod eof; mod decompress; pub use self::generic::BufferedReaderGeneric; pub use self::memory::BufferedReaderMemory; pub use self::limitor::BufferedReaderLimitor; +pub use self::dup::BufferedReaderDup; pub use self::eof::BufferedReaderEOF; pub use self::decompress::BufferedReaderDeflate; pub use self::decompress::BufferedReaderZlib; @@ -116,6 +118,9 @@ pub trait BufferedReader<C> : io::Read + fmt::Debug { /// This is a convenient function that effectively combines data() /// and consume(). + /// + /// If less than `amount` bytes are available, this consumes only + /// what is available. fn data_consume(&mut self, amount: usize) -> Result<&[u8], std::io::Error>; |