harper_core/expr/
duration_expr.rs1use 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 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 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}