csplit 0.1.0

a clone of the unix coreutil csplit
use super::Pattern;

/// Chunks corresponding to a matched pattern.
/// That is, yields a string corresponding from the current line
/// to the next match
pub struct Chunks<'a, P>
where
    P: Iterator<Item = Pattern>,
{
    s: &'a str,
    indices: Indices<'a, P>,
}

/// Indices yields the `(start, end)` pairs that mark the
/// lines where a new file should start or end according to the
/// provided patterns. Is not used directly;
struct Indices<'a, P>
where
    P: Iterator<Item = Pattern>,
{
    lines: std::iter::Enumerate<std::str::Lines<'a>>,
    patterns: P,
    pattern: Pattern,
    start: usize,
}

impl<'a, P> Indices<'a, P>
where
    P: Iterator<Item = Pattern>,
{
     fn new<I>(s: &'a str, patterns: I) -> Option<Self>
    where
        I: IntoIterator<IntoIter = P, Item = Pattern>,
    {
        let mut patterns = patterns.into_iter();
        let pattern = match patterns.next() {
            Some(p) => p,
            None => return None,
        };
        Some(Indices {
            lines: s.lines().enumerate(),
            patterns,
            pattern,
            start: 0,
        })
    }
}

impl<'a, P> Chunks<'a, P>
where
    P: Iterator<Item = Pattern>,
{
    pub fn new<I>(s: &'a str, patterns: I) -> Option<Self>
    where
        I: IntoIterator<Item = Pattern, IntoIter = P>,
    {
        let indices = Indices::new(&s, patterns)?;
        Some(Chunks { s, indices })
    }
}

impl<'a, P> Iterator for Chunks<'a, P>
where
    P: Iterator<Item = Pattern>,
{
    type Item = String;

    fn next(&mut self) -> Option<Self::Item> {
        if let Some((start, end)) = self.indices.next() {
            let lines: Vec<&str> = self.s.lines().skip(start).take(end).collect();
            Some(lines.join("\n"))
        } else {
            None
        }
    }
}
impl<'a, P> Iterator for Indices<'a, P>
where
    P: Iterator<Item = Pattern>,
{
    type Item = (usize, usize);

    fn next(&mut self) -> Option<Self::Item> {
        while let Some((i, text)) = self.lines.next() {
            if let Some((end, write)) = self.pattern.find(self.start, i, text) {
                let start = self.start;
                self.start = end;
                self.pattern = match &self.pattern {
                    Pattern::Match { repeat: 0, .. } | Pattern::NLines { repeat: 0, .. } | Pattern::UntilLine(_) => {
                        match self.patterns.next() {
                            Some(p) => p,
                            None => break,
                        }
                    },
                    Pattern::Match {
                        re,
                        repeat,
                        offset,
                        skip,
                    } => Pattern::Match {
                        re: re.clone(),
                        repeat: repeat - 1,
                        offset: *offset,
                        skip: *skip,
                    },
                    Pattern::NLines { n, repeat } => Pattern::NLines {
                        n: *n,
                        repeat: repeat - 1,
                    },
                };
                if write {
                    return Some((start, end));
                } else {
                    return self.next();
                }
            };
        }
        None
    }
}