chronos_parser_rs/
cron_parser.rs

1use crate::Expr;
2use crate::Expr::{AnyValueExpr, CronExpr, LastValueExpr, ListExpr, NoOp, PerExpr, RangeExpr, ValueExpr};
3use oni_comb_parser_rs::prelude::*;
4
5fn min_digit<'a>() -> Parser<'a, u8, Expr> {
6  (elm_of(b"12345") + elm_of(b"0123456789"))
7    .attempt()
8    .map(|(e1, e2)| ValueExpr((e1 - 48) * 10 + e2 - 48))
9    | (elm(b'0') * elm_of(b"0123456789")).attempt().map(|e| ValueExpr(e - 48))
10    | (elm_of(b"0123456789")).map(|e| ValueExpr(e - 48))
11}
12
13fn hour_digit<'a>() -> Parser<'a, u8, Expr> {
14  (elm(b'2') + elm_of(b"0123"))
15    .attempt()
16    .map(|(e1, e2)| ValueExpr((e1 - 48) * 10 + e2 - 48))
17    | (elm(b'1') + elm_of(b"0123456789"))
18      .attempt()
19      .map(|(e1, e2)| ValueExpr((e1 - 48) * 10 + e2 - 48))
20    | (elm(b'0') * elm_of(b"0123456789")).attempt().map(|e| ValueExpr(e - 48))
21    | elm_of(b"0123456789").map(|e| ValueExpr(e - 48))
22}
23
24fn day_digit<'a>() -> Parser<'a, u8, Expr> {
25  (elm(b'3') + elm_of(b"01"))
26    .attempt()
27    .map(|(e1, e2)| ValueExpr((e1 - 48) * 10 + e2 - 48))
28    | (elm_of(b"12") + elm_of(b"0123456789"))
29      .attempt()
30      .map(|(e1, e2)| ValueExpr((e1 - 48) * 10 + e2 - 48))
31    | (elm(b'0') * elm_of(b"123456789")).attempt().map(|e| ValueExpr(e - 48))
32    | elm_of(b"123456789").map(|e| ValueExpr(e - 48))
33}
34
35fn month_digit<'a>() -> Parser<'a, u8, Expr> {
36  (elm(b'1') + elm_of(b"012"))
37    .attempt()
38    .map(|(e1, e2)| ValueExpr((e1 - 48) * 10 + e2 - 48))
39    | (elm(b'0') * elm_of(b"123456789")).attempt().map(|e| ValueExpr(e - 48))
40    | elm_of(b"123456789").map(|e| ValueExpr(e - 48))
41}
42
43fn day_of_week_digit<'a>() -> Parser<'a, u8, Expr> {
44  seq(b"SUN").attempt().map(|_| ValueExpr(1))
45    | seq(b"MON").attempt().map(|_| ValueExpr(2))
46    | seq(b"TUE").attempt().map(|_| ValueExpr(3))
47    | seq(b"WED").attempt().map(|_| ValueExpr(4))
48    | seq(b"THU").attempt().map(|_| ValueExpr(5))
49    | seq(b"FRI").attempt().map(|_| ValueExpr(6))
50    | seq(b"SAT").attempt().map(|_| ValueExpr(7))
51    | elm(b'L').map(|_| LastValueExpr)
52}
53
54fn day_of_week_text<'a>() -> Parser<'a, u8, Expr> {
55  elm_of(b"1234567").map(|e| ValueExpr(e as u8 - 48))
56}
57
58fn asterisk<'a>() -> Parser<'a, u8, Expr> {
59  elm(b'*').map(|_| AnyValueExpr)
60}
61
62fn per(p: Parser<u8, Expr>) -> Parser<u8, Expr> {
63  elm(b'/') * p
64}
65
66fn asterisk_per(p: Parser<u8, Expr>) -> Parser<u8, Expr> {
67  (asterisk() + per(p)).map(|(d, op)| PerExpr {
68    digit: Box::from(d.clone()),
69    option: Box::from(op.clone()),
70  })
71}
72
73fn range_per(p: Parser<u8, Expr>) -> Parser<u8, Expr> {
74  per(p).opt().map(|e| match e {
75    None => NoOp,
76    Some(s) => s,
77  })
78}
79
80macro_rules! range {
81  ( $x:expr ) => {
82    ($x - elm(b'-') + $x + range_per($x)).map(|((e1, e2), e3)| RangeExpr {
83      from: Box::from(e1),
84      to: Box::from(e2),
85      per_option: Box::from(e3),
86    })
87  };
88}
89
90fn list(p: Parser<u8, Expr>) -> Parser<u8, Expr> {
91  p.of_many0_sep(elm(b',')).map(|e| match e {
92    e if e.len() == 1 => e.get(0).unwrap().clone(),
93    e => ListExpr(e),
94  })
95}
96
97macro_rules! digit_instruction {
98  ( $x:expr ) => {
99    asterisk_per($x).attempt() | asterisk().attempt() | list(range!($x).attempt() | $x)
100  };
101}
102
103fn instruction<'a>() -> Parser<'a, u8, Expr> {
104  (digit_instruction!(min_digit()) - elm(b' ') + digit_instruction!(hour_digit()) - elm(b' ')
105    + digit_instruction!(day_digit())
106    - elm(b' ')
107    + digit_instruction!(month_digit())
108    - elm(b' ')
109    + digit_instruction!(day_of_week_text().attempt() | day_of_week_digit()))
110  .map(|((((mins, hours), days), months), day_of_weeks)| CronExpr {
111    mins: Box::from(mins),
112    hours: Box::from(hours),
113    days: Box::from(days),
114    months: Box::from(months),
115    day_of_weeks: Box::from(day_of_weeks),
116  })
117}
118
119pub struct CronParser;
120
121impl CronParser {
122  pub fn parse(source: &str) -> ParseResult<u8, Expr> {
123    (instruction() - end()).parse(source.as_bytes())
124  }
125}
126
127#[cfg(test)]
128mod tests {
129  use super::*;
130
131  #[test]
132  fn test_instruction() {
133    let result = (instruction() - end()).parse(b"* * * * *").to_result().unwrap();
134    assert_eq!(
135      result,
136      CronExpr {
137        mins: Box::from(AnyValueExpr),
138        hours: Box::from(AnyValueExpr),
139        days: Box::from(AnyValueExpr),
140        months: Box::from(AnyValueExpr),
141        day_of_weeks: Box::from(AnyValueExpr)
142      }
143    );
144    let result = (instruction() - end()).parse(b"1 1 1 1 1").to_result().unwrap();
145    assert_eq!(
146      result,
147      CronExpr {
148        mins: Box::from(ValueExpr(1)),
149        hours: Box::from(ValueExpr(1)),
150        days: Box::from(ValueExpr(1)),
151        months: Box::from(ValueExpr(1)),
152        day_of_weeks: Box::from(ValueExpr(1))
153      }
154    );
155  }
156
157  #[test]
158  fn test_digit_instruction() {
159    let result = (digit_instruction!(min_digit()) - end())
160      .parse(b"*")
161      .to_result()
162      .unwrap();
163    assert_eq!(result, AnyValueExpr);
164    let result = (digit_instruction!(min_digit()) - end())
165      .parse(b"*/2")
166      .to_result()
167      .unwrap();
168    assert_eq!(
169      result,
170      PerExpr {
171        digit: Box::from(AnyValueExpr),
172        option: Box::from(ValueExpr(2))
173      }
174    );
175    let result = (digit_instruction!(min_digit()) - end())
176      .parse(b"1-10/2")
177      .to_result()
178      .unwrap();
179    assert_eq!(
180      result,
181      RangeExpr {
182        from: Box::from(ValueExpr(1)),
183        to: Box::from(ValueExpr(10)),
184        per_option: Box::from(ValueExpr(2))
185      }
186    );
187    let result = (digit_instruction!(min_digit()) - end())
188      .parse(b"1,2,3")
189      .to_result()
190      .unwrap();
191    assert_eq!(result, ListExpr(vec![ValueExpr(1), ValueExpr(2), ValueExpr(3)]));
192    let result = (digit_instruction!(min_digit()) - end())
193      .parse(b"1")
194      .to_result()
195      .unwrap();
196    assert_eq!(result, ValueExpr(1));
197  }
198
199  #[test]
200  fn test_list() {
201    let s = (0..=59).map(|v| v.to_string()).collect::<Vec<_>>().join(",");
202    let result = (list(min_digit()) - end()).parse(s.as_bytes()).to_result().unwrap();
203    let values = (0..=59).map(|v| ValueExpr(v)).collect::<Vec<_>>();
204    assert_eq!(result, ListExpr(values));
205  }
206
207  #[test]
208  fn test_range() {
209    for n2 in 1..=59 {
210      let option = n2 / 2;
211      let n1 = n2 - 1;
212      let s: &str = &format!("{:<02}-{:<02}/{:<02}", n1, n2, option);
213      println!("{}", s);
214      let result = (range!(min_digit()) - end()).parse(s.as_bytes()).to_result().unwrap();
215      assert_eq!(
216        result,
217        RangeExpr {
218          from: Box::from(ValueExpr(n1)),
219          to: Box::from(ValueExpr(n2)),
220          per_option: Box::from(ValueExpr(option)),
221        }
222      );
223    }
224  }
225
226  #[test]
227  fn test_asterisk_per() {
228    for n in 0..59 {
229      let s: &str = &format!("*/{:<02}", n);
230      let result = (asterisk_per(min_digit()) - end())
231        .parse(s.as_bytes())
232        .to_result()
233        .unwrap();
234      assert_eq!(
235        result,
236        PerExpr {
237          digit: Box::from(AnyValueExpr),
238          option: Box::from(ValueExpr(n)),
239        }
240      );
241    }
242  }
243
244  #[test]
245  fn test_per() {
246    let result = (per(min_digit()) - end()).parse(b"/2").to_result().unwrap();
247    assert_eq!(result, ValueExpr(2));
248  }
249
250  #[test]
251  fn test_min_digit() {
252    for n in 0..59 {
253      let s: &str = &format!("{:<02}", n);
254      let result = (min_digit() - end()).parse(s.as_bytes()).to_result().unwrap();
255      assert_eq!(result, ValueExpr(n));
256    }
257    let result = (min_digit() - end()).parse(b"60").to_result();
258    assert_eq!(result.is_err(), true);
259  }
260
261  #[test]
262  fn test_hour_digit() {
263    for n in 0..=23 {
264      if n < 10 {
265        let s: &str = &n.to_string();
266        let result: Expr = (hour_digit() - end()).parse(s.as_bytes()).to_result().unwrap();
267        assert_eq!(result, ValueExpr(n));
268      }
269      let s: &str = &format!("{:<02}", n);
270      let result: Expr = (hour_digit() - end()).parse(s.as_bytes()).to_result().unwrap();
271      assert_eq!(result, ValueExpr(n));
272    }
273    let result = (hour_digit() - end()).parse(b"24").to_result();
274    assert_eq!(result.is_err(), true);
275  }
276
277  #[test]
278  fn test_day_digit() {
279    for n in 1..=31 {
280      if n < 10 {
281        let s: &str = &n.to_string();
282        let result: Expr = (day_digit() - end()).parse(s.as_bytes()).to_result().unwrap();
283        assert_eq!(result, ValueExpr(n));
284      }
285      let s: &str = &format!("{:<02}", n);
286      let result: Expr = (day_digit() - end()).parse(s.as_bytes()).to_result().unwrap();
287      assert_eq!(result, ValueExpr(n));
288    }
289    let result = (day_digit() - end()).parse(b"32").to_result();
290    assert_eq!(result.is_err(), true);
291  }
292
293  #[test]
294  fn test_month_digit() {
295    for n in 1..=12 {
296      if n < 10 {
297        let s: &str = &n.to_string();
298        let result: Expr = (month_digit() - end()).parse(s.as_bytes()).to_result().unwrap();
299        assert_eq!(result, ValueExpr(n));
300      }
301      let s: &str = &format!("{:<02}", n);
302      let result: Expr = (month_digit() - end()).parse(s.as_bytes()).to_result().unwrap();
303      assert_eq!(result, ValueExpr(n));
304    }
305    let result = (month_digit() - end()).parse(b"13").to_result();
306    assert_eq!(result.is_err(), true);
307  }
308}