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