1use crate::util::*;
2use crate::*;
3
4use nom::{
5 branch::alt,
6 character::complete::{char, line_ending, not_line_ending, one_of},
7 combinator::{map_res, opt},
8 multi::many0,
9 sequence::{preceded, terminated, tuple},
10 IResult,
11};
12use std::num::TryFromIntError;
13
14////////////////////////////////////////////////////////////////////////
15
16fn parse_weekday(input: &str) -> IResult<&str, time::Weekday> {
17 alt((
18 str("Monday", "Mon", time::Weekday::Monday),
19 str("Tuesday", "Tue", time::Weekday::Tuesday),
20 str("Wednesday", "Wed", time::Weekday::Wednesday),
21 str("Thursday", "Thu", time::Weekday::Thursday),
22 str("Friday", "Fri", time::Weekday::Friday),
23 str("Saturday", "Sat", time::Weekday::Saturday),
24 str("Sunday", "Sun", time::Weekday::Sunday),
25 ))(input)
26}
27
28fn parse_offset(input: &str) -> IResult<&str, WeekOffset> {
29 let (input, prefix) = one_of("+-")(input)?;
30 let (input, amount) = alt((
31 bind(char('1'), WeekOffsetAmount::First),
32 bind(char('2'), WeekOffsetAmount::Second),
33 bind(char('3'), WeekOffsetAmount::Third),
34 bind(char('4'), WeekOffsetAmount::Fourth),
35 bind(char('5'), WeekOffsetAmount::Fifth),
36 ))(input)?;
37
38 Ok((
39 input,
40 WeekOffset {
41 from_start: prefix == '+',
42 amount: amount,
43 },
44 ))
45}
46
47fn parse_month_str(input: &str) -> IResult<&str, time::Month> {
48 alt((
49 str("January", "Jan", time::Month::January),
50 str("February", "Feb", time::Month::February),
51 str("March", "Mar", time::Month::March),
52 str("April", "Apr", time::Month::April),
53 str("May", "May", time::Month::May),
54 str("June", "Jun", time::Month::June),
55 str("July", "Jul", time::Month::July),
56 str("August", "Aug", time::Month::August),
57 str("September", "Sep", time::Month::September),
58 str("October", "Oct", time::Month::October),
59 str("November", "Nov", time::Month::November),
60 str("December", "Dec", time::Month::December),
61 ))(input)
62}
63
64fn parse_month_num(input: &str) -> IResult<&str, time::Month> {
65 map_res(
66 digits,
67 |n| -> Result<time::Month, time::error::ComponentRange> {
68 // XXX: If there is a u32 → u8 conversion error then use 0xff
69 // as the month value which will result in a ComponentRange error.
70 // Unfourtunately, can't create a ComponentRange directly and use .map_err().
71 let m: u8 = n.try_into().unwrap_or(0xff);
72 time::Month::try_from(m)
73 },
74 )(input)
75}
76
77fn parse_month(input: &str) -> IResult<&str, time::Month> {
78 alt((parse_month_str, parse_month_num))(input)
79}
80
81fn parse_day(input: &str) -> IResult<&str, Day> {
82 map_res(digits, |n| -> Result<Day, TryFromIntError> { n.try_into() })(input)
83}
84
85fn parse_year(input: &str) -> IResult<&str, Year> {
86 map_res(digits, |n| -> Result<Year, TryFromIntError> {
87 n.try_into()
88 })(input)
89}
90
91fn parse_reminder(input: &str) -> IResult<&str, Reminder> {
92 alt((
93 map_res(
94 tuple((parse_weekday, parse_offset)),
95 |(wday, off)| -> Result<Reminder, ()> { Ok(Reminder::SemiWeekly(wday, off)) },
96 ),
97 map_res(parse_weekday, |wday| -> Result<Reminder, ()> {
98 Ok(Reminder::Weekly(wday))
99 }),
100 map_res(
101 tuple((parse_day, ws(char('*')), opt(parse_year))),
102 |(day, _, year)| -> Result<Reminder, ()> { Ok(Reminder::Monthly(day, year)) },
103 ),
104 map_res(
105 tuple((opt(parse_day), ws(parse_month), opt(parse_year))),
106 move |(day, mon, year)| -> Result<Reminder, time::error::ComponentRange> {
107 let day = day.unwrap_or(1);
108 Ok(match year {
109 Some(y) => Reminder::Date(time::Date::from_calendar_date(y, mon, day)?),
110 None => Reminder::Yearly(day, mon),
111 })
112 },
113 ),
114 ))(input)
115}
116
117fn parse_desc(input: &str) -> IResult<&str, String> {
118 let (input, (desc, ext)) = tuple((
119 terminated(not_line_ending, line_ending),
120 many0(terminated(
121 preceded(char('\t'), not_line_ending),
122 line_ending,
123 )),
124 ))(input)?;
125
126 if ext.is_empty() {
127 Ok((input, desc.to_string()))
128 } else {
129 Ok((input, desc.to_owned() + "\n\t" + &ext.join("\n\t")))
130 }
131}
132
133fn parse_entry(input: &str) -> IResult<&str, Entry> {
134 let (input, (day, _, desc)) = tuple((parse_reminder, char('\t'), parse_desc))(input)?;
135
136 Ok((input, Entry { day, desc: desc }))
137}
138
139pub fn parse_entries(input: &str) -> IResult<&str, Vec<Entry>> {
140 many0(empty_lines(parse_entry))(input)
141}
142
143////////////////////////////////////////////////////////////////////////
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148 use nom::error::Error;
149 use nom::error::ErrorKind;
150 use nom::Err;
151 use time::macros::date;
152
153 #[test]
154 fn weekday() {
155 assert_eq!(parse_weekday("Monday"), Ok(("", time::Weekday::Monday)));
156 assert_eq!(parse_weekday("Mon"), Ok(("", time::Weekday::Monday)));
157 assert_eq!(parse_weekday("Tuesday"), Ok(("", time::Weekday::Tuesday)));
158 }
159
160 #[test]
161 fn month() {
162 assert_eq!(parse_month("April"), Ok(("", time::Month::April)));
163 assert_eq!(parse_month("04"), Ok(("", time::Month::April)));
164 assert_eq!(parse_month("4"), Ok(("", time::Month::April)));
165 assert_eq!(
166 parse_month("13"),
167 Err(Err::Error(Error::new("13", ErrorKind::MapRes)))
168 );
169 assert_eq!(
170 parse_month("2342"),
171 Err(Err::Error(Error::new("2342", ErrorKind::MapRes)))
172 );
173 }
174
175 #[test]
176 fn reminder() {
177 assert_eq!(
178 parse_reminder("25 Feb"),
179 Ok(("", Reminder::Yearly(25, time::Month::February)))
180 );
181 assert_eq!(
182 parse_reminder("Fri"),
183 Ok(("", Reminder::Weekly(time::Weekday::Friday)))
184 );
185 assert_eq!(
186 parse_reminder("Fri+2"),
187 Ok((
188 "",
189 Reminder::SemiWeekly(time::Weekday::Friday, 2i8.try_into().unwrap())
190 )),
191 );
192 assert_eq!(
193 parse_reminder("Mon-4"),
194 Ok((
195 "",
196 Reminder::SemiWeekly(time::Weekday::Monday, (-4i8).try_into().unwrap())
197 )),
198 );
199 assert_eq!(
200 parse_reminder("Jan 1990"),
201 Ok(("", Reminder::Date(date!(1990 - 01 - 01))))
202 );
203 assert_eq!(
204 parse_reminder("06 July 2020"),
205 Ok(("", Reminder::Date(date!(2020 - 07 - 06))))
206 );
207 assert_eq!(
208 parse_reminder("12 Dec 1950"),
209 Ok(("", Reminder::Date(date!(1950 - 12 - 12))))
210 );
211 assert_eq!(
212 parse_reminder("10 *"),
213 Ok(("", Reminder::Monthly(10, None)))
214 );
215 assert_eq!(
216 parse_reminder("10 * 1989"),
217 Ok(("", Reminder::Monthly(10, Some(1989))))
218 );
219 }
220
221 #[test]
222 fn desc() {
223 assert_eq!(parse_desc("foo bar\n"), Ok(("", "foo bar".to_string())));
224 assert_eq!(
225 parse_desc("foo\n\tbar\n"),
226 Ok(("", "foo\n\tbar".to_string()))
227 );
228 assert_eq!(
229 parse_desc("foo\n\tbar\n\tbaz\n"),
230 Ok(("", "foo\n\tbar\n\tbaz".to_string()))
231 );
232 }
233
234 #[test]
235 fn event() {
236 assert_eq!(
237 parse_entry("12 Mar 2015\tDo some stuff\n"),
238 Ok((
239 "",
240 Entry {
241 day: Reminder::Date(date!(2015 - 03 - 12)),
242 desc: "Do some stuff".to_string(),
243 }
244 ))
245 );
246
247 assert_eq!(
248 parse_entry("Mon\tMonday\n"),
249 Ok((
250 "",
251 Entry {
252 day: Reminder::Weekly(time::Weekday::Monday),
253 desc: "Monday".to_string(),
254 }
255 ))
256 );
257 }
258}