#![forbid(missing_docs)]
#![cfg_attr(test, deny(warnings))]
use std::fmt;
use std::fs::File;
use std::io::{self, BufReader, Read};
use std::path::Path;
trait FileLike: fmt::Debug + Read + Sized {
fn open<P: AsRef<Path>>(p: P) -> io::Result<Self>;
}
impl FileLike for File {
#[inline]
fn open<P: AsRef<Path>>(p: P) -> io::Result<Self> {
File::open(p)
}
}
struct InnerReader<R, I: IntoIterator> {
curr: Option<R>,
rest: I::IntoIter,
}
impl<R, I> InnerReader<R, I>
where
R: FileLike,
I: IntoIterator,
I::Item: AsRef<Path>,
{
#[inline]
fn new(iter: I) -> InnerReader<R, I> {
InnerReader {
curr: None,
rest: iter.into_iter(),
}
}
}
impl<I> InnerReader<File, I>
where
I: IntoIterator,
I::Item: AsRef<Path>,
I::IntoIter: Clone,
{
#[inline]
fn try_clone(&self) -> io::Result<InnerReader<File, I>> {
Ok(InnerReader {
curr: match self.curr.as_ref().map(|f| f.try_clone()) {
Some(Err(e)) => return Err(e),
Some(Ok(f)) => Some(f),
None => None,
},
rest: self.rest.clone(),
})
}
}
impl<R, I> Read for InnerReader<R, I>
where
R: FileLike,
I: IntoIterator,
I::Item: AsRef<Path>,
{
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
if buf.is_empty() {
return Ok(0);
}
loop {
if self.curr.is_none() {
self.curr = match self.rest.next() {
None => return Ok(0),
Some(p) => Some(try!(FileLike::open(p))),
};
}
let n = self.curr.as_mut().unwrap().read(buf);
match n {
Ok(0) => self.curr = None,
val => return val,
}
}
}
}
impl<R, I> fmt::Debug for InnerReader<R, I>
where
R: fmt::Debug,
I: IntoIterator,
I::Item: fmt::Debug,
I::IntoIter: Clone,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let rest: Vec<_> = self.rest.clone().collect();
f.debug_struct("CatReader")
.field("curr", &self.curr)
.field("rest", &rest)
.finish()
}
}
pub struct CatReader<I: IntoIterator> {
inner: InnerReader<File, I>,
}
impl<I> CatReader<I>
where
I: IntoIterator,
I::Item: AsRef<Path>,
{
#[inline]
pub fn new(iter: I) -> CatReader<I> {
CatReader {
inner: InnerReader::new(iter),
}
}
#[inline]
pub fn buffered(iter: I) -> BufReader<CatReader<I>> {
BufReader::new(CatReader::new(iter))
}
}
impl<I> CatReader<I>
where
I: IntoIterator,
I::Item: AsRef<Path>,
I::IntoIter: Clone,
{
#[inline]
pub fn try_clone(&self) -> io::Result<CatReader<I>> {
Ok(CatReader {
inner: self.inner.try_clone()?,
})
}
}
impl<I> Read for CatReader<I>
where
I: IntoIterator,
I::Item: AsRef<Path>,
{
#[inline]
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.inner.read(buf)
}
}
impl<I> fmt::Debug for CatReader<I>
where
I: IntoIterator,
I::Item: fmt::Debug,
I::IntoIter: Clone,
{
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Debug::fmt(&self.inner, f)
}
}
#[cfg(test)]
mod tests {
use super::{FileLike, InnerReader};
use std::io::{self, Read};
use std::path::Path;
impl FileLike for &'static [u8] {
fn open<P: AsRef<Path>>(p: P) -> io::Result<&'static [u8]> {
let string = p.as_ref().to_string_lossy().into_owned();
let reference: &str = &string;
match reference {
"test1.txt" => Ok(b"some\ntext\n"),
"dir/other.test.txt" => Ok(b"here's "),
_ => Err(io::Error::new(io::ErrorKind::NotFound, "file missing")),
}
}
}
#[test]
fn everything() {
let strs = &["dir/other.test.txt", "404", "test1.txt"];
let mut reader: InnerReader<&'static [u8], _> = InnerReader::new(strs);
assert_eq!(
format!("{:?}", reader),
"CatReader { curr: None, rest: [\"dir/other.test.txt\", \"404\", \
\"test1.txt\"] }"
);
let mut buf = [];
assert_eq!(reader.read(&mut buf).unwrap(), 0);
assert_eq!(
format!("{:?}", reader),
"CatReader { curr: None, rest: [\"dir/other.test.txt\", \"404\", \
\"test1.txt\"] }"
);
let mut buf = [0];
assert_eq!(reader.read(&mut buf).unwrap(), 1);
assert_eq!(buf, [104]);
assert_eq!(
format!("{:?}", reader),
"CatReader { curr: Some([101, 114, 101, 39, 115, 32]), rest: [\"404\", \
\"test1.txt\"] }"
);
let mut buf = Vec::new();
assert!(reader.read_to_end(&mut buf).is_err());
assert_eq!(reader.read_to_end(&mut buf).unwrap(), 10);
assert_eq!(buf, b"ere's some\ntext\n");
assert_eq!(
format!("{:?}", reader),
"CatReader { curr: None, rest: [] }"
);
}
}