use pulldown_cmark::{Event, Parser, Tag}; use std::ops::Range; #[derive(Debug)] pub struct IgnoreBlocks { ignore: Vec>, } impl IgnoreBlocks { pub fn new(s: &str) -> IgnoreBlocks { let mut ignore = Vec::new(); let mut parser = Parser::new(s).into_offset_iter(); while let Some((event, range)) = parser.next() { if let Event::Start(Tag::CodeBlock(_)) = event { let start = range.start; while let Some((event, range)) = parser.next() { if let Event::End(Tag::CodeBlock(_)) = event { ignore.push(start..range.end); break; } } } else if let Event::Start(Tag::BlockQuote) = event { let start = range.start; let mut count = 1; while let Some((event, range)) = parser.next() { if let Event::Start(Tag::BlockQuote) = event { count += 1; } else if let Event::End(Tag::BlockQuote) = event { count -= 1; if count == 0 { ignore.push(start..range.end); break; } } } } else if let Event::Code(_) = event { ignore.push(range); } } IgnoreBlocks { ignore } } pub fn overlaps_ignore(&self, region: Range) -> Option> { for ignore in &self.ignore { // See https://stackoverflow.com/questions/3269434. if ignore.start <= region.end && region.start <= ignore.end { return Some(ignore.clone()); } } None } } #[cfg(test)] #[derive(Debug, PartialEq, Eq)] enum Ignore<'a> { Yes(&'a str), No(&'a str), } #[cfg(test)] fn bodies(s: &str) -> Vec> { let mut bodies = Vec::new(); let cbs = IgnoreBlocks::new(s); let mut previous = 0..0; for range in &cbs.ignore { let range = range.clone(); if previous.end != range.start { bodies.push(Ignore::No(&s[previous.end..range.start])); } bodies.push(Ignore::Yes(&s[range.clone()])); previous = range.clone(); } if let Some(range) = cbs.ignore.last() { if range.end != s.len() { bodies.push(Ignore::No(&s[range.end..])); } } bodies } #[test] fn cbs_1() { assert_eq!( bodies("`hey you`bar me too"), [Ignore::Yes("`hey you`"), Ignore::No("bar me too")] ); } #[test] fn cbs_2() { assert_eq!( bodies("`hey you` me too"), [Ignore::Yes("`hey you`"), Ignore::No(" me too")] ); } #[test] fn cbs_3() { assert_eq!( bodies(r"`hey you\` `me too"), [Ignore::Yes(r"`hey you\`"), Ignore::No(" `me too")] ); } #[test] fn cbs_4() { assert_eq!( bodies( " ```language_spec testing ``` nope " ), [ Ignore::No("\n"), Ignore::Yes("```language_spec\ntesting\n```"), Ignore::No("\n\nnope\n") ], ); } #[test] fn cbs_5() { assert_eq!( bodies(concat!( " ``` tag_after_space testing ```", " " )), [ Ignore::No("\n"), Ignore::Yes("``` tag_after_space\ntesting\n``` "), ], ); } #[test] fn cbs_6() { assert_eq!( bodies( " this is indented this is indented too " ), [ Ignore::No("\n "), Ignore::Yes("this is indented\n this is indented too\n"), ], ); } #[test] fn cbs_7() { assert_eq!( bodies( " ``` testing unclosed " ), [Ignore::No("\n"), Ignore::Yes("```\ntesting unclosed\n"),], ); } #[test] fn cbs_8() { assert_eq!( bodies("`one` not `two`"), [ Ignore::Yes("`one`"), Ignore::No(" not "), Ignore::Yes("`two`") ] ); } #[test] fn cbs_9() { assert_eq!( bodies( " some text > testing citations still in citation more text " ), [ Ignore::No("\nsome text\n"), Ignore::Yes("> testing citations\nstill in citation\n"), Ignore::No("\nmore text\n") ], ); } #[test] fn cbs_10() { assert_eq!( bodies( " # abc > multiline > citation lorem ipsum " ), [ Ignore::No("\n# abc\n\n"), Ignore::Yes("> multiline\n> citation\n"), Ignore::No("\nlorem ipsum\n") ], ); } #[test] fn cbs_11() { assert_eq!( bodies( " > some > > nested > citations " ), [ Ignore::No("\n"), Ignore::Yes("> some\n> > nested\n> citations\n"), ], ); }