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