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 } Err(_) => Err(ErrorKind::Expression("Invalid cron expression.".to_owned()).into()), }
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>, }
41
42trait FromField
43where
44 Self: Sized,
45{
46 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 #[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}