1
use crate::common::{OffsetWeekday, RecurFreq, Weekday};
2
use crate::parser::types::{DateOrDateTime, DateTime, RecurRulePart};
3
use crate::parser::{prop_value_date, prop_value_time};
4
use crate::parser::{Error, InnerError};
5
use nom::branch::alt;
6
use nom::bytes::complete::{take_while1, take_while_m_n};
7
use nom::bytes::streaming::tag;
8
use nom::character::streaming::char;
9
use nom::combinator::{map_res, opt};
10
use nom::error::ParseError;
11
use nom::multi::separated_list1;
12
use nom::{AsChar, IResult, Parser};
13

            
14
216
pub fn prop_value_recur<'a, E>(input: &'a [u8]) -> IResult<&'a [u8], Vec<RecurRulePart>, E>
15
216
where
16
216
    E: ParseError<&'a [u8]>
17
216
        + nom::error::FromExternalError<&'a [u8], nom::Err<E>>
18
216
        + From<Error<'a>>,
19
216
{
20
216
    separated_list1(char(';'), recur_rule_part).parse(input)
21
216
}
22

            
23
906
fn recur_rule_part<'a, E>(input: &'a [u8]) -> IResult<&'a [u8], RecurRulePart, E>
24
906
where
25
906
    E: ParseError<&'a [u8]>
26
906
        + nom::error::FromExternalError<&'a [u8], nom::Err<E>>
27
906
        + From<Error<'a>>,
28
906
{
29
906
    let (input, (name, _)) = (take_while1(AsChar::is_alpha), char('=')).parse(input)?;
30

            
31
896
    match std::str::from_utf8(name).map_err(|e| {
32
        nom::Err::Error(
33
            Error::new(
34
                input,
35
                InnerError::EncodingError("Recur part name".to_string(), e),
36
            )
37
            .into(),
38
        )
39
896
    })? {
40
896
        "FREQ" => recur_freq.map(RecurRulePart::Freq).parse(input),
41
684
        "UNTIL" => end_date.map(RecurRulePart::Until).parse(input),
42
620
        "COUNT" => read_num.map(RecurRulePart::Count).parse(input),
43
576
        "INTERVAL" => read_num.map(RecurRulePart::Interval).parse(input),
44
532
        "BYSECOND" => recur_by_time_list
45
42
            .map(RecurRulePart::BySecList)
46
42
            .parse(input),
47
490
        "BYMINUTE" => recur_by_time_list.map(RecurRulePart::ByMinute).parse(input),
48
446
        "BYHOUR" => recur_by_time_list.map(RecurRulePart::ByHour).parse(input),
49
402
        "BYDAY" => recur_by_weekday_list.map(RecurRulePart::ByDay).parse(input),
50
294
        "BYMONTHDAY" => recur_by_month_day_list
51
42
            .map(RecurRulePart::ByMonthDay)
52
42
            .parse(input),
53
252
        "BYYEARDAY" => recur_by_year_day_list
54
46
            .map(RecurRulePart::ByYearDay)
55
46
            .parse(input),
56
206
        "BYWEEKNO" => recur_by_week_number.map(RecurRulePart::ByWeek).parse(input),
57
160
        "BYMONTH" => recur_by_month_list.map(RecurRulePart::ByMonth).parse(input),
58
92
        "BYSETPOS" => recur_by_year_day_list
59
46
            .map(RecurRulePart::BySetPos)
60
46
            .parse(input),
61
46
        "WKST" => weekday.map(RecurRulePart::WeekStart).parse(input),
62
        n => Err(nom::Err::Error(
63
            Error::new(input, InnerError::InvalidRecurPart(n.to_string())).into(),
64
        )),
65
    }
66
906
}
67

            
68
212
fn recur_freq<'a, E>(input: &'a [u8]) -> IResult<&'a [u8], RecurFreq, E>
69
212
where
70
212
    E: ParseError<&'a [u8]> + From<Error<'a>>,
71
212
{
72
212
    let (input, freq) = alt((
73
212
        tag("SECONDLY").map(|_| RecurFreq::Secondly),
74
212
        tag("MINUTELY").map(|_| RecurFreq::Minutely),
75
212
        tag("HOURLY").map(|_| RecurFreq::Hourly),
76
212
        tag("DAILY").map(|_| RecurFreq::Daily),
77
212
        tag("WEEKLY").map(|_| RecurFreq::Weekly),
78
212
        tag("MONTHLY").map(|_| RecurFreq::Monthly),
79
212
        tag("YEARLY").map(|_| RecurFreq::Yearly),
80
212
    ))
81
212
    .parse(input)?;
82

            
83
212
    Ok((input, freq))
84
212
}
85

            
86
64
fn end_date<'a, E>(input: &'a [u8]) -> IResult<&'a [u8], DateOrDateTime, E>
87
64
where
88
64
    E: ParseError<&'a [u8]> + From<Error<'a>>,
89
64
{
90
64
    let (input, (date, opt_time)) =
91
64
        (prop_value_date, opt((char('T'), prop_value_time))).parse(input)?;
92

            
93
64
    let time = opt_time.map(|(_, time)| time);
94
64

            
95
64
    Ok((
96
64
        input,
97
64
        match time {
98
60
            Some(time) => DateOrDateTime::DateTime(DateTime { date, time }),
99
4
            None => DateOrDateTime::Date(date),
100
        },
101
    ))
102
64
}
103

            
104
88
fn read_num<'a, E>(input: &'a [u8]) -> IResult<&'a [u8], u64, E>
105
88
where
106
88
    E: ParseError<&'a [u8]> + From<Error<'a>>,
107
88
{
108
88
    let (input, c) = take_while1(AsChar::is_dec_digit)(input)?;
109

            
110
88
    let v = std::str::from_utf8(c).map_err(|e| {
111
        nom::Err::Error(
112
            Error::new(input, InnerError::EncodingError("Recur num".to_string(), e)).into(),
113
        )
114
88
    })?;
115

            
116
    Ok((
117
88
        input,
118
88
        v.parse()
119
88
            .map_err(|_| nom::Err::Error(Error::new(input, InnerError::InvalidRecurNum).into()))?,
120
    ))
121
88
}
122

            
123
130
fn recur_by_time_list<'a, E>(input: &'a [u8]) -> IResult<&'a [u8], Vec<u8>, E>
124
130
where
125
130
    E: ParseError<&'a [u8]>
126
130
        + nom::error::FromExternalError<&'a [u8], nom::Err<E>>
127
130
        + From<Error<'a>>,
128
130
{
129
130
    separated_list1(
130
130
        char(','),
131
348
        map_res(take_while_m_n(1, 2, AsChar::is_dec_digit), |s| {
132
348
            std::str::from_utf8(s)
133
348
                .map_err(|e| {
134
                    nom::Err::Error(
135
                        Error::new(
136
                            input,
137
                            InnerError::EncodingError("Recur time list".to_string(), e),
138
                        )
139
                        .into(),
140
                    )
141
348
                })?
142
348
                .parse()
143
348
                .map_err(|_| nom::Err::Error(Error::new(input, InnerError::InvalidRecurNum).into()))
144
348
        }),
145
130
    )
146
130
    .parse(input)
147
130
}
148

            
149
162
fn weekday<'a, E>(input: &'a [u8]) -> IResult<&'a [u8], Weekday, E>
150
162
where
151
162
    E: ParseError<&'a [u8]> + From<Error<'a>>,
152
162
{
153
162
    alt((
154
162
        tag("MO").map(|_| Weekday::Monday),
155
162
        tag("TU").map(|_| Weekday::Tuesday),
156
162
        tag("WE").map(|_| Weekday::Wednesday),
157
162
        tag("TH").map(|_| Weekday::Thursday),
158
162
        tag("FR").map(|_| Weekday::Friday),
159
162
        tag("SA").map(|_| Weekday::Saturday),
160
162
        tag("SU").map(|_| Weekday::Sunday),
161
162
    ))
162
162
    .parse(input)
163
162
}
164

            
165
108
fn recur_by_weekday_list<'a, E>(input: &'a [u8]) -> IResult<&'a [u8], Vec<OffsetWeekday>, E>
166
108
where
167
108
    E: ParseError<&'a [u8]>
168
108
        + nom::error::FromExternalError<&'a [u8], nom::Err<E>>
169
108
        + From<Error<'a>>,
170
108
{
171
108
    separated_list1(
172
108
        char(','),
173
108
        (
174
108
            opt(map_res(
175
108
                (
176
116
                    opt(alt((char('+'), char('-')))).map(|opt_sign| {
177
116
                        if let Some('-') = opt_sign {
178
16
                            -1i8
179
                        } else {
180
100
                            1
181
                        }
182
116
                    }),
183
108
                    take_while_m_n(1, 2, AsChar::is_dec_digit),
184
108
                ),
185
108
                |(sign, num)| {
186
60
                    std::str::from_utf8(num)
187
60
                        .map_err(|e| {
188
                            nom::Err::Error(
189
                                Error::new(
190
                                    input,
191
                                    InnerError::EncodingError("Recur weekday list".to_string(), e),
192
                                )
193
                                .into(),
194
                            )
195
60
                        })?
196
60
                        .parse::<i8>()
197
60
                        .map_err(|_| {
198
                            nom::Err::Error(Error::new(input, InnerError::InvalidRecurNum).into())
199
60
                        })
200
60
                        .map(|num| sign * num)
201
108
                },
202
108
            )),
203
108
            weekday,
204
108
        ),
205
108
    )
206
108
    .map(|values| {
207
108
        values
208
108
            .into_iter()
209
116
            .map(|(offset_weeks, weekday)| OffsetWeekday {
210
116
                offset_weeks,
211
116
                weekday,
212
116
            })
213
108
            .collect()
214
108
    })
215
108
    .parse(input)
216
108
}
217

            
218
42
fn recur_by_month_day_list<'a, E>(input: &'a [u8]) -> IResult<&'a [u8], Vec<i8>, E>
219
42
where
220
42
    E: ParseError<&'a [u8]>
221
42
        + nom::error::FromExternalError<&'a [u8], nom::Err<E>>
222
42
        + From<Error<'a>>,
223
42
{
224
42
    separated_list1(
225
42
        char(','),
226
42
        map_res(
227
42
            (
228
42
                opt(alt((char('+'), char('-'))))
229
116
                    .map(|sign| if let Some('-') = sign { -1i8 } else { 1 }),
230
42
                take_while_m_n(1, 2, AsChar::is_dec_digit),
231
42
            ),
232
116
            |(sign, num)| {
233
116
                std::str::from_utf8(num)
234
116
                    .map_err(|e| {
235
                        nom::Err::Error(
236
                            Error::new(
237
                                input,
238
                                InnerError::EncodingError("Recur month day list".to_string(), e),
239
                            )
240
                            .into(),
241
                        )
242
116
                    })?
243
116
                    .parse::<i8>()
244
116
                    .map_err(|_| {
245
                        nom::Err::Error(Error::new(input, InnerError::InvalidRecurNum).into())
246
116
                    })
247
116
                    .map(|num| sign * num)
248
116
            },
249
42
        ),
250
42
    )
251
42
    .parse(input)
252
42
}
253

            
254
92
fn recur_by_year_day_list<'a, E>(input: &'a [u8]) -> IResult<&'a [u8], Vec<i16>, E>
255
92
where
256
92
    E: ParseError<&'a [u8]>
257
92
        + nom::error::FromExternalError<&'a [u8], nom::Err<E>>
258
92
        + From<Error<'a>>,
259
92
{
260
92
    separated_list1(
261
92
        char(','),
262
92
        map_res(
263
92
            (
264
92
                opt(alt((char('+'), char('-'))))
265
250
                    .map(|sign| if let Some('-') = sign { -1i16 } else { 1 }),
266
92
                take_while_m_n(1, 3, AsChar::is_dec_digit),
267
92
            ),
268
250
            |(sign, num)| {
269
250
                std::str::from_utf8(num)
270
250
                    .map_err(|e| {
271
                        nom::Err::Error(
272
                            Error::new(
273
                                input,
274
                                InnerError::EncodingError("Recur year day list".to_string(), e),
275
                            )
276
                            .into(),
277
                        )
278
250
                    })?
279
250
                    .parse::<i16>()
280
250
                    .map_err(|_| {
281
                        nom::Err::Error(Error::new(input, InnerError::InvalidRecurNum).into())
282
250
                    })
283
250
                    .map(|num| sign * num)
284
250
            },
285
92
        ),
286
92
    )
287
92
    .parse(input)
288
92
}
289

            
290
46
fn recur_by_week_number<'a, E>(input: &'a [u8]) -> IResult<&'a [u8], Vec<i8>, E>
291
46
where
292
46
    E: ParseError<&'a [u8]>
293
46
        + nom::error::FromExternalError<&'a [u8], nom::Err<E>>
294
46
        + From<Error<'a>>,
295
46
{
296
46
    separated_list1(
297
46
        char(','),
298
46
        map_res(
299
46
            (
300
46
                opt(alt((char('+'), char('-'))))
301
120
                    .map(|sign| if let Some('-') = sign { -1i8 } else { 1 }),
302
46
                take_while_m_n(1, 2, AsChar::is_dec_digit),
303
46
            ),
304
120
            |(sign, num)| {
305
120
                std::str::from_utf8(num)
306
120
                    .map_err(|e| {
307
                        nom::Err::Error(
308
                            Error::new(
309
                                input,
310
                                InnerError::EncodingError("Recur week number list".to_string(), e),
311
                            )
312
                            .into(),
313
                        )
314
120
                    })?
315
120
                    .parse::<i8>()
316
120
                    .map_err(|_| {
317
                        nom::Err::Error(Error::new(input, InnerError::InvalidRecurNum).into())
318
120
                    })
319
120
                    .map(|num| sign * num)
320
120
            },
321
46
        ),
322
46
    )
323
46
    .parse(input)
324
46
}
325

            
326
68
fn recur_by_month_list<'a, E>(input: &'a [u8]) -> IResult<&'a [u8], Vec<u8>, E>
327
68
where
328
68
    E: ParseError<&'a [u8]>
329
68
        + nom::error::FromExternalError<&'a [u8], nom::Err<E>>
330
68
        + From<Error<'a>>,
331
68
{
332
68
    separated_list1(
333
68
        char(','),
334
128
        map_res(take_while_m_n(1, 2, AsChar::is_dec_digit), |num| {
335
128
            std::str::from_utf8(num)
336
128
                .map_err(|e| {
337
                    nom::Err::Error(
338
                        Error::new(
339
                            input,
340
                            InnerError::EncodingError("Recur month list".to_string(), e),
341
                        )
342
                        .into(),
343
                    )
344
128
                })?
345
128
                .parse::<u8>()
346
128
                .map_err(|_| nom::Err::Error(Error::new(input, InnerError::InvalidRecurNum).into()))
347
128
        }),
348
68
    )
349
68
    .parse(input)
350
68
}
351

            
352
#[cfg(test)]
353
mod tests {
354
    use super::*;
355
    use crate::test_utils::check_rem;
356

            
357
    #[test]
358
2
    fn daily_rule() {
359
2
        let (rem, rule) = prop_value_recur::<Error>(b"FREQ=DAILY;COUNT=10;INTERVAL=2;").unwrap();
360
2
        check_rem(rem, 1);
361
2
        assert_eq!(
362
2
            rule,
363
2
            vec![
364
2
                RecurRulePart::Freq(RecurFreq::Daily),
365
2
                RecurRulePart::Count(10),
366
2
                RecurRulePart::Interval(2)
367
2
            ]
368
2
        );
369
2
    }
370

            
371
    #[test]
372
2
    fn monthly_rule() {
373
2
        let (rem, rule) =
374
2
            prop_value_recur::<Error>(b"FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-1;").unwrap();
375
2
        check_rem(rem, 1);
376
2
        assert_eq!(
377
2
            rule,
378
2
            vec![
379
2
                RecurRulePart::Freq(RecurFreq::Monthly),
380
2
                RecurRulePart::ByDay(vec![
381
2
                    OffsetWeekday {
382
2
                        offset_weeks: None,
383
2
                        weekday: Weekday::Monday
384
2
                    },
385
2
                    OffsetWeekday {
386
2
                        offset_weeks: None,
387
2
                        weekday: Weekday::Tuesday
388
2
                    },
389
2
                    OffsetWeekday {
390
2
                        offset_weeks: None,
391
2
                        weekday: Weekday::Wednesday
392
2
                    },
393
2
                    OffsetWeekday {
394
2
                        offset_weeks: None,
395
2
                        weekday: Weekday::Thursday
396
2
                    },
397
2
                    OffsetWeekday {
398
2
                        offset_weeks: None,
399
2
                        weekday: Weekday::Friday
400
2
                    }
401
2
                ]),
402
2
                RecurRulePart::BySetPos(vec![-1])
403
2
            ]
404
2
        );
405
2
    }
406

            
407
    #[test]
408
2
    fn yearly_rule() {
409
2
        let (rem, rule) = prop_value_recur::<Error>(
410
2
            b"FREQ=YEARLY;INTERVAL=2;BYMONTH=1;BYDAY=SU;BYHOUR=8,9;BYMINUTE=30;",
411
2
        )
412
2
        .unwrap();
413
2
        check_rem(rem, 1);
414
2
        assert_eq!(
415
2
            rule,
416
2
            vec![
417
2
                RecurRulePart::Freq(RecurFreq::Yearly),
418
2
                RecurRulePart::Interval(2),
419
2
                RecurRulePart::ByMonth(vec![1]),
420
2
                RecurRulePart::ByDay(vec![OffsetWeekday {
421
2
                    offset_weeks: None,
422
2
                    weekday: Weekday::Sunday
423
2
                }]),
424
2
                RecurRulePart::ByHour(vec![8, 9]),
425
2
                RecurRulePart::ByMinute(vec![30]),
426
2
            ]
427
2
        );
428
2
    }
429
}