From e12aaac56d7375515cb9c7c468d6d65dfa1ae76f Mon Sep 17 00:00:00 2001 From: Matthias Beyer Date: Sun, 19 Nov 2017 14:12:50 +0100 Subject: Split parser into modules The TimeType::parse() function was removed because we don't want that there. It is not that nice, because the interface would return either a TimeType or an Iterator. --- src/parser.rs | 1002 ------------------------------------------------ src/parser/iterator.rs | 396 +++++++++++++++++++ src/parser/mod.rs | 77 ++++ src/parser/timetype.rs | 583 ++++++++++++++++++++++++++++ src/timetype.rs | 8 - 5 files changed, 1056 insertions(+), 1010 deletions(-) delete mode 100644 src/parser.rs create mode 100644 src/parser/iterator.rs create mode 100644 src/parser/mod.rs create mode 100644 src/parser/timetype.rs diff --git a/src/parser.rs b/src/parser.rs deleted file mode 100644 index 5d518f5..0000000 --- a/src/parser.rs +++ /dev/null @@ -1,1002 +0,0 @@ -//! The definition of the "kairos" syntax, for parsing user input into TimeType objects -//! -//! The syntax itself is described in the grammar.rustpeg file. -//! Here goes a documentation on the syntax -//! -//! # Syntax -//! -//! ## Units -//! -//! UnitSec = "second" | "seconds" | "sec" | "secs" | "s" -//! UnitMin = "minute" | "minutes" | "min" | "mins" -//! UnitHr = "hour" | "hours" | "hr" | "hrs" -//! UnitDay = "day" | "days" | "d" -//! UnitWeek = "week" | "weeks" | "w" -//! UnitMonth = "month" | "months" | -//! UnitYear = "year" | "years" | "yrs" -//! Unit = UnitSec | UnitMin | UnitHr | UnitDay | UnitWeek | UnitMonth | UnitYear -//! -//! ## Operators -//! -//! Operator = "+" | "-" -//! -//! ## Intermediate syntax nodes -//! -//! Amount = "" -//! -//! TextIterSpec = "secondly" | "minutely" | "hourly" | "daily" | "weekly" | "monthly" | "yearly" -//! Iterspec = TextIterSpec | "every" -//! -//! ## User-facing syntax nodes -//! -//! AmountExpr = ( )? -//! ExactDate = "today" | "yesterday" | "tomorrow" | -//! Date = ( )? -//! Iterator = ("until" | "times")? -//! -//! # Warning -//! -//! This module is not intended for public use... it is still public, so you can use it, but you -//! should know that these interfaces are considered private and I will not follow semver and -//! update the minor or major semver numbers of the interface of this module changes. -//! -//! Be warned! -//! - -use nom::{IResult, space, alpha, alphanumeric, digit}; -use std::str; -use std::str::FromStr; - -use chrono::NaiveDate; - -use timetype; -use iter; -use error; - -named!(integer, alt!( - map_res!( - map_res!( - ws!(digit), - str::from_utf8 - ), - FromStr::from_str - ) -)); - -// WARNING: Order is important here. Long tags first, shorter tags later -named!(unit_parser, alt_complete!( - tag!("seconds") => { |_| Unit::Second } | - tag!("second") => { |_| Unit::Second } | - tag!("secs") => { |_| Unit::Second } | - tag!("sec") => { |_| Unit::Second } | - tag!("s") => { |_| Unit::Second } | - tag!("minutes") => { |_| Unit::Minute } | - tag!("minute") => { |_| Unit::Minute } | - tag!("mins") => { |_| Unit::Minute } | - tag!("min") => { |_| Unit::Minute } | - tag!("hours") => { |_| Unit::Hour } | - tag!("hour") => { |_| Unit::Hour } | - tag!("hrs") => { |_| Unit::Hour } | - tag!("hr") => { |_| Unit::Hour } | - tag!("days") => { |_| Unit::Day } | - tag!("day") => { |_| Unit::Day } | - tag!("d") => { |_| Unit::Day } | - tag!("weeks") => { |_| Unit::Week } | - tag!("week") => { |_| Unit::Week } | - tag!("w") => { |_| Unit::Week } | - tag!("months") => { |_| Unit::Month } | - tag!("month") => { |_| Unit::Month } | - tag!("years") => { |_| Unit::Year } | - tag!("year") => { |_| Unit::Year } | - tag!("yrs") => { |_| Unit::Year } -)); - -#[derive(Debug, PartialEq, Eq)] -pub enum Unit { - Second, - Minute, - Hour, - Day, - Week, - Month, - Year, -} - -named!(operator_parser, alt!( - tag!("+") => { |_| Operator::Plus } | - tag!("-") => { |_| Operator::Minus } -)); - -#[derive(Debug, PartialEq, Eq)] -pub enum Operator { - Plus, - Minus, -} - -named!(amount_parser, do_parse!( - number: integer >> - unit : unit_parser >> - (Amount(number, unit)) -)); - -#[derive(Debug, PartialEq, Eq)] -pub struct Amount(i64, Unit); - -impl Into for Amount { - fn into(self) -> timetype::TimeType { - match self.1 { - Unit::Second => timetype::TimeType::seconds(self.0), - Unit::Minute => timetype::TimeType::minutes(self.0), - Unit::Hour => timetype::TimeType::hours(self.0), - Unit::Day => timetype::TimeType::days(self.0), - Unit::Week => timetype::TimeType::weeks(self.0), - Unit::Month => timetype::TimeType::months(self.0), - Unit::Year => timetype::TimeType::years(self.0), - } - } -} - -named!(iter_spec, alt_complete!( - tag!("secondly") => { |_| Iterspec::Secondly } | - tag!("minutely") => { |_| Iterspec::Minutely } | - tag!("hourly") => { |_| Iterspec::Hourly } | - tag!("daily") => { |_| Iterspec::Daily } | - tag!("weekly") => { |_| Iterspec::Weekly } | - tag!("monthly") => { |_| Iterspec::Monthly } | - tag!("yearly") => { |_| Iterspec::Yearly } | - do_parse!( - tag!("every") >> - number:integer >> - unit:unit_parser >> - (Iterspec::Every(number, unit)) - ) -)); - -#[derive(Debug, PartialEq, Eq)] -pub enum Iterspec { - Secondly, - Minutely, - Hourly, - Daily, - Weekly, - Monthly, - Yearly, - Every(i64, Unit), -} - -use nom::whitespace::sp; - -named!(amount_expr_next<(Operator, Box)>, do_parse!( - op:operator_parser - >> opt!(sp) - >> amexp:amount_expr - >> ((op, Box::new(amexp))) -)); - -named!(amount_expr, do_parse!( - amount:amount_parser >> - opt!(sp) >> - o: opt!(complete!(amount_expr_next)) >> - (AmountExpr { amount: amount, next: o, }) -)); - -#[derive(Debug, PartialEq, Eq)] -pub struct AmountExpr { - amount: Amount, - next: Option<(Operator, Box)>, -} - -impl Into for AmountExpr { - fn into(self) -> timetype::TimeType { - let mut amount = self.amount.into(); - - if let Some((op, other_amonut_expr)) = self.next { - match op { - Operator::Plus => { - amount = amount + (*other_amonut_expr).into(); - }, - Operator::Minus => { - amount = amount - (*other_amonut_expr).into(); - }, - } - } - - amount - } -} - -impl AmountExpr { - fn new(amount: Amount, next: Option<(Operator, Box)>) -> AmountExpr { - AmountExpr { - amount: amount, - next: next - } - } -} - -use iso8601::parsers::parse_date; -use iso8601::parsers::parse_datetime; -// The order is relevant here, because datetime is longer than date, we must parse datetime before -// date. -named!(exact_date_parser, alt_complete!( - tag!("today") => { |_| ExactDate::Today } | - tag!("yesterday") => { |_| ExactDate::Yesterday } | - tag!("tomorrow") => { |_| ExactDate::Tomorrow } | - do_parse!(d: parse_datetime >> (ExactDate::Iso8601DateTime(d))) | - do_parse!(d: parse_date >> (ExactDate::Iso8601Date(d))) -)); - -#[derive(Debug, PartialEq, Eq)] -pub enum ExactDate { - Today, - Yesterday, - Tomorrow, - Iso8601Date(::iso8601::Date), - Iso8601DateTime(::iso8601::DateTime) -} - -impl Into for ExactDate { - fn into(self) -> timetype::TimeType { - match self { - ExactDate::Today => timetype::TimeType::today(), - ExactDate::Yesterday => timetype::TimeType::today() - timetype::TimeType::days(1), - ExactDate::Tomorrow => timetype::TimeType::today() + timetype::TimeType::days(1), - ExactDate::Iso8601Date(date) => { - let (year, month, day) = match date { - ::iso8601::Date::YMD { year, month, day } => { - (year, month, day) - }, - ::iso8601::Date::Week { year, ww, d } => { - unimplemented!() - }, - ::iso8601::Date::Ordinal { year, ddd } => { - unimplemented!() - }, - }; - - let ndt = NaiveDate::from_ymd(year, month, day).and_hms(0, 0, 0); - timetype::TimeType::moment(ndt) - }, - ExactDate::Iso8601DateTime(::iso8601::DateTime { date, time }) => { - let (hour, minute, second) = (time.hour, time.minute, time.second); - let (year, month, day) = match date { - ::iso8601::Date::YMD { year, month, day } => { - (year, month, day) - }, - ::iso8601::Date::Week { year, ww, d } => { - unimplemented!() - }, - ::iso8601::Date::Ordinal { year, ddd } => { - unimplemented!() - }, - }; - - let ndt = NaiveDate::from_ymd(year, month, day).and_hms(hour, minute, second); - timetype::TimeType::moment(ndt) - }, - } - } -} - -named!(date, do_parse!( - exact: exact_date_parser >> - o: opt!( - complete!(do_parse!(sp >> op:operator_parser >> sp >> a:amount_expr >> (op, a))) - ) >> - (Date(exact, o)) -)); - -#[derive(Debug, PartialEq, Eq)] -pub struct Date(ExactDate, Option<(Operator, AmountExpr)>); - -impl Into for Date { - fn into(self) -> timetype::TimeType { - let base : timetype::TimeType = self.0.into(); - match self.1 { - Some((Operator::Plus, amount)) => base + amount.into(), - Some((Operator::Minus, amount)) => base - amount.into(), - None => base, - } - } -} - -/// Main entry function for timetype parser -/// -/// # Notice -/// -/// Note that this function returns a parser::TimeType, not a timetype::TimeType. Though, the -/// parser::TimeType can be `Into::into()`ed. -/// -named!(pub timetype, alt!( - do_parse!(d: date >> (TimeType::Date(d))) | - do_parse!(a: amount_expr >> (TimeType::AmountExpr(a))) -)); - -#[derive(Debug, PartialEq, Eq)] -pub enum TimeType { - Date(Date), - AmountExpr(AmountExpr), -} - -impl Into for TimeType { - fn into(self) -> timetype::TimeType { - match self { - TimeType::Date(d) => d.into(), - TimeType::AmountExpr(a) => a.into(), - } - } -} - -named!(until_spec, alt_complete!( - do_parse!( - tag!("until") >> sp >> - exact: exact_date_parser >> - (UntilSpec::Exact(exact)) - ) | - do_parse!( - num: integer >> sp >> - tag!("times") >> - (UntilSpec::Times(num)) - ) -)); - -#[derive(Debug, PartialEq, Eq)] -pub enum UntilSpec { - Exact(ExactDate), - Times(i64) -} - -named!(iterator, do_parse!( - opt!(sp) >> d: date >> - opt!(sp) >> spec: iter_spec >> - opt!(sp) >> until: opt!(complete!(until_spec)) >> - (Iterator(d, spec, until)) -)); - -#[derive(Debug, PartialEq, Eq)] -pub struct Iterator(Date, Iterspec, Option); - -impl Iterator { - pub fn into_user_iterator(self) -> error::Result> { - use iter::Times; - use iter::Until; - - let unit_to_amount = |i, unit| match unit { - Unit::Second => timetype::TimeType::seconds(i), - Unit::Minute => timetype::TimeType::minutes(i), - Unit::Hour => timetype::TimeType::hours(i), - Unit::Day => timetype::TimeType::days(i), - Unit::Week => timetype::TimeType::weeks(i), - Unit::Month => timetype::TimeType::months(i), - Unit::Year => timetype::TimeType::years(i), - }; - - let recur = match self.1 { - Iterspec::Every(i, unit) => unit_to_amount(i, unit), - Iterspec::Secondly => unit_to_amount(1, Unit::Second), - Iterspec::Minutely => unit_to_amount(1, Unit::Minute), - Iterspec::Hourly => unit_to_amount(1, Unit::Hour), - Iterspec::Daily => unit_to_amount(1, Unit::Day), - Iterspec::Weekly => unit_to_amount(1, Unit::Week), - Iterspec::Monthly => unit_to_amount(1, Unit::Month), - Iterspec::Yearly => unit_to_amount(1, Unit::Year), - }; - - let into_ndt = |e: timetype::TimeType| try!(e.calculate()) - .get_moment() - .ok_or(error::KairosErrorKind::NotADateInsideIterator) - .map(Clone::clone); - - match self.2 { - Some(UntilSpec::Exact(e)) => { - let base = try!(into_ndt(self.0.into())); - let e = try!(into_ndt(e.into())); - - iter::Iter::build(base, recur) - .map(|it| UserIterator::UntilIterator(it.until(e))) - }, - - Some(UntilSpec::Times(i)) => { - let base = try!(into_ndt(self.0.into())); - iter::Iter::build(base, recur) - .map(|it| it.times(i)) - .map(UserIterator::TimesIter) - }, - - None => { - let base = try!(into_ndt(self.0.into())); - iter::Iter::build(base, recur) - .map(UserIterator::Iterator) - }, - } - } -} - -// names are hard -#[derive(Debug)] -pub enum UserIterator - where I: ::std::iter::Iterator> -{ - Iterator(iter::Iter), - TimesIter(iter::TimesIter), - UntilIterator(iter::UntilIter) -} - -impl ::std::iter::Iterator for UserIterator - where I: ::std::iter::Iterator> -{ - type Item = error::Result; - - fn next(&mut self) -> Option { - match *self { - UserIterator::Iterator(ref mut i) => i.next(), - UserIterator::TimesIter(ref mut i) => i.next(), - UserIterator::UntilIterator(ref mut i) => i.next(), - } - } -} - -#[cfg(test)] -mod tests { - use nom::IResult; - use super::*; - - use chrono::Timelike; - use chrono::Datelike; - - #[test] - fn test_integer() { - assert_eq!(integer(&b"2"[..]), IResult::Done(&b""[..], 2)); - assert_eq!(integer(&b"217"[..]), IResult::Done(&b""[..], 217)); - } - - #[test] - fn test_unit() { - assert_eq!(unit_parser(&b"second"[..]), IResult::Done(&b""[..], Unit::Second)); - assert_eq!(unit_parser(&b"seconds"[..]), IResult::Done(&b""[..], Unit::Second)); - assert_eq!(unit_parser(&b"sec"[..]), IResult::Done(&b""[..], Unit::Second)); - assert_eq!(unit_parser(&b"secs"[..]), IResult::Done(&b""[..], Unit::Second)); - assert_eq!(unit_parser(&b"s"[..]), IResult::Done(&b""[..], Unit::Second)); - assert_eq!(unit_parser(&b"minute"[..]), IResult::Done(&b""[..], Unit::Minute)); - assert_eq!(unit_parser(&b"minutes"[..]), IResult::Done(&b""[..], Unit::Minute)); - assert_eq!(unit_parser(&b"min"[..]), IResult::Done(&b""[..], Unit::Minute)); - assert_eq!(unit_parser(&b"mins"[..]), IResult::Done(&b""[..], Unit::Minute)); - assert_eq!(unit_parser(&b"hour"[..]), IResult::Done(&b""[..], Unit::Hour)); - assert_eq!(unit_parser(&b"hours"[..]), IResult::Done(&b""[..], Unit::Hour)); - assert_eq!(unit_parser(&b"hr"[..]), IResult::Done(&b""[..], Unit::Hour)); - assert_eq!(unit_parser(&b"hrs"[..]), IResult::Done(&b""[..], Unit::Hour)); - assert_eq!(unit_parser(&b"day"[..]), IResult::Done(&b""[..], Unit::Day)); - assert_eq!(unit_parser(&b"days"[..]), IResult::Done(&b""[..], Unit::Day)); - assert_eq!(unit_parser(&b"d"[..]), IResult::Done(&b""[..], Unit::Day)); - assert_eq!(unit_parser(&b"week"[..]), IResult::Done(&b""[..], Unit::Week)); - assert_eq!(unit_parser(&b"weeks"[..]), IResult::Done(&b""[..], Unit::Week)); - assert_eq!(unit_parser(&b"w"[..]), IResult::Done(&b""[..], Unit::Week)); - assert_eq!(unit_parser(&b"month"[..]), IResult::Done(&b""[..], Unit::Month)); - assert_eq!(unit_parser(&b"months"[..]), IResult::Done(&b""[..], Unit::Month)); - assert_eq!(unit_parser(&b"year"[..]), IResult::Done(&b""[..], Unit::Year)); - assert_eq!(unit_parser(&b"years"[..]), IResult::Done(&b""[..], Unit::Year)); - assert_eq!(unit_parser(&b"yrs"[..]), IResult::Done(&b""[..], Unit::Year)); - } - - #[test] - fn test_operator() { - assert_eq!(operator_parser(&b"+"[..]), IResult::Done(&b""[..], Operator::Plus)); - assert_eq!(operator_parser(&b"-"[..]), IResult::Done(&b""[..], Operator::Minus)); - } - - #[test] - fn test_amount() { - assert_eq!(amount_parser(&b"5s"[..]), IResult::Done(&b""[..], Amount(5, Unit::Second))); - assert_eq!(amount_parser(&b"5min"[..]), IResult::Done(&b""[..], Amount(5, Unit::Minute))); - assert_eq!(amount_parser(&b"55hrs"[..]), IResult::Done(&b""[..], Amount(55, Unit::Hour))); - assert_eq!(amount_parser(&b"25days"[..]), IResult::Done(&b""[..], Amount(25, Unit::Day))); - assert_eq!(amount_parser(&b"15weeks"[..]), IResult::Done(&b""[..], Amount(15, Unit::Week))); - } - - #[test] - fn test_iterspec() { - assert_eq!(iter_spec(&b"secondly"[..]), IResult::Done(&b""[..], Iterspec::Secondly)); - assert_eq!(iter_spec(&b"minutely"[..]), IResult::Done(&b""[..], Iterspec::Minutely)); - assert_eq!(iter_spec(&b"hourly"[..]), IResult::Done(&b""[..], Iterspec::Hourly)); - assert_eq!(iter_spec(&b"daily"[..]), IResult::Done(&b""[..], Iterspec::Daily)); - assert_eq!(iter_spec(&b"weekly"[..]), IResult::Done(&b""[..], Iterspec::Weekly)); - assert_eq!(iter_spec(&b"monthly"[..]), IResult::Done(&b""[..], Iterspec::Monthly)); - assert_eq!(iter_spec(&b"yearly"[..]), IResult::Done(&b""[..], Iterspec::Yearly)); - assert_eq!(iter_spec(&b"every 5min"[..]), IResult::Done(&b""[..], Iterspec::Every(5, Unit::Minute))); - } - - #[test] - fn test_amountexpr_next() { - assert_eq!(amount_expr_next(&b"+ 12minutes"[..]), - IResult::Done(&b""[..], - ( - Operator::Plus, - Box::new(AmountExpr { amount: Amount(12, Unit::Minute), next: None }) - ) - )); - } - - #[test] - fn test_amountexpr() { - assert_eq!(amount_expr(&b"5minutes"[..]), - IResult::Done(&b""[..], - AmountExpr { - amount: Amount(5, Unit::Minute), - next: None - }) - ); - - assert_eq!(amount_expr(&b"5min + 12min"[..]), - IResult::Done(&b""[..], - AmountExpr { - amount: Amount(5, Unit::Minute), - next: Some((Operator::Plus, Box::new( - AmountExpr { - amount: Amount(12, Unit::Minute), - next: None - }))) - })); - } - - #[test] - fn test_parse_expressions_date() { - use iso8601::Date; - let res = exact_date_parser(&b"2017-01-01"[..]); - assert!(res.is_done()); - - match res.unwrap().1 { - ExactDate::Iso8601DateTime(_) => assert!(false), - ExactDate::Iso8601Date(d) => { - match d { - Date::YMD { year, month, day } => { - assert_eq!(year, 2017); - assert_eq!(month, 1); - assert_eq!(day, 1) - }, - _ => assert!(false), - } - }, - ExactDate::Tomorrow => assert!(false), - ExactDate::Yesterday => assert!(false), - ExactDate::Today => assert!(false), - }; - } - - #[test] - fn test_parse_expressions_datetime() { - use iso8601::Date; - let res = exact_date_parser(&b"2017-01-01T22:00:11"[..]); - assert!(res.is_done()); - - match res.unwrap().1 { - ExactDate::Iso8601DateTime(obj) => { - match obj.date { - Date::YMD { year, month, day } => { - assert_eq!(year, 2017); - assert_eq!(month, 1); - assert_eq!(day, 1) - }, - _ => assert!(false), - } - assert_eq!(obj.time.hour, 22); - assert_eq!(obj.time.minute, 0); - assert_eq!(obj.time.second, 11); - }, - ExactDate::Iso8601Date(_) => assert!(false), - ExactDate::Tomorrow => assert!(false), - ExactDate::Yesterday => assert!(false), - ExactDate::Today => assert!(false), - }; - } - - #[test] - fn test_simple_date_1() { - let res = exact_date_parser(&b"today"[..]); - assert!(res.is_done(), format!("Not done: {:?}", res)); - - let res = date(&b"today"[..]); - assert!(res.is_done(), format!("Not done: {:?}", res)); - } - - #[test] - fn test_simple_date_2() { - let res = date(&b"2017-01-01"[..]); - assert!(res.is_done(), format!("Not done: {:?}", res)); - let (_, o) = res.unwrap(); - - println!("{:#?}", o); - - let calc_res : timetype::TimeType = o.into(); - let calc_res = calc_res.calculate(); - assert!(calc_res.is_ok()); - - let calc_res = calc_res.unwrap(); - println!("{:#?}", calc_res); - - assert_eq!(calc_res.get_moment().unwrap().year() , 2017); - assert_eq!(calc_res.get_moment().unwrap().month() , 01); - assert_eq!(calc_res.get_moment().unwrap().day() , 01); - assert_eq!(calc_res.get_moment().unwrap().hour() , 00); - assert_eq!(calc_res.get_moment().unwrap().minute(), 00); - assert_eq!(calc_res.get_moment().unwrap().second(), 00); - } - - #[test] - fn test_simple_date_3() { - let res = date(&b"2017-01-01T01:02:03"[..]); - assert!(res.is_done(), format!("Not done: {:?}", res)); - let (_, o) = res.unwrap(); - - println!("{:#?}", o); - - let calc_res : timetype::TimeType = o.into(); - let calc_res = calc_res.calculate(); - assert!(calc_res.is_ok()); - - let calc_res = calc_res.unwrap(); - println!("{:#?}", calc_res); - - assert_eq!(calc_res.get_moment().unwrap().year() , 2017); - assert_eq!(calc_res.get_moment().unwrap().month() , 01); - assert_eq!(calc_res.get_moment().unwrap().day() , 01); - assert_eq!(calc_res.get_moment().unwrap().hour() , 01); - assert_eq!(calc_res.get_moment().unwrap().minute(), 02); - assert_eq!(calc_res.get_moment().unwrap().second(), 03); - } - - #[test] - fn test_expressions_to_date() { - let res = amount_expr(&b"5min + 12min"[..]); - assert!(res.is_done()); - let (_, o) = res.unwrap(); - - let calc_res : timetype::TimeType = o.into(); - let calc_res = calc_res.calculate(); - assert!(calc_res.is_ok()); - - let calc_res = calc_res.unwrap(); - assert_eq!(calc_res.get_seconds(), 17 * 60); - assert_eq!(calc_res.get_minutes(), 17); - assert_eq!(calc_res.get_hours(), 0); - assert_eq!(calc_res.get_days(), 0); - assert_eq!(calc_res.get_years(), 0); - } - - #[test] - fn test_expressions_to_date_2() { - let res = amount_expr(&b"5min + 12min + 15hours"[..]); - assert!(res.is_done()); - let (_, o) = res.unwrap(); - - let calc_res : timetype::TimeType = o.into(); - let calc_res = calc_res.calculate(); - assert!(calc_res.is_ok()); - - let calc_res = calc_res.unwrap(); - assert_eq!(calc_res.get_seconds(), 17 * 60 + (15 * 60 * 60)); - assert_eq!(calc_res.get_minutes(), 17 + (15 * 60)); - assert_eq!(calc_res.get_hours(), 15); - assert_eq!(calc_res.get_days(), 0); - assert_eq!(calc_res.get_years(), 0); - } - - #[test] - fn test_expressions_to_date_3() { - let res = date(&b"today + 5min + 12min"[..]); - assert!(res.is_done(), "Not done: {:?}", res.unwrap_err().description()); - let (_, o) = res.unwrap(); - - let calc_res : timetype::TimeType = o.into(); - let calc_res = calc_res.calculate(); - assert!(calc_res.is_ok()); - - // because this test is basically dependent on the current time, which is a baaaad use of - // state in a test, we rely on `test_expressions_to_date_4()` here and assume that the - // upper assertions are enough. - } - - #[test] - fn test_expressions_to_date_4() { - let res = date(&b"2017-01-01 + 5min + 12min"[..]); - assert!(res.is_done(), "Not done: {:?}", res.unwrap_err().description()); - let (_, o) = res.unwrap(); - - println!("{:#?}", o); - - let calc_res : timetype::TimeType = o.into(); - let calc_res = calc_res.calculate(); - assert!(calc_res.is_ok()); - - let calc_res = calc_res.unwrap(); - println!("{:#?}", calc_res); - - assert_eq!(calc_res.get_moment().unwrap().year() , 2017); - assert_eq!(calc_res.get_moment().unwrap().month() , 01); - assert_eq!(calc_res.get_moment().unwrap().day() , 01); - assert_eq!(calc_res.get_moment().unwrap().hour() , 00); - assert_eq!(calc_res.get_moment().unwrap().minute(), 17); - assert_eq!(calc_res.get_moment().unwrap().second(), 00); - } - - #[test] - fn test_expressions_to_timetype() { - let res = timetype(&b"5min + 12min + 15hours"[..]); - assert!(res.is_done()); - let (_, o) = res.unwrap(); - - let calc_res : timetype::TimeType = o.into(); - let calc_res = calc_res.calculate(); - assert!(calc_res.is_ok()); - - let calc_res = calc_res.unwrap(); - assert_eq!(calc_res.get_seconds(), 17 * 60 + (15 * 60 * 60)); - assert_eq!(calc_res.get_minutes(), 17 + (15 * 60)); - assert_eq!(calc_res.get_hours(), 15); - assert_eq!(calc_res.get_days(), 0); - assert_eq!(calc_res.get_years(), 0); - } - - #[test] - fn test_expressions_to_timetype_2() { - let res = timetype(&b"today + 5min + 12min"[..]); - assert!(res.is_done(), "Not done: {:?}", res.unwrap_err().description()); - let (_, o) = res.unwrap(); - - let calc_res : timetype::TimeType = o.into(); - let calc_res = calc_res.calculate(); - assert!(calc_res.is_ok()); - - // because this test is basically dependent on the current time, which is a baaaad use of - // state in a test, we rely on `test_expressions_to_date_4()` here and assume that the - // upper assertions are enough. - } - - #[test] - fn test_expressions_to_timetype_subtract() { - let res = timetype(&b"5min + 12min + 15hours - 1hour"[..]); - assert!(res.is_done()); - let (_, o) = res.unwrap(); - - println!("{:#?}", o); - - let calc_res : timetype::TimeType = o.into(); - println!("{:#?}", calc_res); - - let calc_res = calc_res.calculate(); - assert!(calc_res.is_ok()); - println!("{:#?}", calc_res); - - let calc_res = calc_res.unwrap(); - assert_eq!(calc_res.get_seconds(), 17 * 60 + (14 * 60 * 60)); - assert_eq!(calc_res.get_minutes(), 17 + (14 * 60)); - assert_eq!(calc_res.get_hours(), 14); - assert_eq!(calc_res.get_days(), 0); - assert_eq!(calc_res.get_years(), 0); - } - - #[test] - fn test_iterator_1() { - let res = iterator(&b"2017-01-01 hourly"[..]); - assert!(res.is_done(), format!("Not done: {:?}", res)); - let (_, i) = res.unwrap(); - println!("{:#?}", i); - - let ui : Result, _> = i.into_user_iterator(); - assert!(ui.is_ok(), "Not okay: {:#?}", ui); - let mut ui = ui.unwrap(); - - for hour in 0..10 { // 10 is randomly chosen (fair dice roll... ) - let n = ui.next().unwrap(); - assert!(n.is_ok(), "Not ok: {:#?}", n); - let tt = n.unwrap(); - assert_eq!(tt.get_moment().unwrap().year() , 2017); - assert_eq!(tt.get_moment().unwrap().month() , 01); - assert_eq!(tt.get_moment().unwrap().day() , 01); - assert_eq!(tt.get_moment().unwrap().hour() , hour); - assert_eq!(tt.get_moment().unwrap().minute(), 00); - assert_eq!(tt.get_moment().unwrap().second(), 00); - } - } - - #[test] - fn test_iterator_2() { - let res = iterator(&b"2017-01-01 every 2mins"[..]); - assert!(res.is_done(), format!("Not done: {:?}", res)); - let (_, i) = res.unwrap(); - println!("{:#?}", i); - - let ui : Result, _> = i.into_user_iterator(); - assert!(ui.is_ok(), "Not okay: {:#?}", ui); - let mut ui = ui.unwrap(); - - for min in (0..59).into_iter().filter(|n| n % 2 == 0) { - let n = ui.next().unwrap(); - assert!(n.is_ok(), "Not ok: {:#?}", n); - let tt = n.unwrap(); - assert_eq!(tt.get_moment().unwrap().year() , 2017); - assert_eq!(tt.get_moment().unwrap().month() , 01); - assert_eq!(tt.get_moment().unwrap().day() , 01); - assert_eq!(tt.get_moment().unwrap().hour() , 00); - assert_eq!(tt.get_moment().unwrap().minute(), min); - assert_eq!(tt.get_moment().unwrap().second(), 00); - } - } - - #[test] - fn test_iterator_3() { - let res = iterator(&b"2017-01-01 daily"[..]); - assert!(res.is_done(), format!("Not done: {:?}", res)); - let (_, i) = res.unwrap(); - println!("{:#?}", i); - - let ui : Result, _> = i.into_user_iterator(); - assert!(ui.is_ok(), "Not okay: {:#?}", ui); - let mut ui = ui.unwrap(); - - for day in 1..29 { - let n = ui.next().unwrap(); - assert!(n.is_ok(), "Not ok: {:#?}", n); - let tt = n.unwrap(); - assert_eq!(tt.get_moment().unwrap().year() , 2017); - assert_eq!(tt.get_moment().unwrap().month() , 01); - assert_eq!(tt.get_moment().unwrap().day() , day); - assert_eq!(tt.get_moment().unwrap().hour() , 00); - assert_eq!(tt.get_moment().unwrap().minute(), 00); - assert_eq!(tt.get_moment().unwrap().second(), 00); - } - } - - #[test] - fn test_iterator_4() { - let res = iterator(&b"2017-01-01 weekly"[..]); - assert!(res.is_done(), format!("Not done: {:?}", res)); - let (_, i) = res.unwrap(); - println!("{:#?}", i); - - let ui : Result, _> = i.into_user_iterator(); - assert!(ui.is_ok(), "Not okay: {:#?}", ui); - let mut ui = ui.unwrap(); - - for week in 0..3 { - let n = ui.next().unwrap(); - assert!(n.is_ok(), "Not ok: {:#?}", n); - let tt = n.unwrap(); - assert_eq!(tt.get_moment().unwrap().year() , 2017); - assert_eq!(tt.get_moment().unwrap().month() , 01); - assert_eq!(tt.get_moment().unwrap().day() , 01 + (week * 7)); - assert_eq!(tt.get_moment().unwrap().hour() , 00); - assert_eq!(tt.get_moment().unwrap().minute(), 00); - assert_eq!(tt.get_moment().unwrap().second(), 00); - } - } - - #[test] - fn test_until_spec_1() { - let res = until_spec(&b"until 2017-01-01T05:00:00"[..]); - assert!(res.is_done(), format!("Not done: {:?}", res)); - let (_, i) = res.unwrap(); - println!("{:#?}", i); - } - - #[test] - fn test_until_iterator_1() { - let res = iterator(&b"2017-01-01 hourly until 2017-01-01T05:00:00"[..]); - assert!(res.is_done(), format!("Not done: {:?}", res)); - let (_, i) = res.unwrap(); - println!("{:#?}", i); - - let ui : Result, _> = i.into_user_iterator(); - assert!(ui.is_ok(), "Not okay: {:#?}", ui); - let mut ui = ui.unwrap(); - println!("Okay: {:#?}", ui); - - for hour in 0..10 { // 10 is randomly chosen (fair dice roll... ) - if hour > 4 { - let n = ui.next(); - assert!(n.is_none(), "Is Some, should be None: {:?}", n); - return; - } else { - let n = ui.next().unwrap(); - assert!(n.is_ok(), "Not ok: {:#?}", n); - let tt = n.unwrap(); - assert_eq!(tt.get_moment().unwrap().year() , 2017); - assert_eq!(tt.get_moment().unwrap().month() , 01); - assert_eq!(tt.get_moment().unwrap().day() , 01); - assert_eq!(tt.get_moment().unwrap().hour() , hour); - assert_eq!(tt.get_moment().unwrap().minute(), 00); - assert_eq!(tt.get_moment().unwrap().second(), 00); - } - } - } - - #[test] - fn test_until_iterator_2() { - let res = iterator(&b"2017-01-01 every 2mins until 2017-01-01T00:10:00"[..]); - assert!(res.is_done(), format!("Not done: {:?}", res)); - let (_, i) = res.unwrap(); - println!("{:#?}", i); - - let ui : Result, _> = i.into_user_iterator(); - assert!(ui.is_ok(), "Not okay: {:#?}", ui); - let mut ui = ui.unwrap(); - - for min in (0..60).into_iter().filter(|n| n % 2 == 0) { - if min > 9 { - let n = ui.next(); - assert!(n.is_none(), "Is Some, should be None: {:?}", n); - return; - } else { - let n = ui.next().unwrap(); - assert!(n.is_ok(), "Not ok: {:#?}", n); - let tt = n.unwrap(); - assert_eq!(tt.get_moment().unwrap().year() , 2017); - assert_eq!(tt.get_moment().unwrap().month() , 01); - assert_eq!(tt.get_moment().unwrap().day() , 01); - assert_eq!(tt.get_moment().unwrap().hour() , 00); - assert_eq!(tt.get_moment().unwrap().minute(), min); - assert_eq!(tt.get_moment().unwrap().second(), 00); - } - } - } - - #[test] - fn test_until_iterator_3() { - let res = iterator(&b"2017-01-01 daily until 2017-01-05"[..]); - assert!(res.is_done(), format!("Not done: {:?}", res)); - let (_, i) = res.unwrap(); - println!("{:#?}", i); - - let ui : Result, _> = i.into_user_iterator(); - assert!(ui.is_ok(), "Not okay: {:#?}", ui); - let mut ui = ui.unwrap(); - - for day in 1..30 { - if day > 4 { - let n = ui.next(); - assert!(n.is_none(), "Is Some, should be None: {:?}", n); - return; - } else { - let n = ui.next().unwrap(); - assert!(n.is_ok(), "Not ok: {:#?}", n); - let tt = n.unwrap(); - assert_eq!(tt.get_moment().unwrap().year() , 2017); - assert_eq!(tt.get_moment().unwrap().month() , 01); - assert_eq!(tt.get_moment().unwrap().day() , day); - assert_eq!(tt.get_moment().unwrap().hour() , 00); - assert_eq!(tt.get_moment().unwrap().minute(), 00); - assert_eq!(tt.get_moment().unwrap().second(), 00); - } - } - } - - #[test] - fn test_until_iterator_4() { - let res = iterator(&b"2017-01-01 weekly until 2017-01-14"[..]); - assert!(res.is_done(), format!("Not done: {:?}", res)); - let (_, i) = res.unwrap(); - println!("{:#?}", i); - - let ui : Result, _> = i.into_user_iterator(); - assert!(ui.is_ok(), "Not okay: {:#?}", ui); - let mut ui = ui.unwrap(); - - for week in 0..3 { - if (week * 7) > 13 { - let n = ui.next(); - assert!(n.is_none(), "Is Some, should be None: {:?}", n); - return; - } else { - let n = ui.next().unwrap(); - assert!(n.is_ok(), "Not ok: {:#?}", n); - let tt = n.unwrap(); - assert_eq!(tt.get_moment().unwrap().year() , 2017); - assert_eq!(tt.get_moment().unwrap().month() , 01); - assert_eq!(tt.get_moment().unwrap().day() , 01 + (week * 7)); - assert_eq!(tt.get_moment().unwrap().hour() , 00); - assert_eq!(tt.get_moment().unwrap().minute(), 00); - assert_eq!(tt.get_moment().unwrap().second(), 00); - } - } - } -} - diff --git a/src/parser/iterator.rs b/src/parser/iterator.rs new file mode 100644 index 0000000..f86e98f --- /dev/null +++ b/src/parser/iterator.rs @@ -0,0 +1,396 @@ +use std::str; +use std::str::FromStr; + +use nom::{IResult, space, alpha, alphanumeric, digit}; +use nom::whitespace::sp; +use chrono::NaiveDate; + +use parser::timetype::*; +use timetype; +use iter; +use error; + +named!(pub iter_spec, alt_complete!( + tag!("secondly") => { |_| Iterspec::Secondly } | + tag!("minutely") => { |_| Iterspec::Minutely } | + tag!("hourly") => { |_| Iterspec::Hourly } | + tag!("daily") => { |_| Iterspec::Daily } | + tag!("weekly") => { |_| Iterspec::Weekly } | + tag!("monthly") => { |_| Iterspec::Monthly } | + tag!("yearly") => { |_| Iterspec::Yearly } | + do_parse!( + tag!("every") >> + number:integer >> + unit:unit_parser >> + (Iterspec::Every(number, unit)) + ) +)); + +#[derive(Debug, PartialEq, Eq)] +pub enum Iterspec { + Secondly, + Minutely, + Hourly, + Daily, + Weekly, + Monthly, + Yearly, + Every(i64, Unit), +} + +named!(pub until_spec, alt_complete!( + do_parse!( + tag!("until") >> sp >> + exact: exact_date_parser >> + (UntilSpec::Exact(exact)) + ) | + do_parse!( + num: integer >> sp >> + tag!("times") >> + (UntilSpec::Times(num)) + ) +)); + +#[derive(Debug, PartialEq, Eq)] +pub enum UntilSpec { + Exact(ExactDate), + Times(i64) +} + +named!(pub iterator, do_parse!( + opt!(sp) >> d: date >> + opt!(sp) >> spec: iter_spec >> + opt!(sp) >> until: opt!(complete!(until_spec)) >> + (Iterator(d, spec, until)) +)); + +#[derive(Debug, PartialEq, Eq)] +pub struct Iterator(Date, Iterspec, Option); + +impl Iterator { + pub fn into_user_iterator(self) -> error::Result> { + use iter::Times; + use iter::Until; + + let unit_to_amount = |i, unit| match unit { + Unit::Second => timetype::TimeType::seconds(i), + Unit::Minute => timetype::TimeType::minutes(i), + Unit::Hour => timetype::TimeType::hours(i), + Unit::Day => timetype::TimeType::days(i), + Unit::Week => timetype::TimeType::weeks(i), + Unit::Month => timetype::TimeType::months(i), + Unit::Year => timetype::TimeType::years(i), + }; + + let recur = match self.1 { + Iterspec::Every(i, unit) => unit_to_amount(i, unit), + Iterspec::Secondly => unit_to_amount(1, Unit::Second), + Iterspec::Minutely => unit_to_amount(1, Unit::Minute), + Iterspec::Hourly => unit_to_amount(1, Unit::Hour), + Iterspec::Daily => unit_to_amount(1, Unit::Day), + Iterspec::Weekly => unit_to_amount(1, Unit::Week), + Iterspec::Monthly => unit_to_amount(1, Unit::Month), + Iterspec::Yearly => unit_to_amount(1, Unit::Year), + }; + + let into_ndt = |e: timetype::TimeType| try!(e.calculate()) + .get_moment() + .ok_or(error::KairosErrorKind::NotADateInsideIterator) + .map(Clone::clone); + + match self.2 { + Some(UntilSpec::Exact(e)) => { + let base = try!(into_ndt(self.0.into())); + let e = try!(into_ndt(e.into())); + + iter::Iter::build(base, recur) + .map(|it| UserIterator::UntilIterator(it.until(e))) + }, + + Some(UntilSpec::Times(i)) => { + let base = try!(into_ndt(self.0.into())); + iter::Iter::build(base, recur) + .map(|it| it.times(i)) + .map(UserIterator::TimesIter) + }, + + None => { + let base = try!(into_ndt(self.0.into())); + iter::Iter::build(base, recur) + .map(UserIterator::Iterator) + }, + } + } +} + +// names are hard +#[derive(Debug)] +pub enum UserIterator + where I: ::std::iter::Iterator> +{ + Iterator(iter::Iter), + TimesIter(iter::TimesIter), + UntilIterator(iter::UntilIter) +} + +impl ::std::iter::Iterator for UserIterator + where I: ::std::iter::Iterator> +{ + type Item = error::Result; + + fn next(&mut self) -> Option { + match *self { + UserIterator::Iterator(ref mut i) => i.next(), + UserIterator::TimesIter(ref mut i) => i.next(), + UserIterator::UntilIterator(ref mut i) => i.next(), + } + } +} + + +#[cfg(test)] +mod tests { + use nom::IResult; + use super::*; + + use chrono::Timelike; + use chrono::Datelike; + + #[test] + fn test_iterspec() { + assert_eq!(iter_spec(&b"secondly"[..]), IResult::Done(&b""[..], Iterspec::Secondly)); + assert_eq!(iter_spec(&b"minutely"[..]), IResult::Done(&b""[..], Iterspec::Minutely)); + assert_eq!(iter_spec(&b"hourly"[..]), IResult::Done(&b""[..], Iterspec::Hourly)); + assert_eq!(iter_spec(&b"daily"[..]), IResult::Done(&b""[..], Iterspec::Daily)); + assert_eq!(iter_spec(&b"weekly"[..]), IResult::Done(&b""[..], Iterspec::Weekly)); + assert_eq!(iter_spec(&b"monthly"[..]), IResult::Done(&b""[..], Iterspec::Monthly)); + assert_eq!(iter_spec(&b"yearly"[..]), IResult::Done(&b""[..], Iterspec::Yearly)); + assert_eq!(iter_spec(&b"every 5min"[..]), IResult::Done(&b""[..], Iterspec::Every(5, Unit::Minute))); + } + + #[test] + fn test_iterator_1() { + let res = iterator(&b"2017-01-01 hourly"[..]); + assert!(res.is_done(), format!("Not done: {:?}", res)); + let (_, i) = res.unwrap(); + println!("{:#?}", i); + + let ui : Result, _> = i.into_user_iterator(); + assert!(ui.is_ok(), "Not okay: {:#?}", ui); + let mut ui = ui.unwrap(); + + for hour in 0..10 { // 10 is randomly chosen (fair dice roll... ) + let n = ui.next().unwrap(); + assert!(n.is_ok(), "Not ok: {:#?}", n); + let tt = n.unwrap(); + assert_eq!(tt.get_moment().unwrap().year() , 2017); + assert_eq!(tt.get_moment().unwrap().month() , 01); + assert_eq!(tt.get_moment().unwrap().day() , 01); + assert_eq!(tt.get_moment().unwrap().hour() , hour); + assert_eq!(tt.get_moment().unwrap().minute(), 00); + assert_eq!(tt.get_moment().unwrap().second(), 00); + } + } + + #[test] + fn test_iterator_2() { + let res = iterator(&b"2017-01-01 every 2mins"[..]); + assert!(res.is_done(), format!("Not done: {:?}", res)); + let (_, i) = res.unwrap(); + println!("{:#?}", i); + + let ui : Result, _> = i.into_user_iterator(); + assert!(ui.is_ok(), "Not okay: {:#?}", ui); + let mut ui = ui.unwrap(); + + for min in (0..60).into_iter().filter(|n| n % 2 == 0) { + let n = ui.next().unwrap(); + assert!(n.is_ok(), "Not ok: {:#?}", n); + let tt = n.unwrap(); + assert_eq!(tt.get_moment().unwrap().year() , 2017); + assert_eq!(tt.get_moment().unwrap().month() , 01); + assert_eq!(tt.get_moment().unwrap().day() , 01); + assert_eq!(tt.get_moment().unwrap().hour() , 00); + assert_eq!(tt.get_moment().unwrap().minute(), min); + assert_eq!(tt.get_moment().unwrap().second(), 00); + } + } + + #[test] + fn test_iterator_3() { + let res = iterator(&b"2017-01-01 daily"[..]); + assert!(res.is_done(), format!("Not done: {:?}", res)); + let (_, i) = res.unwrap(); + println!("{:#?}", i); + + let ui : Result, _> = i.into_user_iterator(); + assert!(ui.is_ok(), "Not okay: {:#?}", ui); + let mut ui = ui.unwrap(); + + for day in 1..30 { + let n = ui.next().unwrap(); + assert!(n.is_ok(), "Not ok: {:#?}", n); + let tt = n.unwrap(); + assert_eq!(tt.get_moment().unwrap().year() , 2017); + assert_eq!(tt.get_moment().unwrap().month() , 01); + assert_eq!(tt.get_moment().unwrap().day() , day); + assert_eq!(tt.get_moment().unwrap().hour() , 00); + assert_eq!(tt.get_moment().unwrap().minute(), 00); + assert_eq!(tt.get_moment().unwrap().second(), 00); + } + } + + #[test] + fn test_iterator_4() { + let res = iterator(&b"2017-01-01 weekly"[..]); + assert!(res.is_done(), format!("Not done: {:?}", res)); + let (_, i) = res.unwrap(); + println!("{:#?}", i); + + let ui : Result, _> = i.into_user_iterator(); + assert!(ui.is_ok(), "Not okay: {:#?}", ui); + let mut ui = ui.unwrap(); + + for week in 0..3 { + let n = ui.next().unwrap(); + assert!(n.is_ok(), "Not ok: {:#?}", n); + let tt = n.unwrap(); + assert_eq!(tt.get_moment().unwrap().year() , 2017); + assert_eq!(tt.get_moment().unwrap().month() , 01); + assert_eq!(tt.get_moment().unwrap().day() , 01 + (week * 7)); + assert_eq!(tt.get_moment().unwrap().hour() , 00); + assert_eq!(tt.get_moment().unwrap().minute(), 00); + assert_eq!(tt.get_moment().unwrap().second(), 00); + } + } + + #[test] + fn test_until_spec_1() { + let res = until_spec(&b"until 2017-01-01T05:00:00"[..]); + assert!(res.is_done(), format!("Not done: {:?}", res)); + let (_, i) = res.unwrap(); + println!("{:#?}", i); + } + + #[test] + fn test_until_iterator_1() { + let res = iterator(&b"2017-01-01 hourly until 2017-01-01T05:00:00"[..]); + assert!(res.is_done(), format!("Not done: {:?}", res)); + let (_, i) = res.unwrap(); + println!("{:#?}", i); + + let ui : Result, _> = i.into_user_iterator(); + assert!(ui.is_ok(), "Not okay: {:#?}", ui); + let mut ui = ui.unwrap(); + println!("Okay: {:#?}", ui); + + for hour in 0..10 { // 10 is randomly chosen (fair dice roll... ) + if hour > 4 { + let n = ui.next(); + assert!(n.is_none(), "Is Some, should be None: {:?}", n); + return; + } else { + let n = ui.next().unwrap(); + assert!(n.is_ok(), "Not ok: {:#?}", n); + let tt = n.unwrap(); + assert_eq!(tt.get_moment().unwrap().year() , 2017); + assert_eq!(tt.get_moment().unwrap().month() , 01); + assert_eq!(tt.get_moment().unwrap().day() , 01); + assert_eq!(tt.get_moment().unwrap().hour() , hour); + assert_eq!(tt.get_moment().unwrap().minute(), 00); + assert_eq!(tt.get_moment().unwrap().second(), 00); + } + } + } + + #[test] + fn test_until_iterator_2() { + let res = iterator(&b"2017-01-01 every 2mins until 2017-01-01T00:10:00"[..]); + assert!(res.is_done(), format!("Not done: {:?}", res)); + let (_, i) = res.unwrap(); + println!("{:#?}", i); + + let ui : Result, _> = i.into_user_iterator(); + assert!(ui.is_ok(), "Not okay: {:#?}", ui); + let mut ui = ui.unwrap(); + + for min in (0..60).into_iter().filter(|n| n % 2 == 0) { + if min > 9 { + let n = ui.next(); + assert!(n.is_none(), "Is Some, should be None: {:?}", n); + return; + } else { + let n = ui.next().unwrap(); + assert!(n.is_ok(), "Not ok: {:#?}", n); + let tt = n.unwrap(); + assert_eq!(tt.get_moment().unwrap().year() , 2017); + assert_eq!(tt.get_moment().unwrap().month() , 01); + assert_eq!(tt.get_moment().unwrap().day() , 01); + assert_eq!(tt.get_moment().unwrap().hour() , 00); + assert_eq!(tt.get_moment().unwrap().minute(), min); + assert_eq!(tt.get_moment().unwrap().second(), 00); + } + } + } + + #[test] + fn test_until_iterator_3() { + let res = iterator(&b"2017-01-01 daily until 2017-01-05"[..]); + assert!(res.is_done(), format!("Not done: {:?}", res)); + let (_, i) = res.unwrap(); + println!("{:#?}", i); + + let ui : Result, _> = i.into_user_iterator(); + assert!(ui.is_ok(), "Not okay: {:#?}", ui); + let mut ui = ui.unwrap(); + + for day in 1..30 { + if day > 4 { + let n = ui.next(); + assert!(n.is_none(), "Is Some, should be None: {:?}", n); + return; + } else { + let n = ui.next().unwrap(); + assert!(n.is_ok(), "Not ok: {:#?}", n); + let tt = n.unwrap(); + assert_eq!(tt.get_moment().unwrap().year() , 2017); + assert_eq!(tt.get_moment().unwrap().month() , 01); + assert_eq!(tt.get_moment().unwrap().day() , day); + assert_eq!(tt.get_moment().unwrap().hour() , 00); + assert_eq!(tt.get_moment().unwrap().minute(), 00); + assert_eq!(tt.get_moment().unwrap().second(), 00); + } + } + } + + #[test] + fn test_until_iterator_4() { + let res = iterator(&b"2017-01-01 weekly until 2017-01-14"[..]); + assert!(res.is_done(), format!("Not done: {:?}", res)); + let (_, i) = res.unwrap(); + println!("{:#?}", i); + + let ui : Result, _> = i.into_user_iterator(); + assert!(ui.is_ok(), "Not okay: {:#?}", ui); + let mut ui = ui.unwrap(); + + for week in 0..3 { + if (week * 7) > 13 { + let n = ui.next(); + assert!(n.is_none(), "Is Some, should be None: {:?}", n); + return; + } else { + let n = ui.next().unwrap(); + assert!(n.is_ok(), "Not ok: {:#?}", n); + let tt = n.unwrap(); + assert_eq!(tt.get_moment().unwrap().year() , 2017); + assert_eq!(tt.get_moment().unwrap().month() , 01); + assert_eq!(tt.get_moment().unwrap().day() , 01 + (week * 7)); + assert_eq!(tt.get_moment().unwrap().hour() , 00); + assert_eq!(tt.get_moment().unwrap().minute(), 00); + assert_eq!(tt.get_moment().unwrap().second(), 00); + } + } + } +} + diff --git a/src/parser/mod.rs b/src/parser/mod.rs new file mode 100644 index 0000000..3c01903 --- /dev/null +++ b/src/parser/mod.rs @@ -0,0 +1,77 @@ +//! The definition of the "kairos" syntax, for parsing user input into TimeType objects +//! +//! The syntax itself is described in the grammar.rustpeg file. +//! Here goes a documentation on the syntax +//! +//! # Syntax +//! +//! ## Units +//! +//! UnitSec = "second" | "seconds" | "sec" | "secs" | "s" +//! UnitMin = "minute" | "minutes" | "min" | "mins" +//! UnitHr = "hour" | "hours" | "hr" | "hrs" +//! UnitDay = "day" | "days" | "d" +//! UnitWeek = "week" | "weeks" | "w" +//! UnitMonth = "month" | "months" | +//! UnitYear = "year" | "years" | "yrs" +//! Unit = UnitSec | UnitMin | UnitHr | UnitDay | UnitWeek | UnitMonth | UnitYear +//! +//! ## Operators +//! +//! Operator = "+" | "-" +//! +//! ## Intermediate syntax nodes +//! +//! Amount = "" +//! +//! TextIterSpec = "secondly" | "minutely" | "hourly" | "daily" | "weekly" | "monthly" | "yearly" +//! Iterspec = TextIterSpec | "every" +//! +//! ## User-facing syntax nodes +//! +//! AmountExpr = ( )? +//! ExactDate = "today" | "yesterday" | "tomorrow" | +//! Date = ( )? +//! Iterator = ("until" | "times")? +//! +//! # Warning +//! +//! This module is not intended for public use... it is still public, so you can use it, but you +//! should know that these interfaces are considered private and I will not follow semver and +//! update the minor or major semver numbers of the interface of this module changes. +//! +//! Be warned! +//! + +use nom::Needed; +use nom::IResult; + +mod timetype; +mod iterator; + +use error::Result; +use error::KairosErrorKind as KEK; +use iter::Iter; +use parser::timetype::timetype; +use parser::iterator::iterator; + +pub enum Parsed { + Iterator(Result<::parser::iterator::UserIterator>), + TimeType(::timetype::TimeType) +} + +named!(do_parse, alt_complete!( + do_parse!(it: iterator >> (Parsed::Iterator(it.into_user_iterator()))) | + do_parse!(tt: timetype >> (Parsed::TimeType(tt.into()))) +)); + +pub fn parse(s: &str) -> Result { + match do_parse(s.as_bytes()) { + IResult::Done(_, o) => Ok(o), + IResult::Error(e) => Err(e).map_err(From::from), + IResult::Incomplete(Needed::Unknown) => Err(KEK::UnknownParserError.into()), + IResult::Incomplete(Needed::Size(s)) => Err(KEK::UnknownParserError.into()), + + } +} + diff --git a/src/parser/timetype.rs b/src/parser/timetype.rs new file mode 100644 index 0000000..bf2422a --- /dev/null +++ b/src/parser/timetype.rs @@ -0,0 +1,583 @@ +use std::str; +use std::str::FromStr; + +use nom::{IResult, space, alpha, alphanumeric, digit}; +use nom::whitespace::sp; +use chrono::NaiveDate; + +use timetype; +use iter; +use error; + +named!(pub integer, alt!( + map_res!( + map_res!( + ws!(digit), + str::from_utf8 + ), + FromStr::from_str + ) +)); + +// WARNING: Order is important here. Long tags first, shorter tags later +named!(pub unit_parser, alt_complete!( + tag!("seconds") => { |_| Unit::Second } | + tag!("second") => { |_| Unit::Second } | + tag!("secs") => { |_| Unit::Second } | + tag!("sec") => { |_| Unit::Second } | + tag!("s") => { |_| Unit::Second } | + tag!("minutes") => { |_| Unit::Minute } | + tag!("minute") => { |_| Unit::Minute } | + tag!("mins") => { |_| Unit::Minute } | + tag!("min") => { |_| Unit::Minute } | + tag!("hours") => { |_| Unit::Hour } | + tag!("hour") => { |_| Unit::Hour } | + tag!("hrs") => { |_| Unit::Hour } | + tag!("hr") => { |_| Unit::Hour } | + tag!("days") => { |_| Unit::Day } | + tag!("day") => { |_| Unit::Day } | + tag!("d") => { |_| Unit::Day } | + tag!("weeks") => { |_| Unit::Week } | + tag!("week") => { |_| Unit::Week } | + tag!("w") => { |_| Unit::Week } | + tag!("months") => { |_| Unit::Month } | + tag!("month") => { |_| Unit::Month } | + tag!("years") => { |_| Unit::Year } | + tag!("year") => { |_| Unit::Year } | + tag!("yrs") => { |_| Unit::Year } +)); + +#[derive(Debug, PartialEq, Eq)] +pub enum Unit { + Second, + Minute, + Hour, + Day, + Week, + Month, + Year, +} + +named!(pub operator_parser, alt!( + tag!("+") => { |_| Operator::Plus } | + tag!("-") => { |_| Operator::Minus } +)); + +#[derive(Debug, PartialEq, Eq)] +pub enum Operator { + Plus, + Minus, +} + +named!(pub amount_parser, do_parse!( + number: integer >> + unit : unit_parser >> + (Amount(number, unit)) +)); + +#[derive(Debug, PartialEq, Eq)] +pub struct Amount(i64, Unit); + +impl Into for Amount { + fn into(self) -> timetype::TimeType { + match self.1 { + Unit::Second => timetype::TimeType::seconds(self.0), + Unit::Minute => timetype::TimeType::minutes(self.0), + Unit::Hour => timetype::TimeType::hours(self.0), + Unit::Day => timetype::TimeType::days(self.0), + Unit::Week => timetype::TimeType::weeks(self.0), + Unit::Month => timetype::TimeType::months(self.0), + Unit::Year => timetype::TimeType::years(self.0), + } + } +} + +named!(pub amount_expr_next<(Operator, Box)>, do_parse!( + op:operator_parser + >> opt!(sp) + >> amexp:amount_expr + >> ((op, Box::new(amexp))) +)); + +named!(pub amount_expr, do_parse!( + amount:amount_parser >> + opt!(sp) >> + o: opt!(complete!(amount_expr_next)) >> + (AmountExpr { amount: amount, next: o, }) +)); + +#[derive(Debug, PartialEq, Eq)] +pub struct AmountExpr { + amount: Amount, + next: Option<(Operator, Box)>, +} + +impl Into for AmountExpr { + fn into(self) -> timetype::TimeType { + let mut amount = self.amount.into(); + + if let Some((op, other_amonut_expr)) = self.next { + match op { + Operator::Plus => { + amount = amount + (*other_amonut_expr).into(); + }, + Operator::Minus => { + amount = amount - (*other_amonut_expr).into(); + }, + } + } + + amount + } +} + +impl AmountExpr { + fn new(amount: Amount, next: Option<(Operator, Box)>) -> AmountExpr { + AmountExpr { + amount: amount, + next: next + } + } +} + +use iso8601::parsers::parse_date; +use iso8601::parsers::parse_datetime; +// The order is relevant here, because datetime is longer than date, we must parse datetime before +// date. +named!(pub exact_date_parser, alt_complete!( + tag!("today") => { |_| ExactDate::Today } | + tag!("yesterday") => { |_| ExactDate::Yesterday } | + tag!("tomorrow") => { |_| ExactDate::Tomorrow } | + do_parse!(d: parse_datetime >> (ExactDate::Iso8601DateTime(d))) | + do_parse!(d: parse_date >> (ExactDate::Iso8601Date(d))) +)); + +#[derive(Debug, PartialEq, Eq)] +pub enum ExactDate { + Today, + Yesterday, + Tomorrow, + Iso8601Date(::iso8601::Date), + Iso8601DateTime(::iso8601::DateTime) +} + +impl Into for ExactDate { + fn into(self) -> timetype::TimeType { + match self { + ExactDate::Today => timetype::TimeType::today(), + ExactDate::Yesterday => timetype::TimeType::today() - timetype::TimeType::days(1), + ExactDate::Tomorrow => timetype::TimeType::today() + timetype::TimeType::days(1), + ExactDate::Iso8601Date(date) => { + let (year, month, day) = match date { + ::iso8601::Date::YMD { year, month, day } => { + (year, month, day) + }, + ::iso8601::Date::Week { year, ww, d } => { + unimplemented!() + }, + ::iso8601::Date::Ordinal { year, ddd } => { + unimplemented!() + }, + }; + + let ndt = NaiveDate::from_ymd(year, month, day).and_hms(0, 0, 0); + timetype::TimeType::moment(ndt) + }, + ExactDate::Iso8601DateTime(::iso8601::DateTime { date, time }) => { + let (hour, minute, second) = (time.hour, time.minute, time.second); + let (year, month, day) = match date { + ::iso8601::Date::YMD { year, month, day } => { + (year, month, day) + }, + ::iso8601::Date::Week { year, ww, d } => { + unimplemented!() + }, + ::iso8601::Date::Ordinal { year, ddd } => { + unimplemented!() + }, + }; + + let ndt = NaiveDate::from_ymd(year, month, day).and_hms(hour, minute, second); + timetype::TimeType::moment(ndt) + }, + } + } +} + +named!(pub date, do_parse!( + exact: exact_date_parser >> + o: opt!( + complete!(do_parse!(sp >> op:operator_parser >> sp >> a:amount_expr >> (op, a))) + ) >> + (Date(exact, o)) +)); + +#[derive(Debug, PartialEq, Eq)] +pub struct Date(ExactDate, Option<(Operator, AmountExpr)>); + +impl Into for Date { + fn into(self) -> timetype::TimeType { + let base : timetype::TimeType = self.0.into(); + match self.1 { + Some((Operator::Plus, amount)) => base + amount.into(), + Some((Operator::Minus, amount)) => base - amount.into(), + None => base, + } + } +} + +#[derive(Debug, PartialEq, Eq)] +pub enum TimeType { + Date(Date), + AmountExpr(AmountExpr), +} + +impl Into for TimeType { + fn into(self) -> timetype::TimeType { + match self { + TimeType::Date(d) => d.into(), + TimeType::AmountExpr(a) => a.into(), + } + } +} + + +/// Main entry function for timetype parser +/// +/// # Notice +/// +/// Note that this function returns a parser::TimeType, not a timetype::TimeType. Though, the +/// parser::TimeType can be `Into::into()`ed. +/// +named!(pub timetype, alt!( + do_parse!(d: date >> (TimeType::Date(d))) | + do_parse!(a: amount_expr >> (TimeType::AmountExpr(a))) +)); + +#[cfg(test)] +mod tests { + use nom::IResult; + use super::*; + + use chrono::Timelike; + use chrono::Datelike; + + #[test] + fn test_integer() { + assert_eq!(integer(&b"2"[..]), IResult::Done(&b""[..], 2)); + assert_eq!(integer(&b"217"[..]), IResult::Done(&b""[..], 217)); + } + + #[test] + fn test_unit() { + assert_eq!(unit_parser(&b"second"[..]), IResult::Done(&b""[..], Unit::Second)); + assert_eq!(unit_parser(&b"seconds"[..]), IResult::Done(&b""[..], Unit::Second)); + assert_eq!(unit_parser(&b"sec"[..]), IResult::Done(&b""[..], Unit::Second)); + assert_eq!(unit_parser(&b"secs"[..]), IResult::Done(&b""[..], Unit::Second)); + assert_eq!(unit_parser(&b"s"[..]), IResult::Done(&b""[..], Unit::Second)); + assert_eq!(unit_parser(&b"minute"[..]), IResult::Done(&b""[..], Unit::Minute)); + assert_eq!(unit_parser(&b"minutes"[..]), IResult::Done(&b""[..], Unit::Minute)); + assert_eq!(unit_parser(&b"min"[..]), IResult::Done(&b""[..], Unit::Minute)); + assert_eq!(unit_parser(&b"mins"[..]), IResult::Done(&b""[..], Unit::Minute)); + assert_eq!(unit_parser(&b"hour"[..]), IResult::Done(&b""[..], Unit::Hour)); + assert_eq!(unit_parser(&b"hours"[..]), IResult::Done(&b""[..], Unit::Hour)); + assert_eq!(unit_parser(&b"hr"[..]), IResult::Done(&b""[..], Unit::Hour)); + assert_eq!(unit_parser(&b"hrs"[..]), IResult::Done(&b""[..], Unit::Hour)); + assert_eq!(unit_parser(&b"day"[..]), IRes