diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 18fc23e..d9a5430 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -16,6 +16,7 @@ mod timestamp; pub use generated::*; pub use rowan::ast::support::*; +pub use timestamp::*; use crate::syntax::{SyntaxKind, SyntaxNode}; use rowan::{ast::AstNode, Language, NodeOrToken}; diff --git a/src/ast/timestamp.rs b/src/ast/timestamp.rs index d2c404f..d706945 100644 --- a/src/ast/timestamp.rs +++ b/src/ast/timestamp.rs @@ -1,6 +1,30 @@ +use rowan::NodeOrToken; + use super::{filter_token, Timestamp}; use crate::syntax::SyntaxKind; +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum TimeUnit { + Hour, + Day, + Week, + Month, + Year, +} + +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum RepeaterType { + Cumulate, + CatchUp, + Restart, +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum DelayType { + All, + First, +} + impl Timestamp { /// ```rust /// use orgize::{Org, ast::Timestamp}; @@ -60,6 +84,165 @@ impl Timestamp { > 2 } + /// ```rust + /// use orgize::{Org, ast::{Timestamp, RepeaterType}}; + /// + /// let t = Org::parse("[2000-01-01 +1w]").first_node::().unwrap(); + /// assert_eq!(t.repeater_type(), Some(RepeaterType::Cumulate)); + /// let t = Org::parse("[2000-01-01 .+10d +1w]").first_node::().unwrap(); + /// assert_eq!(t.repeater_type(), Some(RepeaterType::Restart)); + /// let t = Org::parse("[2000-01-01 --1y]").first_node::().unwrap(); + /// assert_eq!(t.repeater_type(), None); + /// ``` + pub fn repeater_type(&self) -> Option { + self.syntax + .children_with_tokens() + .find_map(filter_token(SyntaxKind::TIMESTAMP_REPEATER_MARK)) + .map(|t| match t.text() { + "++" => RepeaterType::CatchUp, + "+" => RepeaterType::Cumulate, + ".+" => RepeaterType::Restart, + _ => { + debug_assert!(false); + RepeaterType::CatchUp + } + }) + } + + /// ```rust + /// use orgize::{Org, ast::Timestamp}; + /// + /// let t = Org::parse("[2000-01-01 +1w]").first_node::().unwrap(); + /// assert_eq!(t.repeater_value(), Some(1)); + /// let t = Org::parse("[2000-01-01 .+10d +1w]").first_node::().unwrap(); + /// assert_eq!(t.repeater_value(), Some(10)); + /// let t = Org::parse("[2000-01-01 --1y]").first_node::().unwrap(); + /// assert_eq!(t.repeater_value(), None); + /// ``` + pub fn repeater_value(&self) -> Option { + self.syntax + .children_with_tokens() + .skip_while(|n| n.kind() != SyntaxKind::TIMESTAMP_REPEATER_MARK) + .nth(1) + .and_then(|e| match e { + NodeOrToken::Token(t) => { + debug_assert!(t.kind() == SyntaxKind::TIMESTAMP_VALUE); + t.text().parse().ok() + } + _ => None, + }) + } + + /// ```rust + /// use orgize::{Org, ast::{Timestamp, TimeUnit}}; + /// + /// let t = Org::parse("[2000-01-01 +1w]").first_node::().unwrap(); + /// assert_eq!(t.repeater_unit(), Some(TimeUnit::Week)); + /// let t = Org::parse("[2000-01-01 .+10d +1w]").first_node::().unwrap(); + /// assert_eq!(t.repeater_unit(), Some(TimeUnit::Day)); + /// let t = Org::parse("[2000-01-01 --1y]").first_node::().unwrap(); + /// assert_eq!(t.repeater_unit(), None); + /// ``` + pub fn repeater_unit(&self) -> Option { + self.syntax + .children_with_tokens() + .skip_while(|n| n.kind() != SyntaxKind::TIMESTAMP_REPEATER_MARK) + .nth(2) + .and_then(|e| match e { + NodeOrToken::Token(t) => { + debug_assert!(t.kind() == SyntaxKind::TIMESTAMP_UNIT); + match t.text() { + "h" => Some(TimeUnit::Hour), + "d" => Some(TimeUnit::Day), + "w" => Some(TimeUnit::Week), + "m" => Some(TimeUnit::Month), + "y" => Some(TimeUnit::Year), + _ => None, + } + } + _ => None, + }) + } + + /// ```rust + /// use orgize::{Org, ast::{Timestamp, DelayType}}; + /// + /// let t = Org::parse("[2000-01-01 -3y]").first_node::().unwrap(); + /// assert_eq!(t.warning_type(), Some(DelayType::All)); + /// let t = Org::parse("[2000-01-01]--[2000-01-02 -5w]").first_node::().unwrap(); + /// assert_eq!(t.warning_type(), Some(DelayType::All)); + /// let t = Org::parse("[2000-01-01 01:00-02:00 --10m]").first_node::().unwrap(); + /// assert_eq!(t.warning_type(), Some(DelayType::First)); + /// ``` + pub fn warning_type(&self) -> Option { + self.syntax + .children_with_tokens() + .find_map(filter_token(SyntaxKind::TIMESTAMP_DELAY_MARK)) + .map(|t| match t.text() { + "-" => DelayType::All, + "--" => DelayType::First, + _ => { + debug_assert!(false); + DelayType::All + } + }) + } + + /// ```rust + /// use orgize::{Org, ast::Timestamp}; + /// + /// let t = Org::parse("[2000-01-01 -3y]").first_node::().unwrap(); + /// assert_eq!(t.warning_value(), Some(3)); + /// let t = Org::parse("[2000-01-01]--[2000-01-02 -5w]").first_node::().unwrap(); + /// assert_eq!(t.warning_value(), Some(5)); + /// let t = Org::parse("[2000-01-01 01:00-02:00 --10m]").first_node::().unwrap(); + /// assert_eq!(t.warning_value(), Some(10)); + /// ``` + pub fn warning_value(&self) -> Option { + self.syntax + .children_with_tokens() + .skip_while(|n| n.kind() != SyntaxKind::TIMESTAMP_DELAY_MARK) + .nth(1) + .and_then(|e| match e { + NodeOrToken::Token(t) => { + debug_assert!(t.kind() == SyntaxKind::TIMESTAMP_VALUE); + t.text().parse().ok() + } + _ => None, + }) + } + + /// ```rust + /// use orgize::{Org, ast::{Timestamp, TimeUnit}}; + /// + /// let t = Org::parse("[2000-01-01 -3y]").first_node::().unwrap(); + /// assert_eq!(t.warning_unit(), Some(TimeUnit::Year)); + /// let t = Org::parse("[2000-01-01]--[2000-01-02 -5w]").first_node::().unwrap(); + /// assert_eq!(t.warning_unit(), Some(TimeUnit::Week)); + /// let t = Org::parse("[2000-01-01 01:00-02:00 --10m]").first_node::().unwrap(); + /// assert_eq!(t.warning_unit(), Some(TimeUnit::Month)); + /// ``` + pub fn warning_unit(&self) -> Option { + self.syntax + .children_with_tokens() + .skip_while(|n| n.kind() != SyntaxKind::TIMESTAMP_DELAY_MARK) + .nth(2) + .and_then(|e| match e { + NodeOrToken::Token(t) => { + debug_assert!(t.kind() == SyntaxKind::TIMESTAMP_UNIT); + match t.text() { + "h" => Some(TimeUnit::Hour), + "d" => Some(TimeUnit::Day), + "w" => Some(TimeUnit::Week), + "m" => Some(TimeUnit::Month), + "y" => Some(TimeUnit::Year), + _ => None, + } + } + _ => None, + }) + } + /// Converts timestamp start to chrono NaiveDateTime /// /// ```rust diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs index f05da2f..c2c6084 100644 --- a/src/syntax/mod.rs +++ b/src/syntax/mod.rs @@ -203,12 +203,18 @@ pub enum SyntaxKind { TIMESTAMP_ACTIVE, TIMESTAMP_INACTIVE, TIMESTAMP_DIARY, + // timestamp tokens TIMESTAMP_YEAR, TIMESTAMP_MONTH, TIMESTAMP_DAY, TIMESTAMP_HOUR, TIMESTAMP_MINUTE, TIMESTAMP_DAYNAME, + // for repeater or delay + TIMESTAMP_REPEATER_MARK, + TIMESTAMP_DELAY_MARK, + TIMESTAMP_VALUE, + TIMESTAMP_UNIT, } impl From for rowan::SyntaxKind { diff --git a/src/syntax/timestamp.rs b/src/syntax/timestamp.rs index 7626153..110658b 100644 --- a/src/syntax/timestamp.rs +++ b/src/syntax/timestamp.rs @@ -1,7 +1,8 @@ use nom::{ - bytes::complete::{take_till, take_while1, take_while_m_n}, - character::complete::{space0, space1}, - combinator::{map, opt}, + branch::alt, + bytes::complete::{tag, take_till, take_while1, take_while_m_n}, + character::complete::{digit1, space0, space1}, + combinator::{iterator, map, opt}, sequence::tuple, IResult, }; @@ -74,6 +75,7 @@ fn dayname(i: Input) -> IResult { && c != '-' && c != ']' && c != '>' + && c != '.' }), |i: Input| i.token(TIMESTAMP_DAYNAME), )(i) @@ -96,6 +98,30 @@ fn time(i: Input) -> IResult { )(i) } +fn repeater_or_delay( + input: Input, +) -> IResult { + let (input, mark) = alt(( + map(alt((tag("++"), tag("+"), tag(".+"))), |i: Input| { + i.token(TIMESTAMP_REPEATER_MARK) + }), + map(alt((tag("--"), tag("-"))), |i: Input| { + i.token(TIMESTAMP_DELAY_MARK) + }), + ))(input)?; + let (input, value) = digit1(input)?; + let (input, unit) = alt((tag("h"), tag("d"), tag("w"), tag("m"), tag("y")))(input)?; + + Ok(( + input, + ( + mark, + value.token(TIMESTAMP_VALUE), + unit.token(TIMESTAMP_UNIT), + ), + )) +} + fn timestamp_node_base( input: Input, l_parser: impl Fn(Input) -> IResult, @@ -123,28 +149,41 @@ fn timestamp_node_base( let (input, minus) = minus_token(input)?; let (input, end_time) = time(input)?; - let (input, space) = space0(input)?; - // TODO: delay-or-repeater - let (input, r_angle) = r_parser(input)?; b.ws(ws); b.children.extend(start_time); b.push(minus); b.children.extend(end_time); + + let mut iter = iterator(input, tuple((space1, repeater_or_delay))); + for (ws, (mark, value, unit)) in &mut iter { + b.children.extend([ws.ws_token(), mark, value, unit]); + } + let (input, _) = iter.finish()?; + + let (input, space) = space0(input)?; + let (input, r_angle) = r_parser(input)?; + b.ws(space); b.push(r_angle); return Ok((input, b.children)); } - let (input, space) = space0(input)?; - let (input, r_angle) = r_parser(input)?; - if let Some((ws, start_time)) = start_time { b.ws(ws); b.children.extend(start_time); } + let mut iter = iterator(input, tuple((space1, repeater_or_delay))); + for (ws, (mark, value, unit)) in &mut iter { + b.children.extend([ws.ws_token(), mark, value, unit]); + } + let (input, _) = iter.finish()?; + + let (input, space) = space0(input)?; + let (input, r_angle) = r_parser(input)?; + b.ws(space); b.push(r_angle); @@ -154,9 +193,6 @@ fn timestamp_node_base( let (input, end_date) = date(input)?; let (input, end_dayname) = opt(tuple((space1, dayname)))(input)?; let (input, end_time) = opt(tuple((space1, time)))(input)?; - let (input, space_) = space0(input)?; - // TODO: delay-or-repeater - let (input, r_angle) = r_parser(input)?; b.children.extend([minus2, l_angle]); b.children.extend(end_date); @@ -168,6 +204,14 @@ fn timestamp_node_base( b.ws(ws); b.children.extend(end_time); } + let mut iter = iterator(input, tuple((space1, repeater_or_delay))); + for (ws, (mark, value, unit)) in &mut iter { + b.children.extend([ws.ws_token(), mark, value, unit]); + } + let (input, _) = iter.finish()?; + + let (input, space_) = space0(input)?; + let (input, r_angle) = r_parser(input)?; b.ws(space_); b.push(r_angle); @@ -212,14 +256,18 @@ fn parse() { to_timestamp("[2003-09-16 Tue]--[2003-09-16 Tue]"); to_timestamp("[2003-09-16 Tue 09:09]--[2003-09-16 Tue 09:09]"); to_timestamp("[2003-09-16 Tue 09:09-09:09]"); - to_timestamp("[2003-09-16 09:09-09:09]"); + to_timestamp("[2003-09-16 09:09-09:09 ]"); + to_timestamp("[2003-09-16 09:09 +1w .+1d]"); + to_timestamp("[2003-09-16 09:09]--[2003-09-16 +1w .+1d --1d ]"); + to_timestamp("[2003-09-16 Tue 09:09 +1w]--[2003-09-16 .+1d --1d ]"); + to_timestamp("[2003-09-16 09:09-10:19 +1w --1d]"); - let ts = to_timestamp("[2003-09-16 Tue]"); + let ts = to_timestamp("[2003-09-16 Tue +1w]"); assert!(!ts.is_range()); insta::assert_debug_snapshot!( ts.syntax, @r###" - TIMESTAMP_INACTIVE@0..16 + TIMESTAMP_INACTIVE@0..20 L_BRACKET@0..1 "[" TIMESTAMP_YEAR@1..5 "2003" MINUS@5..6 "-" @@ -228,7 +276,11 @@ fn parse() { TIMESTAMP_DAY@9..11 "16" WHITESPACE@11..12 " " TIMESTAMP_DAYNAME@12..15 "Tue" - R_BRACKET@15..16 "]" + WHITESPACE@15..16 " " + TIMESTAMP_REPEATER_MARK@16..17 "+" + TIMESTAMP_VALUE@17..18 "1" + TIMESTAMP_UNIT@18..19 "w" + R_BRACKET@19..20 "]" "### );