harper_core/patterns/
word.rs

1use super::SingleTokenPattern;
2
3use crate::{CharString, Token};
4
5/// Matches a predefined word.
6#[derive(Clone)]
7pub struct Word {
8    /// The word to match.
9    word: CharString,
10    /// Determines whether the match is case-sensitive.
11    case_sensitive: 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            case_sensitive: false,
20        }
21    }
22
23    /// Matches the provided word, ignoring case.
24    pub fn from_chars(word: &[char]) -> Self {
25        Self {
26            word: word.iter().copied().collect(),
27            case_sensitive: false,
28        }
29    }
30
31    /// Matches the provided word, ignoring case.
32    pub fn from_char_string(word: CharString) -> Self {
33        Self {
34            word,
35            case_sensitive: false,
36        }
37    }
38
39    /// Matches the provided word, case-sensitive.
40    pub fn new_exact(word: &'static str) -> Self {
41        Self {
42            word: word.chars().collect(),
43            case_sensitive: true,
44        }
45    }
46}
47
48impl SingleTokenPattern for Word {
49    fn matches_token(&self, token: &Token, source: &[char]) -> bool {
50        if !token.kind.is_word() {
51            return false;
52        }
53        if token.span.len() != self.word.len() {
54            return false;
55        }
56
57        let chars = token.span.get_content(source);
58        if self.case_sensitive {
59            chars == self.word.as_slice()
60        } else {
61            chars
62                .iter()
63                .zip(&self.word)
64                .all(|(a, b)| a.eq_ignore_ascii_case(b))
65        }
66    }
67}
68
69#[cfg(test)]
70mod tests {
71    use crate::{Document, Span, patterns::DocPattern};
72
73    use super::Word;
74
75    #[test]
76    fn fruit() {
77        let doc = Document::new_markdown_default_curated("I ate a banana and an apple today.");
78
79        assert_eq!(
80            Word::new("banana").find_all_matches_in_doc(&doc),
81            vec![Span::new(6, 7)]
82        );
83        assert_eq!(
84            Word::new_exact("banana").find_all_matches_in_doc(&doc),
85            vec![Span::new(6, 7)]
86        );
87    }
88
89    #[test]
90    fn fruit_whack_capitalization() {
91        let doc = Document::new_markdown_default_curated("I Ate A bAnaNa And aN apPlE today.");
92
93        assert_eq!(
94            Word::new("banana").find_all_matches_in_doc(&doc),
95            vec![Span::new(6, 7)]
96        );
97        assert_eq!(
98            Word::new_exact("banana").find_all_matches_in_doc(&doc),
99            vec![]
100        );
101    }
102}