From 107966389c40ca3303da61e93da7f3339104e427 Mon Sep 17 00:00:00 2001 From: Joscha Date: Sat, 6 Nov 2021 17:36:40 +0100 Subject: [PATCH] Parse simple dates and times --- Cargo.lock | 30 ++++++++++ Cargo.toml | 1 + src/main.rs | 1 + src/parse.rs | 151 +++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 183 insertions(+) create mode 100644 src/parse.rs diff --git a/Cargo.lock b/Cargo.lock index 59da77e..f2bb935 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -27,6 +27,29 @@ version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a60553f9a9e039a333b4e9b20573b9e9b9c0bb3a11e201ccc48ef4283456d673" +[[package]] +name = "memchr" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "nom" +version = "7.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d11e1ef389c76fe5b81bcaf2ea32cf88b62bc494e19f493d0b30e7a930109" +dependencies = [ + "memchr", + "minimal-lexical", + "version_check", +] + [[package]] name = "num-integer" version = "0.1.44" @@ -62,8 +85,15 @@ name = "today" version = "0.1.0" dependencies = [ "chrono", + "nom", ] +[[package]] +name = "version_check" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" + [[package]] name = "wasi" version = "0.10.0+wasi-snapshot-preview1" diff --git a/Cargo.toml b/Cargo.toml index 14aa956..fa01f4c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,3 +5,4 @@ edition = "2018" [dependencies] chrono = "0.4.19" +nom = "7.1.0" diff --git a/src/main.rs b/src/main.rs index b97e52d..3bff741 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,5 @@ mod commands; +mod parse; fn main() { println!("Hello, world!"); diff --git a/src/parse.rs b/src/parse.rs new file mode 100644 index 0000000..f353408 --- /dev/null +++ b/src/parse.rs @@ -0,0 +1,151 @@ +use std::str::FromStr; + +use chrono::{NaiveDate, NaiveTime}; +use nom::branch::Alt; +use nom::bytes::complete::{take_while1, take_while_m_n}; +use nom::character::complete::{char, digit1, newline}; +use nom::combinator::{eof, fail, map_opt, map_res}; +use nom::sequence::terminated; +use nom::{IResult, Parser}; + +fn line_ending(i: &str) -> IResult<&str, ()> { + (newline.map(|_| ()), eof.map(|_| ())).choice(i) +} + +fn title(i: &str) -> IResult<&str, &str> { + terminated(take_while1(|c| c != '\n'), line_ending)(i) +} + +fn number(i: &str) -> IResult<&str, N> { + map_res(digit1, str::parse)(i) +} + +fn fixed_width_number(i: &str) -> IResult<&str, N> { + map_res( + take_while_m_n(W, W, |c: char| c.is_ascii_digit()), + str::parse, + )(i) +} + +fn date(i: &str) -> IResult<&str, NaiveDate> { + let (i, year) = number::(i)?; + let (i, _) = char('-')(i)?; + let (i, month) = fixed_width_number::(i)?; + let (i, _) = char('-')(i)?; + let (i, day) = fixed_width_number::(i)?; + match NaiveDate::from_ymd_opt(year, month, day) { + Some(date) => Ok((i, date)), + None => fail(i), + } +} + +fn time(i: &str) -> IResult<&str, NaiveTime> { + let (i, hour) = fixed_width_number::(i)?; + let (i, _) = char(':')(i)?; + let (i, min) = fixed_width_number::(i)?; + if hour < 24 && min < 60 { + Ok((i, NaiveTime::from_hms(hour, min, 0))) + } else { + fail(i) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_line_ending() { + assert_eq!(line_ending("\n"), Ok(("", ()))); + assert_eq!(line_ending("\nbla"), Ok(("bla", ()))); + assert_eq!(line_ending("\n\n"), Ok(("\n", ()))); + assert_eq!(line_ending(""), Ok(("", ()))); + + assert!(line_ending("bla").is_err()); + assert!(line_ending("\r").is_err()); + assert!(line_ending("\r\n").is_err()); + } + + #[test] + fn test_title() { + assert_eq!(title("foo bar\nbaz"), Ok(("baz", "foo bar"))); + assert_eq!(title("foo bar\n"), Ok(("", "foo bar"))); + assert_eq!(title("foo bar"), Ok(("", "foo bar"))); + assert_eq!(title(" \nbla"), Ok(("bla", " "))); + assert_eq!( + title("!\"§$%&/()'<>[]{}_:;.,-=\nbla"), + Ok(("bla", "!\"§$%&/()'<>[]{}_:;.,-=")) + ); + + assert!(title("\nxyz").is_err()); + assert!(title("").is_err()); + } + + #[test] + fn test_number() { + assert_eq!(number::("012345abc"), Ok(("abc", 12345))); + assert_eq!(number::("255"), Ok(("", 255))); + assert_eq!(number::("0x3f"), Ok(("x3f", 0))); + + assert!(number::("256").is_err()); + assert!(number::("xyz").is_err()); + assert!(number::("").is_err()); + } + + #[test] + fn test_fixed_width_number() { + assert_eq!( + fixed_width_number::("012345abc"), + Ok(("2345abc", 1)) + ); + assert_eq!( + fixed_width_number::("012345abc"), + Ok(("345abc", 12)) + ); + assert_eq!( + fixed_width_number::("012345abc"), + Ok(("abc", 12345)) + ); + + assert!(fixed_width_number::("012345abc").is_err()); + assert!(fixed_width_number::("012345abc").is_err()); + assert!(fixed_width_number::("14x").is_err()); + assert!(fixed_width_number::("").is_err()); + } + + #[test] + fn test_date() { + assert_eq!( + date("2021-11-06"), + Ok(("", NaiveDate::from_ymd(2021, 11, 6))) + ); + assert_eq!( + date("2021-11-0678"), + Ok(("78", NaiveDate::from_ymd(2021, 11, 6))) + ); + assert_eq!(date("0-01-01"), Ok(("", NaiveDate::from_ymd(0, 1, 1)))); + assert_eq!( + date("2020-02-29"), + Ok(("", NaiveDate::from_ymd(2020, 2, 29))) + ); + + assert!(date("2021-11-6").is_err()); + assert!(date("0000-00-00").is_err()); + assert!(date("2021-02-29").is_err()); + } + + #[test] + fn test_time() { + assert_eq!(time("12:34"), Ok(("", NaiveTime::from_hms(12, 34, 0)))); + assert_eq!(time("00:00"), Ok(("", NaiveTime::from_hms(0, 0, 0)))); + assert_eq!(time("23:59"), Ok(("", NaiveTime::from_hms(23, 59, 0)))); + assert_eq!(time("02:04:06"), Ok((":06", NaiveTime::from_hms(2, 4, 0)))); + + assert!(time("abc").is_err()); + assert!(time("24:23").is_err()); + assert!(time("12:60").is_err()); + assert!(time("12-34").is_err()); + assert!(time("2:34").is_err()); + assert!(time("12:3").is_err()); + } +}