use opts::{self, error::Error::ConflictingOption};
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> {
use opts::flags::In;
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(In::COUNT_BYTES)) {
(Some(bytes), true) => Some(bytes),
(Some(blocks), false) => Some(blocks * o.input_block_size),
_ => None,
},
sync: o.iflag(In::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> {
#[inline]
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> { self.inner.read(buf) }
}
impl<'a, R: Read> BufRead for Reader<'a, R> {
#[inline]
fn fill_buf(&mut self) -> std::io::Result<&[u8]> {
self.front += match self.remaining_bytes {
Some(0) => return Ok(&self.buf[0..0]),
None => self.inner.read(&mut self.buf[self.front..])?,
Some(n) => {
let back = usize::min(n, self.buf.len());
self.inner.read(&mut self.buf[self.front..back])?
},
};
if self.sync {
if self.mode == opts::Mode::Standard {
self.buf.iter_mut().for_each(|b| *b = 0);
} else {
self.buf.iter_mut().for_each(|b| *b = b' ');
}
Ok(&self.buf)
} else {
Ok(&self.buf[..self.front])
}
}
#[inline]
fn consume(&mut self, bytes: usize) { self.front -= bytes }
}
impl<'a> InnerReader<'a, File> {
fn new(stdin: &'a Stdin, o: &opts::Opts) -> Result<Self> {
use opts::flags::In;
let ibs = o.input_block_size;
match &o.input_file {
None if o.iflag(In::APPEND) => {
return Err(Error::from(ConflictingOption(
"iflag 'append' is only usable with an input file",
)))
},
None if o.input_seek.is_some() || o.iflag(In::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(In::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),
}
}
}