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::character::{is_alphabetic, is_digit};
10
use nom::combinator::{map_res, opt};
11
use nom::error::ParseError;
12
use nom::multi::separated_list1;
13
use nom::sequence::tuple;
14
use nom::{IResult, Parser};
15

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
347
#[cfg(test)]
348
mod tests {
349
    use super::*;
350
    use crate::test_utils::check_rem;
351

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

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

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