summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatthias Beyer <mail@beyermatthias.de>2017-11-11 03:47:46 +0100
committerGitHub <noreply@github.com>2017-11-11 03:47:46 +0100
commit76050160d43239aee29a4f34ce161bb305ee36fc (patch)
tree7ac0563bbd255d62a0622d01cbe83bf37b966b4a
parentf707177fd0897a701c907ec4b0a036982a9a7995 (diff)
parent2053d956af43d26238f7be9ae6acf0d60b85acad (diff)
Merge pull request #3 from matthiasbeyer/parser
Parser
-rw-r--r--Cargo.toml2
-rw-r--r--examples/main.rs20
-rw-r--r--src/error.rs6
-rw-r--r--src/lib.rs5
-rw-r--r--src/parser.rs645
-rw-r--r--src/timetype.rs9
6 files changed, 687 insertions, 0 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 5752988..c0a4c1a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -15,6 +15,8 @@ repository = "https://github.com/matthiasbeyer/kairos"
[dependencies]
chrono = "0.4"
error-chain = "0.10"
+nom = "3.2"
+iso8601 = { git = "https://github.com/matthiasbeyer/iso8601", branch = "update-nom" }
filters = { version = "0.1.1", optional = true }
diff --git a/examples/main.rs b/examples/main.rs
new file mode 100644
index 0000000..920accc
--- /dev/null
+++ b/examples/main.rs
@@ -0,0 +1,20 @@
+extern crate kairos;
+
+fn main() {
+ // not sure whether this is actually fast or something, but we don't care here, do we?
+ let s = ::std::env::args().skip(1).fold(String::new(), |acc, obj| format!("{} {}", acc, obj));
+ let s = s.trim(); // because kairos is not yet whitespace tolerant
+
+ println!("Parsing: '{}'", s);
+ match kairos::timetype::TimeType::parse(&s) {
+ Ok(tt) => {
+ println!("{:?}", tt);
+
+ match tt.calculate() {
+ Ok(r) => println!("{:?}", r),
+ Err(e) => println!("Error calculating: {:?}", e),
+ }
+ },
+ Err(e) => println!("Error -> {:?}", e),
+ }
+}
diff --git a/src/error.rs b/src/error.rs
index 39d482f..653b368 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -9,6 +9,7 @@ error_chain! {
}
foreign_links {
+ NomError(::nom::simple_errors::Err);
}
errors {
@@ -58,6 +59,11 @@ error_chain! {
display("Cannot compare Month to non-Moment TimeType: {:?}", tt_rep)
}
+ UnknownParserError {
+ description("Unknown parser error")
+ display("Unknown parser error")
+ }
+
}
}
diff --git a/src/lib.rs b/src/lib.rs
index 7aeab51..d39493a 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -2,6 +2,10 @@
extern crate error_chain;
extern crate chrono;
+#[macro_use]
+extern crate nom;
+extern crate iso8601;
+
#[cfg(feature = "with-filters")]
extern crate filters;
@@ -17,5 +21,6 @@ pub mod iter;
pub mod timetype;
pub mod indicator;
pub mod matcher;
+pub mod parser;
mod util;
diff --git a/src/parser.rs b/src/parser.rs
new file mode 100644
index 0000000..2cbf046
--- /dev/null
+++ b/src/parser.rs
@@ -0,0 +1,645 @@
+//! 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 = "<Number><Unit>"
+//!
+//! TextIterSpec = "secondly" | "minutely" | "hourly" | "daily" | "weekly" | "monthly" | "yearly"
+//! Iterspec = TextIterSpec | "every" <Number><Unit>
+//!
+//! ## User-facing syntax nodes
+//!
+//! AmountExpr = <Amount> (<Operator> <AmountExpr>)?
+//! ExactDate = "today" | "yesterday" | "tomorrow" | <Iso8601>
+//! Date = <ExactDate> (<Operator> <AmountExpr>)?
+//! Iterator = <Date> <Iterspec> ("until" <ExactDate> | <number> "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;
+
+named!(integer<i64>, 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<Unit>, 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<Operator>, alt!(
+ tag!("+") => { |_| Operator::Plus } |
+ tag!("-") => { |_| Operator::Minus }
+));
+
+#[derive(Debug, PartialEq, Eq)]
+pub enum Operator {
+ Plus,
+ Minus,
+}
+
+named!(amount_parser<Amount>, do_parse!(
+ number: integer >>
+ unit : unit_parser >>
+ (Amount(number, unit))
+));
+
+#[derive(Debug, PartialEq, Eq)]
+pub struct Amount(i64, Unit);
+
+impl Into<timetype::TimeType> 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<Iterspec>, 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<AmountExpr>)>, do_parse!(
+ op:operator_parser
+ >> opt!(sp)
+ >> amexp:amount_expr
+ >> ((op, Box::new(amexp)))
+));
+
+named!(amount_expr<AmountExpr>, 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<AmountExpr>)>,
+}
+
+impl Into<timetype::TimeType> 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 {
+ 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<ExactDate>, 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<timetype::TimeType> 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<Date>, do_parse!(
+ exact:exact_date_parser >>
+ o: opt!(do_parse!(sp >> op:operator_parser >> a:amount_expr >> (op, a))) >>
+ (Date(exact, o))
+));
+
+#[derive(Debug, PartialEq, Eq)]
+pub struct Date(ExactDate, Option<(Operator, AmountExpr)>);
+
+impl Into<timetype::TimeType> 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<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<timetype::TimeType> for TimeType {
+ fn into(self) -> timetype::TimeType {
+ match self {
+ TimeType::Date(d) => d.into(),
+ TimeType::AmountExpr(a) => a.into(),
+ }
+ }
+}
+
+named!(until_spec<UntilSpec>, alt!(
+ do_parse!(
+ tag!("until") >>
+ exact: exact_date_parser >>
+ (UntilSpec::Exact(exact))
+ ) |
+ do_parse!(
+ num: integer >>
+ tag!("times") >>
+ (UntilSpec::Times(num))
+ )
+));
+
+#[derive(Debug, PartialEq, Eq)]
+pub enum UntilSpec {
+ Exact(ExactDate),
+ Times(i64)
+}
+
+named!(iterator<Iterator>, do_parse!(
+ d: date >>
+ spec: iter_spec >>
+ until: opt!(until_spec) >>
+ (Iterator(d, spec, until))
+));
+
+#[derive(Debug, PartialEq, Eq)]
+pub struct Iterator(Date, Iterspec, Option<UntilSpec>);
+
+impl Into<timetype::TimeType> for Iterator {
+ fn into(self) -> timetype::TimeType {
+ unimplemented!()
+ }
+}
+
+#[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_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);
+ }
+
+}
+
diff --git a/src/timetype.rs b/src/timetype.rs
index 8d17abd..eb840d3 100644
--- a/src/timetype.rs
+++ b/src/timetype.rs
@@ -435,6 +435,15 @@ impl TimeType {
pub fn calculate(self) -> Result<TimeType> {
do_calculate(self)
}
+
+ pub fn parse(s: &str) -> Result<TimeType> {
+ use parser;
+ match parser::timetype(s.as_bytes()) {
+ ::nom::IResult::Done(_, o) => Ok(o.into()),
+ ::nom::IResult::Error(e) => Err(KE::from(e)),
+ ::nom::IResult::Incomplete(_) => Err(KEK::UnknownParserError.into()),
+ }
+ }
}
fn do_calculate(tt: TimeType) -> Result<TimeType> {