harper_core/expr/
duration_expr.rs

1use crate::patterns::{IndefiniteArticle, WordSet};
2use crate::{Span, Token};
3
4use super::{Expr, SequenceExpr, SpelledNumberExpr};
5
6#[derive(Default)]
7pub struct DurationExpr;
8
9impl Expr for DurationExpr {
10    fn run(&self, cursor: usize, tokens: &[Token], source: &[char]) -> Option<Span<Token>> {
11        if tokens.is_empty() {
12            return None;
13        }
14
15        let units = WordSet::new(&[
16            "minute", "minutes", "hour", "hours", "day", "days", "week", "weeks", "month",
17            "months", "year", "years",
18        ]);
19
20        let expr = SequenceExpr::default()
21            .then_longest_of(vec![
22                Box::new(SpelledNumberExpr),
23                Box::new(SequenceExpr::default().then_number()),
24                Box::new(IndefiniteArticle::default()),
25            ])
26            .then_whitespace()
27            .then(units);
28
29        expr.run(cursor, tokens, source)
30    }
31}
32
33#[cfg(test)]
34pub mod tests {
35    use super::DurationExpr;
36    use crate::Document;
37    use crate::expr::ExprExt;
38    use crate::linting::tests::SpanVecExt;
39
40    #[test]
41    fn detect_10_days() {
42        let doc = Document::new_markdown_default_curated("Is 10 days a long time?");
43        let matches = DurationExpr.iter_matches_in_doc(&doc).collect::<Vec<_>>();
44        assert_eq!(matches.to_strings(&doc), vec!["10 days"]);
45    }
46
47    #[test]
48    fn detect_ten_days() {
49        let doc = Document::new_markdown_default_curated("I think ten days is a long time.");
50        let matches = DurationExpr.iter_matches_in_doc(&doc).collect::<Vec<_>>();
51        assert_eq!(matches.to_strings(&doc), vec!["ten days"]);
52    }
53}