Skip to main content

harper_core/expr/
duration_expr.rs

1use crate::{
2    Span, Token,
3    patterns::{IndefiniteArticle, WordSet},
4};
5
6use super::{Expr, SequenceExpr, SpelledNumberExpr};
7
8#[derive(Default)]
9pub struct DurationExpr;
10
11impl Expr for DurationExpr {
12    fn run(&self, cursor: usize, tokens: &[Token], source: &[char]) -> Option<Span<Token>> {
13        if tokens.is_empty() {
14            return None;
15        }
16
17        let units = WordSet::new(&[
18            "second", "seconds", "minute", "minutes", "hour", "hours", "day", "days", "week",
19            "weeks", "month", "months", "year", "years",
20        ]);
21
22        // standard "a couple of" and colloquial "a couple"
23        let a_couple_of = SequenceExpr::default()
24            .t_aco("a")
25            .t_ws()
26            .t_aco("couple")
27            .then_optional(SequenceExpr::default().t_ws().t_aco("of"));
28
29        // positive "a few" and negative "few"
30        let a_few = SequenceExpr::optional(SequenceExpr::aco("a").t_ws()).t_aco("few");
31
32        let expr = SequenceExpr::longest_of([
33            Box::new(SpelledNumberExpr) as Box<dyn Expr>,
34            Box::new(SequenceExpr::number()),
35            Box::new(IndefiniteArticle::default()),
36            Box::new(a_couple_of),
37            Box::new(a_few),
38            Box::new(SequenceExpr::default().then_quantifier()),
39        ])
40        .then_whitespace()
41        .then(units);
42
43        expr.run(cursor, tokens, source)
44    }
45}
46
47#[cfg(test)]
48pub mod tests {
49    use super::DurationExpr;
50    use crate::Document;
51    use crate::expr::ExprExt;
52    use crate::linting::tests::SpanVecExt;
53
54    #[test]
55    fn detect_10_days() {
56        let doc = Document::new_markdown_default_curated("Is 10 days a long time?");
57        let matches = DurationExpr.iter_matches_in_doc(&doc).collect::<Vec<_>>();
58        assert_eq!(matches.to_strings(&doc), vec!["10 days"]);
59    }
60
61    #[test]
62    fn detect_ten_days() {
63        let doc = Document::new_markdown_default_curated("I think ten days is a long time.");
64        let matches = DurationExpr.iter_matches_in_doc(&doc).collect::<Vec<_>>();
65        assert_eq!(matches.to_strings(&doc), vec!["ten days"]);
66    }
67
68    #[test]
69    fn detect_a_few_months() {
70        let doc = Document::new_markdown_default_curated(
71            "I am struggling since a few months with the rebuild of an old FORTRAN program.",
72        );
73        let matches = DurationExpr.iter_matches_in_doc(&doc).collect::<Vec<_>>();
74        assert_eq!(matches.to_strings(&doc), vec!["a few months"]);
75    }
76}