use opts::{self, Error::ConflictingOption, IFlag};
use results::{Error, Result};
use std::{
fs::File,
io::{BufRead, Read, Seek, SeekFrom, Stdin, StdinLock},
};
pub struct Reader<'a, R: Read> {
inner: InnerReader<'a, R>,
pub buf: Vec<u8>,
pub front: usize,
pub remaining_bytes: Option<usize>,
pub mode: opts::Mode,
pub sync: bool,
}
#[derive(Debug)]
pub enum InnerReader<'a, R: Read> {
Stdin(StdinLock<'a>),
R(R),
}
impl<'a> Reader<'a, File> {
pub fn new(stdin: &'a Stdin, o: &opts::Opts) -> Result<Self> {
Ok(Reader {
buf: vec![0; o.input_block_size],
front: 0,
mode: o.mode,
inner: InnerReader::new(stdin, o)?,
remaining_bytes: match (o.count, o.iflag(IFlag::COUNT_BYTES)) {
(Some(bytes), true) => Some(bytes),
(Some(blocks), false) => Some(blocks * o.input_block_size),
_ => None,
},
sync: o.iflag(IFlag::SYNC),
})
}
}
impl<'a, R: Read> Reader<'a, R> {
pub fn basic(inner: InnerReader<'a, R>, block_size: usize) -> Self {
Reader {
buf: vec![0; block_size],
front: 0,
mode: opts::Mode::Standard,
inner,
remaining_bytes: None,
sync: false,
}
}
}
impl<'a, R: Read> Read for Reader<'a, R> {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> { self.inner.read(buf) }
}
impl<'a, R: Read> BufRead for Reader<'a, R> {
fn fill_buf(&mut self) -> std::io::Result<&[u8]> {
self.front += match self.remaining_bytes {
Some(0) => return Ok(&self.buf[0..0]),
Some(n) => {
let back = usize::min(n, self.buf.len());
self.inner.read(&mut self.buf[self.front..back])?
},
None => self.inner.read(&mut self.buf[self.front..])?,
};
match (self.sync, self.mode) {
(true, opts::Mode::Standard) => {
self.buf.iter_mut().for_each(|b| *b = 0);
Ok(&self.buf)
},
(true, _) => {
self.buf.iter_mut().for_each(|b| *b = b' ');
Ok(&self.buf)
},
(false, _) => Ok(&self.buf[..self.front]),
}
}
fn consume(&mut self, bytes: usize) { self.front -= bytes }
}
impl<'a> InnerReader<'a, File> {
fn new(stdin: &'a Stdin, o: &opts::Opts) -> Result<Self> {
let ibs = o.input_block_size;
match &o.input_file {
None if o.iflag(IFlag::APPEND) => {
return Err(Error::from(ConflictingOption(
"iflag 'append' is only usable with an input file",
)))
},
None if o.input_seek.is_some() || o.iflag(IFlag::SKIP_BYTES) => {
return Err(Error::from(ConflictingOption(
"options 'input_seek' and 'seek_bytes' are only usable with an input file",
)))
},
None => Ok(InnerReader::Stdin(stdin.lock())),
Some(path) => {
let mut f = File::open(path)?;
match (o.input_seek, o.iflag(IFlag::SKIP_BYTES)) {
(None, true) => {
return Err(Error::from(ConflictingOption(
"cannot use 'seek_bytes' with no 'input_seek' distance selected",
)))
},
(None, false) => {},
(Some(bytes), true) => {
f.seek(SeekFrom::Start(bytes as u64))?;
},
(Some(blocks), false) => {
f.seek(SeekFrom::Start(blocks as u64 * ibs as u64))?;
},
};
Ok(InnerReader::R(f))
},
}
}
}
impl<'a, T: BufRead> From<StdinLock<'a>> for InnerReader<'a, T> {
fn from(s: StdinLock) -> InnerReader<T> { InnerReader::Stdin(s) }
}
impl<'a> From<File> for InnerReader<'a, File> {
fn from(f: File) -> InnerReader<'a, File> { InnerReader::R(f) }
}
impl<'a, T: Read> Read for InnerReader<'a, T> {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
match self {
InnerReader::R(f) => f.read(buf),
InnerReader::Stdin(s) => s.read(buf),
}
}
}