use pulldown_cmark::{CowStr, Event, Tag}; use std::collections::VecDeque; pub fn new<'a, I: Iterator>>(iter: I) -> FootnoteDefs<'a, I> { FootnoteDefs { iter, footnote_events: VecDeque::new(), has_started_emitting_defs: false, } } pub struct FootnoteDefs<'a, I: Iterator>> { iter: I, footnote_events: VecDeque>, has_started_emitting_defs: bool, } impl<'a, I: Iterator>> Iterator for FootnoteDefs<'a, I> { type Item = Event<'a>; fn next(&mut self) -> Option { match self.iter.next() { Some(Event::Start(Tag::FootnoteDefinition(ref id))) => { self.footnote_events.push_back(Event::Html( format!( r#"
{}."#, id, id ) .into(), )); loop { match self.iter.next() { Some(Event::End(Tag::FootnoteDefinition(_))) => { self.footnote_events.push_back(Event::Html("
".into())); break; } Some(e) => { self.footnote_events.push_back(e); } None => { break; } } } self.next() } Some(e) => Some(e), None => { if !self.has_started_emitting_defs && !self.footnote_events.is_empty() { self.has_started_emitting_defs = true; let before: CowStr<'a> = r#"
"#.into(); self.footnote_events.push_front(Event::Html(before)); let after: CowStr<'a> = "
".into(); self.footnote_events.push_back(Event::Html(after)); } self.footnote_events.pop_front() } } } } #[cfg(test)] mod tests { use pulldown_cmark::{html, Options, Parser}; fn render(s: &str) -> String { let mut out = String::new(); let parser = Parser::new_ext(s, Options::ENABLE_FOOTNOTES); let footnote_backrefs = super::new(parser); html::push_html(&mut out, footnote_backrefs); out } #[test] fn test_group_footnotes() { assert_eq!( render("foo[^1]\n\n[^1]: bar\n\nbaz"), r##"

foo1

baz


1.

bar

"## ); } #[test] fn test_multiple() { assert_eq!( render("foo[^1] bar[^2]\n\n[^1]: foo\n\n[^2]: bar"), r##"

foo1 bar2


1.

foo

2.

bar

"## ); } }