dd-lib 0.2.1

library functions for a clone of the unix coreutil dd
Documentation
use opts::{self, Error::ConflictingOption, IFlag};
use results::{Error, Result};
use std::{
    fs::File,
    io::{BufRead, Read, Seek, SeekFrom, Stdin, StdinLock},
};

/// Readers read blocks from the specified file or [Stdin][std::io::Stdin]
pub struct Reader<'a, R: Read> {
    inner: InnerReader<'a, R>,
    /// internal buffer to implement BufRead
    pub buf: Vec<u8>,
    /// the front of the buffer; beyond here is free space
    pub front: usize,
    /// the remaining bytes to read. None means to continue reading until EOF.
    pub remaining_bytes: Option<usize>,
    /// the mode (`Standard`, `Block`, or `Unblock`) that dd will run in
    pub mode: opts::Mode,
    /// if true, pad every read blocks to self.buf.len() size
    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> {
        // let fullblock = opts.iflags.contains(IFlag::FULLBLOCK),

        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",
                )))
            },

            // valid stdin
            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),
        }
    }
}