1use std::sync::Arc;
4
5use crate::{Analyzer, BoxedTokenizer, TokenFilter, Tokenizer};
6
7pub struct StandardAnalyzer {
9 tokenizer: BoxedTokenizer,
10 filters: Vec<Box<dyn TokenFilter>>,
11}
12
13impl StandardAnalyzer {
14 pub fn with_tokenizer(tokenizer: impl Tokenizer + 'static) -> Self {
16 Self {
17 tokenizer: Arc::new(tokenizer),
18 filters: Vec::new(),
19 }
20 }
21
22 #[must_use]
24 pub fn filter(mut self, f: impl TokenFilter + 'static) -> Self {
25 self.filters.push(Box::new(f));
26 self
27 }
28}
29
30impl Analyzer for StandardAnalyzer {
31 fn analyze(&self, text: &str) -> Vec<String> {
32 let mut tokens = self.tokenizer.tokenize(text);
33 for f in &self.filters {
34 tokens = tokens.into_iter().filter_map(|t| f.apply(t)).collect();
35 }
36 tokens
37 }
38}
39
40#[cfg(test)]
41mod tests {
42 use super::*;
43 use crate::filter::{LowercaseFilter, MinLengthFilter};
44 use crate::tokenizer::WhitespaceTokenizer;
45
46 #[test]
47 fn pipeline_lowercases() {
48 let a = StandardAnalyzer::with_tokenizer(WhitespaceTokenizer).filter(LowercaseFilter);
49 assert_eq!(a.analyze("Hello WORLD"), vec!["hello", "world"]);
50 }
51
52 #[test]
53 fn pipeline_chains_filters() {
54 let a = StandardAnalyzer::with_tokenizer(WhitespaceTokenizer)
55 .filter(LowercaseFilter)
56 .filter(MinLengthFilter(4));
57 assert_eq!(a.analyze("hi hello world"), vec!["hello", "world"]);
58 }
59
60 #[test]
61 fn empty_input() {
62 let a = StandardAnalyzer::with_tokenizer(WhitespaceTokenizer);
63 assert!(a.analyze("").is_empty());
64 }
65}