1use crate::util::*;2use crate::*;34use 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;1314////////////////////////////////////////////////////////////////////////1516fn 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}2728fn 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)?;3738 Ok((39 input,40 WeekOffset {41 from_start: prefix == '+',42 amount: amount,43 },44 ))45}4647fn 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}6364fn 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 0xff69 // 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}7677fn parse_month(input: &str) -> IResult<&str, time::Month> {78 alt((parse_month_str, parse_month_num))(input)79}8081fn parse_day(input: &str) -> IResult<&str, Day> {82 map_res(digits, |n| -> Result<Day, TryFromIntError> { n.try_into() })(input)83}8485fn parse_year(input: &str) -> IResult<&str, Year> {86 map_res(digits, |n| -> Result<Year, TryFromIntError> {87 n.try_into()88 })(input)89}9091fn 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}116117fn 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)?;125126 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}132133fn parse_entry(input: &str) -> IResult<&str, Entry> {134 let (input, (day, _, desc)) = tuple((parse_reminder, char('\t'), parse_desc))(input)?;135136 Ok((input, Entry { day, desc: desc }))137}138139pub fn parse_entries(input: &str) -> IResult<&str, Vec<Entry>> {140 many0(empty_lines(parse_entry))(input)141}142143////////////////////////////////////////////////////////////////////////144145#[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;152153 #[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 }159160 #[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 }174175 #[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 }220221 #[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 }233234 #[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 );246247 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}