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 pub nag_interval: Option<Interval>,
143}
144
145#[derive(Debug)]
146pub struct Cron {
147 pub expr: String,
148}
149
150#[derive(Debug, Default)]
151pub struct Description(pub String);
152
153trait Parse {
154 fn parse(pair: Pair<'_, Rule>) -> Option<Self>
155 where
156 Self: Sized;
157}
158
159impl Parse for HoleyDate {
160 fn parse(pair: Pair<'_, Rule>) -> Option<Self> {
161 let mut holey_date = Self::default();
162 for rec in pair.into_inner() {
163 match rec.as_rule() {
164 Rule::year => {
165 holey_date.year = Some(rec.as_str().parse().ok()?);
166 }
167 Rule::month => {
168 holey_date.month = Some(rec.as_str().parse().ok()?);
169 }
170 Rule::day => {
171 holey_date.day = Some(rec.as_str().parse().ok()?);
172 }
173 _ => unreachable!(),
174 }
175 }
176 Some(holey_date)
177 }
178}
179
180impl Parse for Interval {
181 fn parse(pair: Pair<'_, Rule>) -> Option<Self> {
182 let mut interval = Self::default();
183 for rec in pair.into_inner() {
184 match rec.as_rule() {
185 Rule::interval_years => {
186 interval.years = rec.as_str().parse().ok()?;
187 }
188 Rule::interval_months => {
189 interval.months = rec.as_str().parse().ok()?;
190 }
191 Rule::interval_weeks => {
192 interval.weeks = rec.as_str().parse().ok()?;
193 }
194 Rule::interval_days => {
195 interval.days = rec.as_str().parse().ok()?;
196 }
197 Rule::interval_hours => {
198 interval.hours = rec.as_str().parse().ok()?;
199 }
200 Rule::interval_minutes => {
201 interval.minutes = rec.as_str().parse().ok()?;
202 }
203 Rule::interval_seconds => {
204 interval.seconds = rec.as_str().parse().ok()?;
205 }
206 _ => unreachable!(),
207 }
208 }
209 Some(interval)
210 }
211}
212
213impl Weekday {
214 fn next(&self) -> Self {
215 match *self {
216 Self::Monday => Self::Tuesday,
217 Self::Tuesday => Self::Wednesday,
218 Self::Wednesday => Self::Thursday,
219 Self::Thursday => Self::Friday,
220 Self::Friday => Self::Saturday,
221 Self::Saturday => Self::Sunday,
222 Self::Sunday => Self::Monday,
223 }
224 }
225}
226
227impl Parse for Weekday {
228 fn parse(pair: Pair<'_, Rule>) -> Option<Self> {
229 pair.into_inner()
230 .next()
231 .map(|weekday| match weekday.as_rule() {
232 Rule::monday => Self::Monday,
233 Rule::tuesday => Self::Tuesday,
234 Rule::wednesday => Self::Wednesday,
235 Rule::thursday => Self::Thursday,
236 Rule::friday => Self::Friday,
237 Rule::saturday => Self::Saturday,
238 Rule::sunday => Self::Sunday,
239 _ => unreachable!(),
240 })
241 }
242}
243
244impl Weekdays {
245 fn push(&mut self, weekday: Weekday) {
246 *self |= match weekday {
247 Weekday::Monday => Self::Monday,
248 Weekday::Tuesday => Self::Tuesday,
249 Weekday::Wednesday => Self::Wednesday,
250 Weekday::Thursday => Self::Thursday,
251 Weekday::Friday => Self::Friday,
252 Weekday::Saturday => Self::Saturday,
253 Weekday::Sunday => Self::Sunday,
254 };
255 }
256}
257impl Parse for Weekdays {
258 fn parse(pair: Pair<'_, Rule>) -> Option<Self> {
259 let mut weekdays = Self::none();
260 let mut weekday_range = pair.into_inner();
261 let mut weekday_from = weekday_range.next().and_then(Weekday::parse)?;
262 let weekday_to = weekday_range
263 .next()
264 .and_then(Weekday::parse)
265 .unwrap_or(weekday_from);
266 while weekday_from != weekday_to {
267 weekdays.push(weekday_from);
268 weekday_from = weekday_from.next();
269 }
270 weekdays.push(weekday_from);
271 Some(weekdays)
272 }
273}
274
275impl Parse for DateRange {
276 fn parse(pair: Pair<'_, Rule>) -> Option<Self> {
277 let mut date_range = Self::default();
278 for rec in pair.into_inner() {
279 match rec.as_rule() {
280 Rule::date_from => {
281 date_range.from = HoleyDate::parse(rec)?;
282 }
283 Rule::date_until => {
284 date_range.until = Some(HoleyDate::parse(rec)?);
285 }
286 Rule::date_interval => {
287 date_range.date_divisor =
288 DateDivisor::Interval(DateInterval::parse(rec)?);
289 }
290 Rule::weekdays_range => {
291 let weekdays = match date_range.date_divisor {
292 DateDivisor::Weekdays(ref mut w) => w,
293 _ => {
294 date_range.date_divisor =
295 DateDivisor::Weekdays(Weekdays::none());
296 match date_range.date_divisor {
297 DateDivisor::Weekdays(ref mut w) => w,
298 _ => unreachable!(),
299 }
300 }
301 };
302 *weekdays |= Weekdays::parse(rec)?;
303 }
304 _ => unreachable!(),
305 }
306 }
307 Some(date_range)
308 }
309}
310
311impl Parse for Time {
312 fn parse(pair: Pair<'_, Rule>) -> Option<Self> {
313 let mut time = Self::default();
314 for time_component in pair.into_inner() {
315 match time_component.as_rule() {
316 Rule::hour => {
317 time.hour = time_component.as_str().parse().ok()?;
318 }
319 Rule::minute => {
320 time.minute = time_component.as_str().parse().ok()?;
321 }
322 Rule::second => {
323 time.second = time_component.as_str().parse().ok()?;
324 }
325 _ => unreachable!(),
326 }
327 }
328 Some(time)
329 }
330}
331
332impl Parse for TimeInterval {
333 fn parse(pair: Pair<'_, Rule>) -> Option<Self> {
334 let mut time_interval = Self::default();
335 for rec in pair.into_inner() {
336 match rec.as_rule() {
337 Rule::interval_hours => {
338 time_interval.hours = rec.as_str().parse().ok()?;
339 }
340 Rule::interval_minutes => {
341 time_interval.minutes = rec.as_str().parse().ok()?;
342 }
343 Rule::interval_seconds => {
344 time_interval.seconds = rec.as_str().parse().ok()?;
345 }
346 _ => unreachable!(),
347 }
348 }
349 Some(time_interval)
350 }
351}
352
353impl Parse for DateInterval {
354 fn parse(pair: Pair<'_, Rule>) -> Option<Self> {
355 let mut date_interval = Self::default();
356 for rec in pair.into_inner() {
357 match rec.as_rule() {
358 Rule::interval_years => {
359 date_interval.years = rec.as_str().parse().ok()?;
360 }
361 Rule::interval_months => {
362 date_interval.months = rec.as_str().parse().ok()?;
363 }
364 Rule::interval_weeks => {
365 date_interval.weeks = rec.as_str().parse().ok()?;
366 }
367 Rule::interval_days => {
368 date_interval.days = rec.as_str().parse().ok()?;
369 }
370 _ => unreachable!(),
371 }
372 }
373 Some(date_interval)
374 }
375}
376
377impl Parse for TimeRange {
378 fn parse(pair: Pair<'_, Rule>) -> Option<Self> {
379 let mut time_range = Self::default();
380 for rec in pair.into_inner() {
381 match rec.as_rule() {
382 Rule::time_from => {
383 time_range.from = Some(Time::parse(rec)?);
384 }
385 Rule::time_until => {
386 time_range.until = Some(Time::parse(rec)?);
387 }
388 Rule::time_interval => {
389 time_range.interval = TimeInterval::parse(rec)?;
390 }
391 _ => unreachable!(),
392 }
393 }
394 Some(time_range)
395 }
396}
397
398impl Default for Recurrence {
399 fn default() -> Self {
400 Self {
403 dates_patterns: nonempty![DatePattern::Point(HoleyDate::default())],
404 time_patterns: vec![],
405 }
406 }
407}
408
409impl Parse for Recurrence {
410 fn parse(pair: Pair<'_, Rule>) -> Option<Self> {
411 let mut recurrence = Self::default();
412 for rec in pair.into_inner() {
413 match rec.as_rule() {
414 Rule::dates_point => {
415 recurrence
416 .dates_patterns
417 .push(DatePattern::Point(HoleyDate::parse(rec)?));
418 }
419 Rule::dates_range => {
420 recurrence
421 .dates_patterns
422 .push(DatePattern::Range(DateRange::parse(rec)?));
423 }
424 Rule::time_point => {
425 recurrence
426 .time_patterns
427 .push(TimePattern::Point(Time::parse(rec)?));
428 }
429 Rule::time_range => {
430 recurrence
431 .time_patterns
432 .push(TimePattern::Range(TimeRange::parse(rec)?));
433 }
434 _ => unreachable!(),
435 }
436 }
437 if recurrence.dates_patterns.len() > 1 {
438 recurrence.dates_patterns =
439 NonEmpty::from_vec(recurrence.dates_patterns.tail).unwrap();
440 }
441 Some(recurrence)
442 }
443}
444
445impl Parse for Countdown {
446 fn parse(pair: Pair<'_, Rule>) -> Option<Self> {
447 let mut countdown = Self::default();
448 for rec in pair.into_inner() {
449 match rec.as_rule() {
450 Rule::interval => {
451 countdown.durations.push(Interval::parse(rec)?);
452 }
453 _ => unreachable!(),
454 }
455 }
456 Some(countdown)
457 }
458}
459
460impl Parse for Cron {
461 fn parse(pair: Pair<'_, Rule>) -> Option<Self> {
462 for rec in pair.into_inner() {
463 if rec.as_rule() == Rule::cron_expr {
464 return Some(Self {
465 expr: rec.as_str().to_string(),
466 });
467 }
468 }
469 None
470 }
471}
472
473impl Parse for Description {
474 fn parse(pair: Pair<'_, Rule>) -> Option<Self> {
475 Some(Self(pair.as_str().to_string()))
476 }
477}
478
479impl Parse for Reminder {
480 fn parse(pair: Pair<'_, Rule>) -> Option<Self> {
481 let mut reminder = Self::default();
482 for rec in pair.into_inner() {
483 match rec.as_rule() {
484 Rule::description => {
485 reminder.description = Some(Description::parse(rec)?);
486 }
487 Rule::recurrence => {
488 reminder.pattern = Some(ReminderPattern::Recurrence(
489 Recurrence::parse(rec)?,
490 ));
491 }
492 Rule::countdown => {
493 reminder.pattern = Some(ReminderPattern::Countdown(
494 Countdown::parse(rec)?,
495 ));
496 }
497 Rule::cron => {
498 reminder.pattern =
499 Some(ReminderPattern::Cron(Cron::parse(rec)?));
500 }
501 Rule::nag_suffix => {
502 let nag_interval = rec.into_inner().find_map(|inner| {
503 if inner.as_rule() == Rule::interval {
504 Interval::parse(inner)
505 } else {
506 None
507 }
508 })?;
509 reminder.nag_interval = Some(nag_interval);
510 }
511 Rule::EOI => {}
512 _ => unreachable!(),
513 }
514 }
515 Some(reminder)
516 }
517}
518
519pub fn parse_reminder(s: &str) -> Option<Reminder> {
520 Reminder::parse(
521 ReminderParser::parse(Rule::reminder, s)
522 .map_err(|err| {
523 log::debug!("{err}");
524 })
525 .ok()?
526 .next()?,
527 )
528}