1use bitmask_enum::bitmask;
2use nonempty::{nonempty, NonEmpty};
3
4use pest::{iterators::Pair, Parser};
5use pest_derive::Parser;
6
7extern crate alloc;
8
9#[derive(Parser)]
10#[grammar = "grammars/reminder.pest"]
11struct ReminderParser;
12
13#[derive(Debug, Default)]
14pub struct HoleyDate {
15 pub year: Option<i32>,
16 pub month: Option<u32>,
17 pub day: Option<u32>,
18}
19
20#[derive(Debug, Default)]
21pub struct Interval {
22 pub years: i32,
23 pub months: u32,
24 pub weeks: u32,
25 pub days: u32,
26 pub hours: u32,
27 pub minutes: u32,
28 pub seconds: u32,
29}
30
31#[derive(Debug, Clone, Copy, PartialEq)]
32enum Weekday {
33 Monday,
34 Tuesday,
35 Wednesday,
36 Thursday,
37 Friday,
38 Saturday,
39 Sunday,
40}
41
42#[bitmask(u8)]
43pub enum Weekdays {
44 Monday,
45 Tuesday,
46 Wednesday,
47 Thursday,
48 Friday,
49 Saturday,
50 Sunday,
51}
52
53#[derive(Debug)]
54pub enum DateDivisor {
55 Weekdays(Weekdays),
56 Interval(DateInterval),
57}
58
59#[derive(Debug)]
60pub struct DateRange {
61 pub from: HoleyDate,
62 pub until: Option<HoleyDate>,
63 pub date_divisor: DateDivisor,
64}
65
66impl Default for DateRange {
67 fn default() -> Self {
68 Self {
69 date_divisor: DateDivisor::Interval(DateInterval {
70 days: 1,
71 ..Default::default()
72 }),
73 from: Default::default(),
74 until: None,
75 }
76 }
77}
78
79#[derive(Debug)]
80pub enum DatePattern {
81 Point(HoleyDate),
82 Range(DateRange),
83}
84
85#[derive(Debug, Default)]
86pub struct Time {
87 pub hour: u32,
88 pub minute: u32,
89 pub second: u32,
90}
91
92#[derive(Debug, Default)]
93pub struct TimeInterval {
94 pub hours: u32,
95 pub minutes: u32,
96 pub seconds: u32,
97}
98
99#[derive(Debug, Default)]
100pub struct DateInterval {
101 pub years: i32,
102 pub months: u32,
103 pub weeks: u32,
104 pub days: u32,
105}
106
107#[derive(Debug, Default)]
108pub struct TimeRange {
109 pub from: Option<Time>,
110 pub until: Option<Time>,
111 pub interval: TimeInterval,
112}
113
114#[derive(Debug)]
115pub enum TimePattern {
116 Point(Time),
117 Range(TimeRange),
118}
119
120#[derive(Debug)]
121pub struct Recurrence {
122 pub dates_patterns: NonEmpty<DatePattern>,
123 pub time_patterns: Vec<TimePattern>,
124}
125
126#[derive(Debug, Default)]
127pub struct Countdown {
128 pub durations: Vec<Interval>,
129}
130
131#[derive(Debug)]
132pub enum ReminderPattern {
133 Recurrence(Recurrence),
134 Countdown(Countdown),
135 Cron(Cron),
136}
137
138#[derive(Debug, Default)]
139pub struct Reminder {
140 pub description: Option<Description>,
141 pub pattern: Option<ReminderPattern>,
142}
143
144#[derive(Debug)]
145pub struct Cron {
146 pub expr: String,
147}
148
149#[derive(Debug, Default)]
150pub struct Description(pub String);
151
152trait Parse {
153 fn parse(pair: Pair<'_, Rule>) -> Option<Self>
154 where
155 Self: Sized;
156}
157
158impl Parse for HoleyDate {
159 fn parse(pair: Pair<'_, Rule>) -> Option<Self> {
160 let mut holey_date = Self::default();
161 for rec in pair.into_inner() {
162 match rec.as_rule() {
163 Rule::year => {
164 holey_date.year = Some(rec.as_str().parse().ok()?);
165 }
166 Rule::month => {
167 holey_date.month = Some(rec.as_str().parse().ok()?);
168 }
169 Rule::day => {
170 holey_date.day = Some(rec.as_str().parse().ok()?);
171 }
172 _ => unreachable!(),
173 }
174 }
175 Some(holey_date)
176 }
177}
178
179impl Parse for Interval {
180 fn parse(pair: Pair<'_, Rule>) -> Option<Self> {
181 let mut interval = Self::default();
182 for rec in pair.into_inner() {
183 match rec.as_rule() {
184 Rule::interval_years => {
185 interval.years = rec.as_str().parse().ok()?;
186 }
187 Rule::interval_months => {
188 interval.months = rec.as_str().parse().ok()?;
189 }
190 Rule::interval_weeks => {
191 interval.weeks = rec.as_str().parse().ok()?;
192 }
193 Rule::interval_days => {
194 interval.days = rec.as_str().parse().ok()?;
195 }
196 Rule::interval_hours => {
197 interval.hours = rec.as_str().parse().ok()?;
198 }
199 Rule::interval_minutes => {
200 interval.minutes = rec.as_str().parse().ok()?;
201 }
202 Rule::interval_seconds => {
203 interval.seconds = rec.as_str().parse().ok()?;
204 }
205 _ => unreachable!(),
206 }
207 }
208 Some(interval)
209 }
210}
211
212impl Weekday {
213 fn next(&self) -> Self {
214 match *self {
215 Self::Monday => Self::Tuesday,
216 Self::Tuesday => Self::Wednesday,
217 Self::Wednesday => Self::Thursday,
218 Self::Thursday => Self::Friday,
219 Self::Friday => Self::Saturday,
220 Self::Saturday => Self::Sunday,
221 Self::Sunday => Self::Monday,
222 }
223 }
224}
225
226impl Parse for Weekday {
227 fn parse(pair: Pair<'_, Rule>) -> Option<Self> {
228 pair.into_inner()
229 .next()
230 .map(|weekday| match weekday.as_rule() {
231 Rule::monday => Self::Monday,
232 Rule::tuesday => Self::Tuesday,
233 Rule::wednesday => Self::Wednesday,
234 Rule::thursday => Self::Thursday,
235 Rule::friday => Self::Friday,
236 Rule::saturday => Self::Saturday,
237 Rule::sunday => Self::Sunday,
238 _ => unreachable!(),
239 })
240 }
241}
242
243impl Weekdays {
244 fn push(&mut self, weekday: Weekday) {
245 *self |= match weekday {
246 Weekday::Monday => Self::Monday,
247 Weekday::Tuesday => Self::Tuesday,
248 Weekday::Wednesday => Self::Wednesday,
249 Weekday::Thursday => Self::Thursday,
250 Weekday::Friday => Self::Friday,
251 Weekday::Saturday => Self::Saturday,
252 Weekday::Sunday => Self::Sunday,
253 };
254 }
255}
256impl Parse for Weekdays {
257 fn parse(pair: Pair<'_, Rule>) -> Option<Self> {
258 let mut weekdays = Self::none();
259 let mut weekday_range = pair.into_inner();
260 let mut weekday_from = weekday_range.next().and_then(Weekday::parse)?;
261 let weekday_to = weekday_range
262 .next()
263 .and_then(Weekday::parse)
264 .unwrap_or(weekday_from);
265 while weekday_from != weekday_to {
266 weekdays.push(weekday_from);
267 weekday_from = weekday_from.next();
268 }
269 weekdays.push(weekday_from);
270 Some(weekdays)
271 }
272}
273
274impl Parse for DateRange {
275 fn parse(pair: Pair<'_, Rule>) -> Option<Self> {
276 let mut date_range = Self::default();
277 for rec in pair.into_inner() {
278 match rec.as_rule() {
279 Rule::date_from => {
280 date_range.from = HoleyDate::parse(rec)?;
281 }
282 Rule::date_until => {
283 date_range.until = Some(HoleyDate::parse(rec)?);
284 }
285 Rule::date_interval => {
286 date_range.date_divisor =
287 DateDivisor::Interval(DateInterval::parse(rec)?);
288 }
289 Rule::weekdays_range => {
290 let weekdays = match date_range.date_divisor {
291 DateDivisor::Weekdays(ref mut w) => w,
292 _ => {
293 date_range.date_divisor =
294 DateDivisor::Weekdays(Weekdays::none());
295 match date_range.date_divisor {
296 DateDivisor::Weekdays(ref mut w) => w,
297 _ => unreachable!(),
298 }
299 }
300 };
301 *weekdays |= Weekdays::parse(rec)?;
302 }
303 _ => unreachable!(),
304 }
305 }
306 Some(date_range)
307 }
308}
309
310impl Parse for Time {
311 fn parse(pair: Pair<'_, Rule>) -> Option<Self> {
312 let mut time = Self::default();
313 for time_component in pair.into_inner() {
314 match time_component.as_rule() {
315 Rule::hour => {
316 time.hour = time_component.as_str().parse().ok()?;
317 }
318 Rule::minute => {
319 time.minute = time_component.as_str().parse().ok()?;
320 }
321 Rule::second => {
322 time.second = time_component.as_str().parse().ok()?;
323 }
324 _ => unreachable!(),
325 }
326 }
327 Some(time)
328 }
329}
330
331impl Parse for TimeInterval {
332 fn parse(pair: Pair<'_, Rule>) -> Option<Self> {
333 let mut time_interval = Self::default();
334 for rec in pair.into_inner() {
335 match rec.as_rule() {
336 Rule::interval_hours => {
337 time_interval.hours = rec.as_str().parse().ok()?;
338 }
339 Rule::interval_minutes => {
340 time_interval.minutes = rec.as_str().parse().ok()?;
341 }
342 Rule::interval_seconds => {
343 time_interval.seconds = rec.as_str().parse().ok()?;
344 }
345 _ => unreachable!(),
346 }
347 }
348 Some(time_interval)
349 }
350}
351
352impl Parse for DateInterval {
353 fn parse(pair: Pair<'_, Rule>) -> Option<Self> {
354 let mut date_interval = Self::default();
355 for rec in pair.into_inner() {
356 match rec.as_rule() {
357 Rule::interval_years => {
358 date_interval.years = rec.as_str().parse().ok()?;
359 }
360 Rule::interval_months => {
361 date_interval.months = rec.as_str().parse().ok()?;
362 }
363 Rule::interval_weeks => {
364 date_interval.weeks = rec.as_str().parse().ok()?;
365 }
366 Rule::interval_days => {
367 date_interval.days = rec.as_str().parse().ok()?;
368 }
369 _ => unreachable!(),
370 }
371 }
372 Some(date_interval)
373 }
374}
375
376impl Parse for TimeRange {
377 fn parse(pair: Pair<'_, Rule>) -> Option<Self> {
378 let mut time_range = Self::default();
379 for rec in pair.into_inner() {
380 match rec.as_rule() {
381 Rule::time_from => {
382 time_range.from = Some(Time::parse(rec)?);
383 }
384 Rule::time_until => {
385 time_range.until = Some(Time::parse(rec)?);
386 }
387 Rule::time_interval => {
388 time_range.interval = TimeInterval::parse(rec)?;
389 }
390 _ => unreachable!(),
391 }
392 }
393 Some(time_range)
394 }
395}
396
397impl Default for Recurrence {
398 fn default() -> Self {
399 Self {
402 dates_patterns: nonempty![DatePattern::Point(HoleyDate::default())],
403 time_patterns: vec![],
404 }
405 }
406}
407
408impl Parse for Recurrence {
409 fn parse(pair: Pair<'_, Rule>) -> Option<Self> {
410 let mut recurrence = Self::default();
411 for rec in pair.into_inner() {
412 match rec.as_rule() {
413 Rule::dates_point => {
414 recurrence
415 .dates_patterns
416 .push(DatePattern::Point(HoleyDate::parse(rec)?));
417 }
418 Rule::dates_range => {
419 recurrence
420 .dates_patterns
421 .push(DatePattern::Range(DateRange::parse(rec)?));
422 }
423 Rule::time_point => {
424 recurrence
425 .time_patterns
426 .push(TimePattern::Point(Time::parse(rec)?));
427 }
428 Rule::time_range => {
429 recurrence
430 .time_patterns
431 .push(TimePattern::Range(TimeRange::parse(rec)?));
432 }
433 _ => unreachable!(),
434 }
435 }
436 if recurrence.dates_patterns.len() > 1 {
437 recurrence.dates_patterns =
438 NonEmpty::from_vec(recurrence.dates_patterns.tail).unwrap();
439 }
440 Some(recurrence)
441 }
442}
443
444impl Parse for Countdown {
445 fn parse(pair: Pair<'_, Rule>) -> Option<Self> {
446 let mut countdown = Self::default();
447 for rec in pair.into_inner() {
448 match rec.as_rule() {
449 Rule::interval => {
450 countdown.durations.push(Interval::parse(rec)?);
451 }
452 _ => unreachable!(),
453 }
454 }
455 Some(countdown)
456 }
457}
458
459impl Parse for Cron {
460 fn parse(pair: Pair<'_, Rule>) -> Option<Self> {
461 for rec in pair.into_inner() {
462 if rec.as_rule() == Rule::cron_expr {
463 return Some(Self {
464 expr: rec.as_str().to_string(),
465 });
466 }
467 }
468 None
469 }
470}
471
472impl Parse for Description {
473 fn parse(pair: Pair<'_, Rule>) -> Option<Self> {
474 Some(Self(pair.as_str().to_string()))
475 }
476}
477
478impl Parse for Reminder {
479 fn parse(pair: Pair<'_, Rule>) -> Option<Self> {
480 let mut reminder = Self::default();
481 for rec in pair.into_inner() {
482 match rec.as_rule() {
483 Rule::description => {
484 reminder.description = Some(Description::parse(rec)?);
485 }
486 Rule::recurrence => {
487 reminder.pattern = Some(ReminderPattern::Recurrence(
488 Recurrence::parse(rec)?,
489 ));
490 }
491 Rule::countdown => {
492 reminder.pattern = Some(ReminderPattern::Countdown(
493 Countdown::parse(rec)?,
494 ));
495 }
496 Rule::cron => {
497 reminder.pattern =
498 Some(ReminderPattern::Cron(Cron::parse(rec)?));
499 }
500 Rule::EOI => {}
501 _ => unreachable!(),
502 }
503 }
504 Some(reminder)
505 }
506}
507
508pub fn parse_reminder(s: &str) -> Option<Reminder> {
509 Reminder::parse(
510 ReminderParser::parse(Rule::reminder, s)
511 .map_err(|err| {
512 log::debug!("{}", err);
513 })
514 .ok()?
515 .next()?,
516 )
517}