1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
use crate::matchers::Matcher;
use pulldown_cmark::{Event, Tag};

/// Matches the items inside a heading tag, including the start and end tags.
#[derive(Debug, Clone, PartialEq)]
pub struct Heading {
    inside_heading: bool,
    level: Option<u32>,
}

impl Heading {
    /// Create a new [`Heading`].
    const fn new(level: Option<u32>) -> Self {
        Heading {
            level,
            inside_heading: false,
        }
    }

    /// Matches any heading.
    pub const fn any_level() -> Self { Heading::new(None) }

    /// Matches only headings with the desired level.
    pub const fn with_level(level: u32) -> Self { Heading::new(Some(level)) }

    fn matches_level(&self, level: u32) -> bool {
        match self.level {
            Some(expected) => level == expected,
            None => true,
        }
    }
}

impl Matcher for Heading {
    fn matches_event(&mut self, event: &Event<'_>) -> bool {
        match event {
            Event::Start(Tag::Heading(level)) if self.matches_level(*level) => {
                self.inside_heading = true;
            },
            Event::End(Tag::Heading(level)) if self.matches_level(*level) => {
                self.inside_heading = false;
                // make sure the end tag is also matched
                return true;
            },
            _ => {},
        }

        self.inside_heading
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use pulldown_cmark::LinkType;

    #[test]
    fn match_everything_inside_a_header() {
        // The original text for these events was:
        //
        // This is some text.
        //
        // ## Then a *header*
        //
        // [And a link](https://example.com)
        let inputs = vec![
            (Event::Start(Tag::Paragraph), false),
            (Event::Text("This is some text.".into()), false),
            (Event::End(Tag::Paragraph), false),
            (Event::Start(Tag::Heading(2)), true),
            (Event::Text("Then a ".into()), true),
            (Event::Start(Tag::Emphasis), true),
            (Event::Text("header".into()), true),
            (Event::End(Tag::Emphasis), true),
            (Event::End(Tag::Heading(2)), true),
            (Event::Start(Tag::Paragraph), false),
            (
                Event::Start(Tag::Link(
                    LinkType::Inline,
                    "https://example.com".into(),
                    "".into(),
                )),
                false,
            ),
            (Event::Text("And a link".into()), false),
            (
                Event::End(Tag::Link(
                    LinkType::Inline,
                    "https://example.com".into(),
                    "".into(),
                )),
                false,
            ),
            (Event::End(Tag::Paragraph), false),
        ];

        let mut matcher = Heading::any_level();

        for (tag, should_be) in inputs {
            let got = matcher.matches_event(&tag);
            assert_eq!(got, should_be, "{:?}", tag);
        }
    }
}