|
| 1 | +use std::str::FromStr; |
| 2 | + |
| 3 | +use chrono::{Month, Weekday}; |
| 4 | +use winnow::{ |
| 5 | + ascii::{digit1, space1}, |
| 6 | + combinator::{alt, opt}, |
| 7 | + error::ContextError, |
| 8 | + PResult, Parser, |
| 9 | +}; |
| 10 | + |
| 11 | +use crate::{HumanDateExpr, HumanDateKeyword, Ordinal}; |
| 12 | + |
| 13 | +pub struct HumanDateParserEnglishParser; |
| 14 | + |
| 15 | +impl HumanDateParserEnglishParser { |
| 16 | + pub fn new() -> Self { |
| 17 | + HumanDateParserEnglishParser {} |
| 18 | + } |
| 19 | +} |
| 20 | + |
| 21 | +impl Parser<&str, HumanDateExpr, ContextError> for HumanDateParserEnglishParser { |
| 22 | + fn parse_next(&mut self, input: &mut &str) -> PResult<HumanDateExpr> { |
| 23 | + let mut parser = alt(( |
| 24 | + keyword.map(HumanDateExpr::Keyword), |
| 25 | + in_n_days.map(HumanDateExpr::InNDays), |
| 26 | + ordinal_weekday_of_month.map(|(ordinal, weekday, month)| { |
| 27 | + HumanDateExpr::OrdinalWeekdayOfMonth(ordinal, weekday, month) |
| 28 | + }), |
| 29 | + this_week_weekday.map(HumanDateExpr::ThisWeekWeekday), |
| 30 | + next_week_weekday.map(HumanDateExpr::NextWeekWeekday), |
| 31 | + )); |
| 32 | + parser.parse_next(input) |
| 33 | + } |
| 34 | +} |
| 35 | + |
| 36 | +fn keyword(input: &mut &str) -> PResult<HumanDateKeyword> { |
| 37 | + alt(( |
| 38 | + "today".value(HumanDateKeyword::Today), |
| 39 | + "tomorrow".value(HumanDateKeyword::Tomorrow), |
| 40 | + "day after tomorrow".value(HumanDateKeyword::AfterTomorrow), |
| 41 | + )) |
| 42 | + .parse_next(input) |
| 43 | +} |
| 44 | + |
| 45 | +fn in_n_days(input: &mut &str) -> PResult<u64> { |
| 46 | + let (_, n, _) = ( |
| 47 | + (alt(("in", "after")), space1), |
| 48 | + number, |
| 49 | + (space1, "day", opt('s')), |
| 50 | + ) |
| 51 | + .parse_next(input)?; |
| 52 | + Ok(n) |
| 53 | +} |
| 54 | + |
| 55 | +fn this_week_weekday(input: &mut &str) -> PResult<Weekday> { |
| 56 | + let (_, weekday) = (opt((this, space1)), weekday).parse_next(input)?; |
| 57 | + Ok(weekday) |
| 58 | +} |
| 59 | + |
| 60 | +fn next_week_weekday(input: &mut &str) -> PResult<Weekday> { |
| 61 | + let (_, _, weekday) = (next, space1, weekday).parse_next(input)?; |
| 62 | + Ok(weekday) |
| 63 | +} |
| 64 | + |
| 65 | +fn ordinal_weekday_of_month(input: &mut &str) -> PResult<(Ordinal, Weekday, Month)> { |
| 66 | + let (ordinal, _, weekday, _, _, _, month) = |
| 67 | + (ordinal, space1, weekday, space1, "of", space1, month).parse_next(input)?; |
| 68 | + Ok((ordinal, weekday, month)) |
| 69 | +} |
| 70 | + |
| 71 | +fn this(input: &mut &str) -> PResult<()> { |
| 72 | + alt(("this", "the current")) |
| 73 | + .void() |
| 74 | + .parse_next(input) |
| 75 | +} |
| 76 | + |
| 77 | +fn next(input: &mut &str) -> PResult<()> { |
| 78 | + alt(("next", "the next", "the following")) |
| 79 | + .void() |
| 80 | + .parse_next(input) |
| 81 | +} |
| 82 | + |
| 83 | +fn ordinal(input: &mut &str) -> PResult<Ordinal> { |
| 84 | + alt(( |
| 85 | + "first".value(Ordinal::First), |
| 86 | + "second".value(Ordinal::Second), |
| 87 | + "third".value(Ordinal::Third), |
| 88 | + "fourth".value(Ordinal::Fourth), |
| 89 | + "fifth".value(Ordinal::Fifth), |
| 90 | + )) |
| 91 | + .parse_next(input) |
| 92 | +} |
| 93 | + |
| 94 | +fn number(input: &mut &str) -> PResult<u64> { |
| 95 | + alt(( |
| 96 | + digit1.try_map(FromStr::from_str), |
| 97 | + "twenty".value(20), |
| 98 | + "nineteen".value(19), |
| 99 | + "eighteen".value(18), |
| 100 | + "seventeen".value(17), |
| 101 | + "sixteen".value(16), |
| 102 | + "fifteen".value(15), |
| 103 | + "fourteen".value(14), |
| 104 | + "thirteen".value(13), |
| 105 | + "twelve".value(12), |
| 106 | + "eleven".value(11), |
| 107 | + "ten".value(10), |
| 108 | + "nine".value(9), |
| 109 | + "eight".value(8), |
| 110 | + "seven".value(7), |
| 111 | + "six".value(6), |
| 112 | + "five".value(5), |
| 113 | + "four".value(4), |
| 114 | + "three".value(3), |
| 115 | + "two".value(2), |
| 116 | + "one".value(1), |
| 117 | + )) |
| 118 | + .parse_next(input) |
| 119 | +} |
| 120 | + |
| 121 | +fn weekday(input: &mut &str) -> PResult<Weekday> { |
| 122 | + alt(( |
| 123 | + alt(("monday", "mon")).value(Weekday::Mon), |
| 124 | + alt(("tuesday", "tue")).value(Weekday::Tue), |
| 125 | + alt(("wednesday", "wed")).value(Weekday::Wed), |
| 126 | + alt(("thursday", "thu")).value(Weekday::Thu), |
| 127 | + alt(("friday", "fri")).value(Weekday::Fri), |
| 128 | + alt(("saturday", "sat")).value(Weekday::Sat), |
| 129 | + alt(("sunday", "sun")).value(Weekday::Sun), |
| 130 | + )) |
| 131 | + .parse_next(input) |
| 132 | +} |
| 133 | + |
| 134 | +fn month(input: &mut &str) -> PResult<Month> { |
| 135 | + alt(( |
| 136 | + alt(("january", "jan")).value(Month::January), |
| 137 | + alt(("february", "feb")).value(Month::February), |
| 138 | + alt(("march", "mar")).value(Month::March), |
| 139 | + alt(("april", "apr")).value(Month::April), |
| 140 | + "may".value(Month::May), |
| 141 | + alt(("june", "jun")).value(Month::June), |
| 142 | + alt(("july", "jul")).value(Month::July), |
| 143 | + alt(("august", "aug")).value(Month::August), |
| 144 | + alt(("september", "sep")).value(Month::September), |
| 145 | + alt(("october", "oct")).value(Month::October), |
| 146 | + alt(("november", "nov")).value(Month::November), |
| 147 | + alt(("december", "dec")).value(Month::December), |
| 148 | + )) |
| 149 | + .parse_next(input) |
| 150 | +} |
| 151 | + |
| 152 | +#[cfg(test)] |
| 153 | +mod tests { |
| 154 | + use crate::{HumanDateExpr, HumanDateKeyword, Ordinal}; |
| 155 | + use chrono::{Month, Weekday}; |
| 156 | + use winnow::Parser; |
| 157 | + |
| 158 | + use super::{next, number, this, weekday, HumanDateParserEnglishParser}; |
| 159 | + |
| 160 | + #[test] |
| 161 | + fn test_keywords() { |
| 162 | + let mut parser = HumanDateParserEnglishParser::new(); |
| 163 | + assert_eq!( |
| 164 | + parser.parse_peek("today"), |
| 165 | + Ok(("", HumanDateExpr::Keyword(HumanDateKeyword::Today))) |
| 166 | + ); |
| 167 | + assert_eq!( |
| 168 | + parser.parse_peek("tomorrow"), |
| 169 | + Ok(("", HumanDateExpr::Keyword(HumanDateKeyword::Tomorrow))) |
| 170 | + ); |
| 171 | + assert_eq!( |
| 172 | + parser.parse_peek("day after tomorrow"), |
| 173 | + Ok(("", HumanDateExpr::Keyword(HumanDateKeyword::AfterTomorrow))) |
| 174 | + ); |
| 175 | + } |
| 176 | + |
| 177 | + #[test] |
| 178 | + fn test_in_n_days() { |
| 179 | + let mut parser = HumanDateParserEnglishParser::new(); |
| 180 | + assert_eq!( |
| 181 | + parser.parse_peek("in 2 days"), |
| 182 | + Ok(("", HumanDateExpr::InNDays(2))) |
| 183 | + ); |
| 184 | + assert_eq!( |
| 185 | + parser.parse_peek("after 2 days"), |
| 186 | + Ok(("", HumanDateExpr::InNDays(2))) |
| 187 | + ); |
| 188 | + assert_eq!( |
| 189 | + parser.parse_peek("in two days"), |
| 190 | + Ok(("", HumanDateExpr::InNDays(2))) |
| 191 | + ); |
| 192 | + assert_eq!( |
| 193 | + parser.parse_peek("after two days"), |
| 194 | + Ok(("", HumanDateExpr::InNDays(2))) |
| 195 | + ); |
| 196 | + } |
| 197 | + |
| 198 | + #[test] |
| 199 | + fn test_this_week_weekday() { |
| 200 | + let mut parser = HumanDateParserEnglishParser::new(); |
| 201 | + assert_eq!( |
| 202 | + parser.parse_peek("this monday"), |
| 203 | + Ok(("", HumanDateExpr::ThisWeekWeekday(Weekday::Mon))) |
| 204 | + ); |
| 205 | + assert_eq!( |
| 206 | + parser.parse_peek("tuesday"), |
| 207 | + Ok(("", HumanDateExpr::ThisWeekWeekday(Weekday::Tue))) |
| 208 | + ); |
| 209 | + assert_eq!( |
| 210 | + parser.parse_peek("this wednesday"), |
| 211 | + Ok(("", HumanDateExpr::ThisWeekWeekday(Weekday::Wed))) |
| 212 | + ); |
| 213 | + assert_eq!( |
| 214 | + parser.parse_peek("thursday"), |
| 215 | + Ok(("", HumanDateExpr::ThisWeekWeekday(Weekday::Thu))) |
| 216 | + ); |
| 217 | + assert_eq!( |
| 218 | + parser.parse_peek("this friday"), |
| 219 | + Ok(("", HumanDateExpr::ThisWeekWeekday(Weekday::Fri))) |
| 220 | + ); |
| 221 | + assert_eq!( |
| 222 | + parser.parse_peek("saturday"), |
| 223 | + Ok(("", HumanDateExpr::ThisWeekWeekday(Weekday::Sat))) |
| 224 | + ); |
| 225 | + assert_eq!( |
| 226 | + parser.parse_peek("this sunday"), |
| 227 | + Ok(("", HumanDateExpr::ThisWeekWeekday(Weekday::Sun))) |
| 228 | + ); |
| 229 | + } |
| 230 | + |
| 231 | + #[test] |
| 232 | + fn test_next_week_weekday() { |
| 233 | + let mut parser = HumanDateParserEnglishParser::new(); |
| 234 | + assert_eq!( |
| 235 | + parser.parse_peek("next monday"), |
| 236 | + Ok(("", HumanDateExpr::NextWeekWeekday(Weekday::Mon))) |
| 237 | + ); |
| 238 | + assert_eq!( |
| 239 | + parser.parse_peek("next tuesday"), |
| 240 | + Ok(("", HumanDateExpr::NextWeekWeekday(Weekday::Tue))) |
| 241 | + ); |
| 242 | + assert_eq!( |
| 243 | + parser.parse_peek("next wednesday"), |
| 244 | + Ok(("", HumanDateExpr::NextWeekWeekday(Weekday::Wed))) |
| 245 | + ); |
| 246 | + assert_eq!( |
| 247 | + parser.parse_peek("next thursday"), |
| 248 | + Ok(("", HumanDateExpr::NextWeekWeekday(Weekday::Thu))) |
| 249 | + ); |
| 250 | + assert_eq!( |
| 251 | + parser.parse_peek("next friday"), |
| 252 | + Ok(("", HumanDateExpr::NextWeekWeekday(Weekday::Fri))) |
| 253 | + ); |
| 254 | + assert_eq!( |
| 255 | + parser.parse_peek("next saturday"), |
| 256 | + Ok(("", HumanDateExpr::NextWeekWeekday(Weekday::Sat))) |
| 257 | + ); |
| 258 | + assert_eq!( |
| 259 | + parser.parse_peek("next sunday"), |
| 260 | + Ok(("", HumanDateExpr::NextWeekWeekday(Weekday::Sun))) |
| 261 | + ); |
| 262 | + } |
| 263 | + |
| 264 | + #[test] |
| 265 | + fn test_ordinal_weekday_of_month() { |
| 266 | + let mut parser = HumanDateParserEnglishParser::new(); |
| 267 | + assert_eq!( |
| 268 | + parser.parse_peek("first sun of september"), |
| 269 | + Ok(( |
| 270 | + "", |
| 271 | + HumanDateExpr::OrdinalWeekdayOfMonth( |
| 272 | + Ordinal::First, |
| 273 | + Weekday::Sun, |
| 274 | + Month::September |
| 275 | + ) |
| 276 | + )) |
| 277 | + ); |
| 278 | + assert_eq!( |
| 279 | + parser.parse_peek("second thursday of september"), |
| 280 | + Ok(( |
| 281 | + "", |
| 282 | + HumanDateExpr::OrdinalWeekdayOfMonth( |
| 283 | + Ordinal::Second, |
| 284 | + Weekday::Thu, |
| 285 | + Month::September |
| 286 | + ) |
| 287 | + )) |
| 288 | + ); |
| 289 | + assert_eq!( |
| 290 | + parser.parse_peek("third sunday of september"), |
| 291 | + Ok(( |
| 292 | + "", |
| 293 | + HumanDateExpr::OrdinalWeekdayOfMonth( |
| 294 | + Ordinal::Third, |
| 295 | + Weekday::Sun, |
| 296 | + Month::September |
| 297 | + ) |
| 298 | + )) |
| 299 | + ); |
| 300 | + } |
| 301 | + |
| 302 | + #[test] |
| 303 | + fn test_weekday() { |
| 304 | + assert_eq!(weekday.parse_peek("monday"), Ok(("", Weekday::Mon))); |
| 305 | + assert_eq!(weekday.parse_peek("mon"), Ok(("", Weekday::Mon))); |
| 306 | + assert_eq!(weekday.parse_peek("tuesday"), Ok(("", Weekday::Tue))); |
| 307 | + assert_eq!(weekday.parse_peek("tue"), Ok(("", Weekday::Tue))); |
| 308 | + assert_eq!(weekday.parse_peek("wednesday"), Ok(("", Weekday::Wed))); |
| 309 | + assert_eq!(weekday.parse_peek("wed"), Ok(("", Weekday::Wed))); |
| 310 | + assert_eq!(weekday.parse_peek("thursday"), Ok(("", Weekday::Thu))); |
| 311 | + assert_eq!(weekday.parse_peek("thu"), Ok(("", Weekday::Thu))); |
| 312 | + assert_eq!(weekday.parse_peek("friday"), Ok(("", Weekday::Fri))); |
| 313 | + assert_eq!(weekday.parse_peek("fri"), Ok(("", Weekday::Fri))); |
| 314 | + assert_eq!(weekday.parse_peek("saturday"), Ok(("", Weekday::Sat))); |
| 315 | + assert_eq!(weekday.parse_peek("sat"), Ok(("", Weekday::Sat))); |
| 316 | + assert_eq!(weekday.parse_peek("sunday"), Ok(("", Weekday::Sun))); |
| 317 | + assert_eq!(weekday.parse_peek("sun"), Ok(("", Weekday::Sun))); |
| 318 | + } |
| 319 | + |
| 320 | + #[test] |
| 321 | + fn test_this() { |
| 322 | + assert_eq!(this.parse_peek("this"), Ok(("", ()))); |
| 323 | + assert_eq!(this.parse_peek("the current"), Ok(("", ()))); |
| 324 | + } |
| 325 | + |
| 326 | + #[test] |
| 327 | + fn test_next() { |
| 328 | + assert_eq!(next.parse_peek("next"), Ok(("", ()))); |
| 329 | + assert_eq!(next.parse_peek("the next"), Ok(("", ()))); |
| 330 | + assert_eq!(next.parse_peek("the following"), Ok(("", ()))); |
| 331 | + } |
| 332 | + |
| 333 | + #[test] |
| 334 | + fn test_number() { |
| 335 | + assert_eq!(number(&mut "1"), Ok(1)); |
| 336 | + assert_eq!(number(&mut "01"), Ok(1)); |
| 337 | + assert_eq!(number(&mut "one"), Ok(1)); |
| 338 | + assert_eq!(number(&mut "two"), Ok(2)); |
| 339 | + assert_eq!(number(&mut "three"), Ok(3)); |
| 340 | + assert_eq!(number(&mut "four"), Ok(4)); |
| 341 | + assert_eq!(number(&mut "five"), Ok(5)); |
| 342 | + assert_eq!(number(&mut "six"), Ok(6)); |
| 343 | + assert_eq!(number(&mut "seven"), Ok(7)); |
| 344 | + assert_eq!(number(&mut "eight"), Ok(8)); |
| 345 | + assert_eq!(number(&mut "nine"), Ok(9)); |
| 346 | + assert_eq!(number(&mut "ten"), Ok(10)); |
| 347 | + assert_eq!(number(&mut "eleven"), Ok(11)); |
| 348 | + assert_eq!(number(&mut "twelve"), Ok(12)); |
| 349 | + assert_eq!(number(&mut "thirteen"), Ok(13)); |
| 350 | + assert_eq!(number(&mut "fourteen"), Ok(14)); |
| 351 | + assert_eq!(number(&mut "fifteen"), Ok(15)); |
| 352 | + assert_eq!(number(&mut "sixteen"), Ok(16)); |
| 353 | + assert_eq!(number(&mut "seventeen"), Ok(17)); |
| 354 | + assert_eq!(number(&mut "eighteen"), Ok(18)); |
| 355 | + assert_eq!(number(&mut "nineteen"), Ok(19)); |
| 356 | + assert_eq!(number(&mut "twenty"), Ok(20)); |
| 357 | + } |
| 358 | +} |
0 commit comments