harper_core/patterns/
word.rs

1use super::Pattern;
2
3use crate::{CharString, Token};
4
5/// Matches a predefined word.
6///
7/// Note that any capitalization of the contained words will result in a match.
8#[derive(Clone)]
9pub struct Word {
10    word: CharString,
11    exact: bool,
12}
13
14impl Word {
15    /// Matches the provided word, ignoring case.
16    pub fn new(word: &'static str) -> Self {
17        Self {
18            word: word.chars().collect(),
19            exact: false,
20        }
21    }
22    /// Matches the provided word, ignoring case.
23    pub fn from_chars(word: &[char]) -> Self {
24        Self {
25            word: word.iter().copied().collect(),
26            exact: false,
27        }
28    }
29
30    /// Matches the provided word, case-sensitive.
31    pub fn new_exact(word: &'static str) -> Self {
32        Self {
33            word: word.chars().collect(),
34            exact: true,
35        }
36    }
37}
38
39impl Pattern for Word {
40    fn matches(&self, tokens: &[Token], source: &[char]) -> Option<usize> {
41        let tok = tokens.first()?;
42        if !tok.kind.is_word() {
43            return None;
44        }
45        if tok.span.len() != self.word.len() {
46            return None;
47        }
48
49        let chars = tok.span.get_content(source);
50        let eq = if self.exact {
51            chars == self.word.as_slice()
52        } else {
53            chars
54                .iter()
55                .zip(&self.word)
56                .all(|(a, b)| a.eq_ignore_ascii_case(b))
57        };
58
59        if eq { Some(1) } else { None }
60    }
61}
62
63#[cfg(test)]
64mod tests {
65    use crate::{Document, Span, patterns::DocPattern};
66
67    use super::Word;
68
69    #[test]
70    fn fruit() {
71        let doc = Document::new_markdown_default_curated("I ate a banana and an apple today.");
72
73        assert_eq!(
74            Word::new("banana").find_all_matches_in_doc(&doc),
75            vec![Span::new(6, 7)]
76        );
77        assert_eq!(
78            Word::new_exact("banana").find_all_matches_in_doc(&doc),
79            vec![Span::new(6, 7)]
80        );
81    }
82
83    #[test]
84    fn fruit_whack_capitalization() {
85        let doc = Document::new_markdown_default_curated("I Ate A bAnaNa And aN apPlE today.");
86
87        assert_eq!(
88            Word::new("banana").find_all_matches_in_doc(&doc),
89            vec![Span::new(6, 7)]
90        );
91        assert_eq!(
92            Word::new_exact("banana").find_all_matches_in_doc(&doc),
93            vec![]
94        );
95    }
96}