harper 0.0.2

A library to implement SSGs.
Documentation
use std::collections::VecDeque;

use pulldown_cmark::{Event, Tag, InlineStr, CowStr::*, TagEnd};

use super::Plugin;

pub trait CodeFilter: FnMut(&str, usize) -> bool {}
impl<F: FnMut(&str, usize) -> bool> CodeFilter for F {}

#[derive(Clone)]
pub struct CodeTrim<F> {
    trimmer: F,
}

#[derive(Clone)]
struct Iter<'a, F, I: Iterator<Item = Event<'a>>> {
    trimmer: F,
    inner: I,
    line_num: Option<usize>,
    stack: VecDeque<Event<'a>>,
}

impl<F: CodeFilter> CodeTrim<F> {
    pub fn trim(trimmer: F) -> Self { Self { trimmer } }
}

impl CodeTrim<()> {
    pub fn trim_start() -> CodeTrim<impl CodeFilter> {
        let mut is_start = true;
        CodeTrim::trim(move |line: &str, n: usize| {
            if n == 0 { is_start = true; }
            if is_start && line.bytes().all(|c| c.is_ascii_whitespace()) {
                true
            } else {
                is_start = false;
                false
            }
        })
    }
}

impl<F: CodeFilter> Plugin for CodeTrim<F> {
    fn remap<'a, I>(&'a mut self, i: I) -> impl Iterator<Item = Event<'a>> + 'a
        where I: Iterator<Item = Event<'a>> + 'a
    {
        Iter {
            trimmer: &mut self.trimmer,
            inner: i,
            line_num: None,
            stack: VecDeque::new(),
        }
    }
}

impl<'a, F: CodeFilter, I: Iterator<Item = Event<'a>>> Iterator for Iter<'a, F, I> {
    type Item = Event<'a>;

    fn next(&mut self) -> Option<Self::Item> {
        loop {
            if let Some(event) = self.stack.pop_front() {
                return Some(event);
            }

            let event = self.inner.next()?;
            match &event {
                Event::Start(Tag::CodeBlock(_)) => {
                    self.line_num = Some(0);
                    return Some(event);
                }
                Event::End(TagEnd::CodeBlock) => {
                    self.line_num = None;
                    return Some(event);
                }
                Event::Text(text) if self.line_num.is_some() => {
                    let line_num = self.line_num.as_mut().unwrap();

                    let mut i = 0;
                    while i < text.len() {
                        let j = memchr::memchr(b'\n', text[i..].as_bytes())
                            .map(|k| i + k + 1)
                            .unwrap_or(text.len());

                        let line = &text[i..j];
                        if !(self.trimmer)(line, *line_num) {
                            let text = match text {
                                Inlined(_) => Inlined(InlineStr::try_from(line).unwrap()),
                                Borrowed(s) => Borrowed(&s[i..j]),
                                _ => line.to_string().into(),
                            };

                            self.stack.push_back(Event::Text(text));
                        }

                        *line_num += 1;
                        i = j;
                    }
                }
                _ => return Some(event),
            }
        }
    }
}