slice-command 0.4.2

slice is a command-line tool that allows you to slice the contents of a file using syntax similar to Python's slice notation.
use std::io::{self, BufRead};

#[derive(Debug)]
pub(crate) struct LinesWithEol<B> {
    buf: B,
}

impl<B: BufRead> Iterator for LinesWithEol<B> {
    type Item = io::Result<Vec<u8>>;

    #[inline]
    fn next(&mut self) -> Option<io::Result<Vec<u8>>> {
        let mut buf = Default::default();
        match self.buf.read_until(b'\n', &mut buf) {
            Ok(0) => None,
            Ok(_n) => Some(Ok(buf)),
            Err(e) => Some(Err(e)),
        }
    }
}

#[derive(Clone, Debug)]
pub(crate) struct Delimited<'d, B> {
    buf: B,
    delimiter: &'d [u8],
}

impl<B: BufRead> Iterator for Delimited<'_, B> {
    type Item = io::Result<Vec<u8>>;

    #[inline]
    fn next(&mut self) -> Option<Self::Item> {
        if let Some(last) = self.delimiter.last() {
            let mut buf = Default::default();
            loop {
                match self.buf.read_until(*last, &mut buf) {
                    Ok(0) => return if buf.is_empty() { None } else { Some(Ok(buf)) },
                    Ok(_n) => {
                        if buf.ends_with(self.delimiter) {
                            return Some(Ok(buf));
                        }
                    }
                    Err(e) => return Some(Err(e)),
                }
            }
        } else {
            let mut buf = [0; 1];
            match self.buf.read(&mut buf) {
                Ok(0) => None,
                Ok(_n) => Some(Ok(Vec::from(buf))),
                Err(e) => Some(Err(e)),
            }
        }
    }
}

pub(crate) trait BufReadExt {
    #[inline]
    fn lines_with_eol(self) -> LinesWithEol<Self>
    where
        Self: Sized,
    {
        LinesWithEol { buf: self }
    }

    #[inline]
    fn delimit_by(self, delimiter: &[u8]) -> Delimited<Self>
    where
        Self: Sized,
    {
        Delimited {
            buf: self,
            delimiter,
        }
    }
}

impl<B: BufRead> BufReadExt for B {}

#[cfg(test)]
mod tests {
    use super::*;
    use std::io::BufReader;

    #[test]
    fn empty_lines_with_eol() {
        let mut lines = BufReader::new(&b""[..]).lines_with_eol();
        assert!(lines.next().is_none());
    }

    #[test]
    fn lines_with_eol() {
        let mut lines = BufReader::new(&b"1\n2\n"[..]).lines_with_eol();
        assert_eq!(b"1\n", lines.next().unwrap().unwrap().as_slice());
        assert_eq!(b"2\n", lines.next().unwrap().unwrap().as_slice());
    }

    #[test]
    fn lines_without_eol() {
        let mut lines = BufReader::new(&b"1\n2"[..]).lines_with_eol();
        assert_eq!(b"1\n", lines.next().unwrap().unwrap().as_slice());
        assert_eq!(b"2", lines.next().unwrap().unwrap().as_slice());
    }

    #[test]
    fn empty_delimit_by_empty() {
        let mut delimited = BufReader::new(&b""[..]).delimit_by(&b""[..]);
        assert!(delimited.next().is_none());
    }

    #[test]
    fn empty_delimit_by_character() {
        let mut delimited = BufReader::new(&b""[..]).delimit_by(&b"|"[..]);
        assert!(delimited.next().is_none());
    }

    #[test]
    fn empty_delimit_by_string() {
        let mut delimited = BufReader::new(&b""[..]).delimit_by(&b"||"[..]);
        assert!(delimited.next().is_none());
    }

    #[test]
    fn delimit_by_empty() {
        let mut delimited = BufReader::new(&b"a|b|"[..]).delimit_by(&b""[..]);
        assert_eq!(b"a", delimited.next().unwrap().unwrap().as_slice());
        assert_eq!(b"|", delimited.next().unwrap().unwrap().as_slice());
        assert_eq!(b"b", delimited.next().unwrap().unwrap().as_slice());
        assert_eq!(b"|", delimited.next().unwrap().unwrap().as_slice());
    }

    #[test]
    fn delimit_by_character() {
        let mut delimited = BufReader::new(&b"a|b|"[..]).delimit_by(&b"|"[..]);
        assert_eq!(b"a|", delimited.next().unwrap().unwrap().as_slice());
        assert_eq!(b"b|", delimited.next().unwrap().unwrap().as_slice());
    }

    #[test]
    fn delimit_by_string() {
        let mut delimited = BufReader::new(&b"a|||b|"[..]).delimit_by(&b"||"[..]);
        assert_eq!(b"a||", delimited.next().unwrap().unwrap().as_slice());
        assert_eq!(b"|b|", delimited.next().unwrap().unwrap().as_slice());
    }
}