clockwork_cron/
parsing.rs

1use nom::branch::alt;
2use nom::bytes::complete::tag;
3use nom::character::complete::{alpha1, digit1, multispace0};
4use nom::combinator::{all_consuming, eof, map, map_res, opt};
5use nom::multi::separated_list1;
6use nom::sequence::{delimited, separated_pair, terminated, tuple};
7use nom::IResult;
8
9use std::convert::TryFrom;
10use std::str::{self, FromStr};
11
12use crate::error::{Error, ErrorKind};
13use crate::schedule::{ScheduleFields, Schedule};
14use crate::specifier::*;
15use crate::time_unit::*;
16use crate::ordinal::*;
17
18impl FromStr for Schedule {
19    type Err = Error;
20    fn from_str(expression: &str) -> Result<Self, Self::Err> {
21        match schedule(expression) {
22            Ok((_, schedule_fields)) => {
23                Ok(Schedule::new(String::from(expression), schedule_fields))
24            } // Extract from nom tuple
25            Err(_) => Err(ErrorKind::Expression("Invalid cron expression.".to_owned()).into()), //TODO: Details
26        }
27    }
28}
29impl TryFrom<&str> for Schedule {
30    type Error = Error;
31
32    fn try_from(value: &str) -> Result<Self, Self::Error> {
33        Self::from_str(value)
34    }
35}
36
37#[derive(Debug, PartialEq)]
38pub struct Field {
39    pub specifiers: Vec<RootSpecifier>, // TODO: expose iterator?
40}
41
42trait FromField
43where
44    Self: Sized,
45{
46    //TODO: Replace with std::convert::TryFrom when stable
47    fn from_field(field: Field) -> Result<Self, Error>;
48}
49
50impl<T> FromField for T
51where
52    T: TimeUnitField,
53{
54    fn from_field(field: Field) -> Result<T, Error> {
55        if field.specifiers.len() == 1 && 
56            field.specifiers.get(0).unwrap() == &RootSpecifier::from(Specifier::All) 
57            { return Ok(T::all()); }
58        let mut ordinals = OrdinalSet::new(); 
59        for specifier in field.specifiers {
60            let specifier_ordinals: OrdinalSet = T::ordinals_from_root_specifier(&specifier)?;
61            for ordinal in specifier_ordinals {
62                ordinals.insert(T::validate_ordinal(ordinal)?);
63            }
64        }
65        Ok(T::from_ordinal_set(ordinals))
66    }
67}
68
69fn ordinal(i: &str) -> IResult<&str, u32> {
70    map_res(delimited(multispace0, digit1, multispace0), u32::from_str)(i)
71}
72
73fn name(i: &str) -> IResult<&str, String> {
74    map(
75        delimited(multispace0, alpha1, multispace0),
76        ToOwned::to_owned,
77    )(i)
78}
79
80fn point(i: &str) -> IResult<&str, Specifier> {
81    let (i, o) = ordinal(i)?;
82    Ok((i, Specifier::Point(o)))
83}
84
85fn named_point(i: &str) -> IResult<&str, RootSpecifier> {
86    let (i, n) = name(i)?;
87    Ok((i, RootSpecifier::NamedPoint(n)))
88}
89
90fn period(i: &str) -> IResult<&str, RootSpecifier> {
91    map(
92        separated_pair(specifier, tag("/"), ordinal),
93        |(start, step)| RootSpecifier::Period(start, step),
94    )(i)
95}
96
97fn period_with_any(i: &str) -> IResult<&str, RootSpecifier> {
98    map(
99        separated_pair(specifier_with_any, tag("/"), ordinal),
100        |(start, step)| RootSpecifier::Period(start, step),
101    )(i)
102}
103
104fn range(i: &str) -> IResult<&str, Specifier> {
105    map(
106        separated_pair(ordinal, tag("-"), ordinal),
107        |(start, end)| Specifier::Range(start, end),
108    )(i)
109}
110
111fn named_range(i: &str) -> IResult<&str, Specifier> {
112    map(separated_pair(name, tag("-"), name), |(start, end)| {
113        Specifier::NamedRange(start, end)
114    })(i)
115}
116
117fn all(i: &str) -> IResult<&str, Specifier> {
118    let (i, _) = tag("*")(i)?;
119    Ok((i, Specifier::All))
120}
121
122fn any(i: &str) -> IResult<&str, Specifier> {
123    let (i, _) = tag("?")(i)?;
124    Ok((i, Specifier::All))
125}
126
127fn specifier(i: &str) -> IResult<&str, Specifier> {
128    alt((all, range, point, named_range))(i)
129}
130
131fn specifier_with_any(i: &str) -> IResult<&str, Specifier> {
132    alt((any, specifier))(i)
133}
134
135fn root_specifier(i: &str) -> IResult<&str, RootSpecifier> {
136    alt((period, map(specifier, RootSpecifier::from), named_point))(i)
137}
138
139fn root_specifier_with_any(i: &str) -> IResult<&str, RootSpecifier> {
140    alt((
141        period_with_any,
142        map(specifier_with_any, RootSpecifier::from),
143        named_point,
144    ))(i)
145}
146
147fn root_specifier_list(i: &str) -> IResult<&str, Vec<RootSpecifier>> {
148    let list = separated_list1(tag(","), root_specifier);
149    let single_item = map(root_specifier, |spec| vec![spec]);
150    delimited(multispace0, alt((list, single_item)), multispace0)(i)
151}
152
153fn root_specifier_list_with_any(i: &str) -> IResult<&str, Vec<RootSpecifier>> {
154    let list = separated_list1(tag(","), root_specifier_with_any);
155    let single_item = map(root_specifier_with_any, |spec| vec![spec]);
156    delimited(multispace0, alt((list, single_item)), multispace0)(i)
157}
158
159fn field(i: &str) -> IResult<&str, Field> {
160    let (i, specifiers) = root_specifier_list(i)?;
161    Ok((i, Field { specifiers }))
162}
163
164fn field_with_any(i: &str) -> IResult<&str, Field> {
165    let (i, specifiers) = root_specifier_list_with_any(i)?;
166    Ok((i, Field { specifiers }))
167}
168
169fn shorthand_yearly(i: &str) -> IResult<&str, ScheduleFields> {
170    let (i, _) = tag("@yearly")(i)?;
171    let fields = ScheduleFields::new(
172        Seconds::from_ordinal(0),
173        Minutes::from_ordinal(0),
174        Hours::from_ordinal(0),
175        DaysOfMonth::from_ordinal(1),
176        Months::from_ordinal(1),
177        DaysOfWeek::all(),
178        Years::all(),
179    );
180    Ok((i, fields))
181}
182
183fn shorthand_monthly(i: &str) -> IResult<&str, ScheduleFields> {
184    let (i, _) = tag("@monthly")(i)?;
185    let fields = ScheduleFields::new(
186        Seconds::from_ordinal(0),
187        Minutes::from_ordinal(0),
188        Hours::from_ordinal(0),
189        DaysOfMonth::from_ordinal(1),
190        Months::all(),
191        DaysOfWeek::all(),
192        Years::all(),
193    );
194    Ok((i, fields))
195}
196
197fn shorthand_weekly(i: &str) -> IResult<&str, ScheduleFields> {
198    let (i, _) = tag("@weekly")(i)?;
199    let fields = ScheduleFields::new(
200        Seconds::from_ordinal(0),
201        Minutes::from_ordinal(0),
202        Hours::from_ordinal(0),
203        DaysOfMonth::all(),
204        Months::all(),
205        DaysOfWeek::from_ordinal(1),
206        Years::all(),
207    );
208    Ok((i, fields))
209}
210
211fn shorthand_daily(i: &str) -> IResult<&str, ScheduleFields> {
212    let (i, _) = tag("@daily")(i)?;
213    let fields = ScheduleFields::new(
214        Seconds::from_ordinal(0),
215        Minutes::from_ordinal(0),
216        Hours::from_ordinal(0),
217        DaysOfMonth::all(),
218        Months::all(),
219        DaysOfWeek::all(),
220        Years::all(),
221    );
222    Ok((i, fields))
223}
224
225fn shorthand_hourly(i: &str) -> IResult<&str, ScheduleFields> {
226    let (i, _) = tag("@hourly")(i)?;
227    let fields = ScheduleFields::new(
228        Seconds::from_ordinal(0),
229        Minutes::from_ordinal(0),
230        Hours::all(),
231        DaysOfMonth::all(),
232        Months::all(),
233        DaysOfWeek::all(),
234        Years::all(),
235    );
236    Ok((i, fields))
237}
238
239fn shorthand(i: &str) -> IResult<&str, ScheduleFields> {
240    let keywords = alt((
241        shorthand_yearly,
242        shorthand_monthly,
243        shorthand_weekly,
244        shorthand_daily,
245        shorthand_hourly,
246    ));
247    delimited(multispace0, keywords, multispace0)(i)
248}
249
250fn longhand(i: &str) -> IResult<&str, ScheduleFields> {
251    let seconds = map_res(field, Seconds::from_field);
252    let minutes = map_res(field, Minutes::from_field);
253    let hours = map_res(field, Hours::from_field);
254    let days_of_month = map_res(field_with_any, DaysOfMonth::from_field);
255    let months = map_res(field, Months::from_field);
256    let days_of_week = map_res(field_with_any, DaysOfWeek::from_field);
257    let years = opt(map_res(field, Years::from_field));
258    let fields = tuple((seconds, minutes, hours, days_of_month, months, days_of_week, years));
259
260    map(
261        terminated(fields, eof),
262        |(seconds, minutes, hours, days_of_month, months, days_of_week, years)| {
263            let years = years.unwrap_or_else(Years::all);
264            ScheduleFields::new(
265                seconds,
266                minutes,
267                hours,
268                days_of_month,
269                months,
270                days_of_week,
271                years,
272            )
273        },
274    )(i)
275}
276
277fn schedule(i: &str) -> IResult<&str, ScheduleFields> {
278    all_consuming(alt((shorthand, longhand)))(i)
279}
280
281#[cfg(test)]
282mod test {
283    use super::*;
284
285    #[test]
286    fn test_nom_valid_number() {
287        let expression = "1997";
288        point(expression).unwrap();
289    }
290
291    #[test]
292    fn test_nom_invalid_point() {
293        let expression = "a";
294        assert!(point(expression).is_err());
295    }
296
297    #[test]
298    fn test_nom_valid_named_point() {
299        let expression = "WED";
300        named_point(expression).unwrap();
301    }
302
303    #[test]
304    fn test_nom_invalid_named_point() {
305        let expression = "8";
306        assert!(named_point(expression).is_err());
307    }
308
309    #[test]
310    fn test_nom_valid_period() {
311        let expression = "1/2";
312        period(expression).unwrap();
313    }
314
315    #[test]
316    fn test_nom_invalid_period() {
317        let expression = "Wed/4";
318        assert!(period(expression).is_err());
319    }
320
321    #[test]
322    fn test_nom_valid_number_list() {
323        let expression = "1,2";
324        field(expression).unwrap();
325        field_with_any(expression).unwrap();
326    }
327
328    #[test]
329    fn test_nom_invalid_number_list() {
330        let expression = ",1,2";
331        assert!(field(expression).is_err());
332        assert!(field_with_any(expression).is_err());
333    }
334
335    #[test]
336    fn test_nom_field_with_any_valid_any() {
337        let expression = "?";
338        field_with_any(expression).unwrap();
339    }
340
341    #[test]
342    fn test_nom_field_invalid_any() {
343        let expression = "?";
344        assert!(field(expression).is_err());
345    }
346
347    #[test]
348    fn test_nom_valid_range_field() {
349        let expression = "1-4";
350        range(expression).unwrap();
351    }
352
353    #[test]
354    fn test_nom_valid_period_all() {
355        let expression = "*/2";
356        period(expression).unwrap();
357    }
358
359    #[test]
360    fn test_nom_valid_period_range() {
361        let expression = "10-20/2";
362        period(expression).unwrap();
363    }
364
365    #[test]
366    fn test_nom_valid_period_named_range() {
367        let expression = "Mon-Thurs/2";
368        period(expression).unwrap();
369
370        let expression = "February-November/2";
371        period(expression).unwrap();
372    }
373
374    #[test]
375    fn test_nom_valid_period_point() {
376        let expression = "10/2";
377        period(expression).unwrap();
378    }
379
380    #[test]
381    fn test_nom_invalid_period_any() {
382        let expression = "?/2";
383        assert!(period(expression).is_err());
384    }
385
386    #[test]
387    fn test_nom_invalid_period_named_point() {
388        let expression = "Tues/2";
389        assert!(period(expression).is_err());
390
391        let expression = "February/2";
392        assert!(period(expression).is_err());
393    }
394
395    #[test]
396    fn test_nom_invalid_period_specifier_range() {
397        let expression = "10-12/*";
398        assert!(period(expression).is_err());
399    }
400
401    #[test]
402    fn test_nom_valid_period_with_any_all() {
403        let expression = "*/2";
404        period_with_any(expression).unwrap();
405    }
406
407    #[test]
408    fn test_nom_valid_period_with_any_range() {
409        let expression = "10-20/2";
410        period_with_any(expression).unwrap();
411    }
412
413    #[test]
414    fn test_nom_valid_period_with_any_named_range() {
415        let expression = "Mon-Thurs/2";
416        period_with_any(expression).unwrap();
417
418        let expression = "February-November/2";
419        period_with_any(expression).unwrap();
420    }
421
422    #[test]
423    fn test_nom_valid_period_with_any_point() {
424        let expression = "10/2";
425        period_with_any(expression).unwrap();
426    }
427
428    #[test]
429    fn test_nom_valid_period_with_any_any() {
430        let expression = "?/2";
431        period_with_any(expression).unwrap();
432    }
433
434    #[test]
435    fn test_nom_invalid_period_with_any_named_point() {
436        let expression = "Tues/2";
437        assert!(period_with_any(expression).is_err());
438
439        let expression = "February/2";
440        assert!(period_with_any(expression).is_err());
441    }
442
443    #[test]
444    fn test_nom_invalid_period_with_any_specifier_range() {
445        let expression = "10-12/*";
446        assert!(period_with_any(expression).is_err());
447    }
448
449    #[test]
450    fn test_nom_invalid_range_field() {
451        let expression = "-4";
452        assert!(range(expression).is_err());
453    }
454
455    #[test]
456    fn test_nom_valid_named_range_field() {
457        let expression = "TUES-THURS";
458        named_range(expression).unwrap();
459    }
460
461    #[test]
462    fn test_nom_invalid_named_range_field() {
463        let expression = "3-THURS";
464        assert!(named_range(expression).is_err());
465    }
466
467    #[test]
468    fn test_nom_valid_schedule() {
469        let expression = "* * * * * *";
470        schedule(expression).unwrap();
471    }
472
473    #[test]
474    fn test_nom_invalid_schedule() {
475        let expression = "* * * *";
476        assert!(schedule(expression).is_err());
477    }
478
479    #[test]
480    fn test_nom_valid_seconds_list() {
481        let expression = "0,20,40 * * * * *";
482        schedule(expression).unwrap();
483    }
484
485    #[test]
486    fn test_nom_valid_seconds_range() {
487        let expression = "0-40 * * * * *";
488        schedule(expression).unwrap();
489    }
490
491    #[test]
492    fn test_nom_valid_seconds_mix() {
493        let expression = "0-5,58 * * * * *";
494        schedule(expression).unwrap();
495    }
496
497    #[test]
498    fn test_nom_invalid_seconds_range() {
499        let expression = "0-65 * * * * *";
500        assert!(schedule(expression).is_err());
501    }
502
503    #[test]
504    fn test_nom_invalid_seconds_list() {
505        let expression = "103,12 * * * * *";
506        assert!(schedule(expression).is_err());
507    }
508
509    #[test]
510    fn test_nom_invalid_seconds_mix() {
511        let expression = "0-5,102 * * * * *";
512        assert!(schedule(expression).is_err());
513    }
514
515    #[test]
516    fn test_nom_valid_days_of_week_list() {
517        let expression = "* * * * * MON,WED,FRI";
518        schedule(expression).unwrap();
519    }
520
521    #[test]
522    fn test_nom_invalid_days_of_week_list() {
523        let expression = "* * * * * MON,TURTLE";
524        assert!(schedule(expression).is_err());
525    }
526
527    #[test]
528    fn test_nom_valid_days_of_week_range() {
529        let expression = "* * * * * MON-FRI";
530        schedule(expression).unwrap();
531    }
532
533    #[test]
534    fn test_nom_invalid_days_of_week_range() {
535        let expression = "* * * * * BEAR-OWL";
536        assert!(schedule(expression).is_err());
537    }
538
539    #[test]
540    fn test_nom_invalid_period_with_range_specifier() {
541        let expression = "10-12/10-12 * * * * ?";
542        assert!(schedule(expression).is_err());
543    }
544
545    #[test]
546    fn test_nom_valid_days_of_month_any() {
547        let expression = "* * * ? * *";
548        schedule(expression).unwrap();
549    }
550
551    #[test]
552    fn test_nom_valid_days_of_week_any() {
553        let expression = "* * * * * ?";
554        schedule(expression).unwrap();
555    }
556
557    #[test]
558    fn test_nom_valid_days_of_month_any_days_of_week_specific() {
559        let expression = "* * * ? * Mon,Thu";
560        schedule(expression).unwrap();
561    }
562
563    #[test]
564    fn test_nom_valid_days_of_week_any_days_of_month_specific() {
565        let expression = "* * * 1,2 * ?";
566        schedule(expression).unwrap();
567    }
568
569    #[test]
570    fn test_nom_valid_dom_and_dow_any() {
571        let expression = "* * * ? * ?";
572        schedule(expression).unwrap();
573    }
574
575    #[test]
576    fn test_nom_invalid_other_fields_any() {
577        let expression = "? * * * * *";
578        assert!(schedule(expression).is_err());
579
580        let expression = "* ? * * * *";
581        assert!(schedule(expression).is_err());
582
583        let expression = "* * ? * * *";
584        assert!(schedule(expression).is_err());
585
586        let expression = "* * * * ? *";
587        assert!(schedule(expression).is_err());
588    }
589
590    #[test]
591    fn test_nom_invalid_trailing_characters() {
592        let expression = "* * * * * *foo *";
593        assert!(schedule(expression).is_err());
594
595        let expression = "* * * * * * * foo";
596        assert!(schedule(expression).is_err());
597    }
598
599    /// Issue #86
600    #[test]
601    fn shorthand_must_match_whole_input() {
602        let expression = "@dailyBla";
603        assert!(schedule(expression).is_err());
604        let expression = " @dailyBla ";
605        assert!(schedule(expression).is_err());
606    }
607}