use std::iter::empty; use crate::generator::util::one_more::OneMore; use crate::generator::util::slugify::slugify_iter; use pulldown_cmark::{html, CowStr, Event, HeadingLevel, Tag}; use State::*; pub struct HeadingAnchors<'a, I: Iterator>> { iter: I, state: State, } pub fn new<'a, I: Iterator>>(iter: I) -> HeadingAnchors<'a, I> { HeadingAnchors { iter, state: TakeEvent, } } impl<'a, I: Iterator>> Iterator for HeadingAnchors<'a, I> { type Item = Event<'a>; fn next(&mut self) -> Option { match self.state { TakeEvent => match self.iter.next() { Some(e) => Some(self.handle_upstream_event(e)), None => { self.state = Done; None } }, Done => None, } } } impl<'a, I: Iterator>> HeadingAnchors<'a, I> { fn handle_upstream_event(&mut self, e: Event<'a>) -> Event<'a> { match e { Event::Start(Tag::Heading(level, _, _)) => { let inside_heading = self.accumulate_heading_contents(); let slug = slugify_events(&inside_heading); let mut inner = String::new(); html::push_html(&mut inner, inside_heading.into_iter()); let hash_marks = level_hash_marks(level); let heading_html = format!( "\ <{} id=\"{}\">\ {} \ {}\ ", level, slug, slug, hash_marks, inner, level ); Event::Html(CowStr::Boxed(heading_html.into_boxed_str())) } _ => e, } } fn accumulate_heading_contents(&mut self) -> Vec> { let mut events = vec![]; while let Some(e) = self.iter.next() { if let Event::End(Tag::Heading(_, _, _)) = e { break; } else { events.push(e); } } events } } fn slugify_events<'a>(events: &[Event<'a>]) -> String { let chars = events .iter() .flat_map(|e| -> Box> { match e { Event::Text(s) | Event::Code(s) => Box::new(OneMore::new(s.chars(), '-')), _ => Box::new(empty()), } }); slugify_iter(chars) } fn level_hash_marks(level: HeadingLevel) -> &'static str { match level { HeadingLevel::H1 => "#", HeadingLevel::H2 => "##", HeadingLevel::H3 => "###", HeadingLevel::H4 => "####", HeadingLevel::H5 => "#####", HeadingLevel::H6 => "######", } } enum State { TakeEvent, Done, } #[cfg(test)] mod tests { use pulldown_cmark::{html, CowStr, Event, Parser}; fn render(s: &str) -> String { let mut out = String::new(); let parser = Parser::new(s); let heading_anchors = super::new(parser); html::push_html(&mut out, heading_anchors); out } #[test] fn test_slugify_events() { let slugified = super::slugify_events(&[ Event::Text(CowStr::Borrowed("foo")), Event::Code(CowStr::Borrowed("bar")), Event::Text(CowStr::Borrowed("baz")), ]); assert_eq!(slugified, "foo-bar-baz"); } #[test] fn test_heading_ids() { assert_eq!( render("# Test"), r##"

Test

"## ); assert_eq!( render("# `Test`"), r##"

Test

"## ); assert_eq!( render("# [Test](https://example.com)"), r##"

Test

"## ); } }