feat: support repeater/delay parsing
This commit is contained in:
parent
a0fdf1d5c8
commit
c49f7e5716
4 changed files with 258 additions and 16 deletions
|
|
@ -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};
|
||||
|
|
|
|||
|
|
@ -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::<Timestamp>().unwrap();
|
||||
/// assert_eq!(t.repeater_type(), Some(RepeaterType::Cumulate));
|
||||
/// let t = Org::parse("[2000-01-01 .+10d +1w]").first_node::<Timestamp>().unwrap();
|
||||
/// assert_eq!(t.repeater_type(), Some(RepeaterType::Restart));
|
||||
/// let t = Org::parse("[2000-01-01 --1y]").first_node::<Timestamp>().unwrap();
|
||||
/// assert_eq!(t.repeater_type(), None);
|
||||
/// ```
|
||||
pub fn repeater_type(&self) -> Option<RepeaterType> {
|
||||
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::<Timestamp>().unwrap();
|
||||
/// assert_eq!(t.repeater_value(), Some(1));
|
||||
/// let t = Org::parse("[2000-01-01 .+10d +1w]").first_node::<Timestamp>().unwrap();
|
||||
/// assert_eq!(t.repeater_value(), Some(10));
|
||||
/// let t = Org::parse("[2000-01-01 --1y]").first_node::<Timestamp>().unwrap();
|
||||
/// assert_eq!(t.repeater_value(), None);
|
||||
/// ```
|
||||
pub fn repeater_value(&self) -> Option<u32> {
|
||||
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::<Timestamp>().unwrap();
|
||||
/// assert_eq!(t.repeater_unit(), Some(TimeUnit::Week));
|
||||
/// let t = Org::parse("[2000-01-01 .+10d +1w]").first_node::<Timestamp>().unwrap();
|
||||
/// assert_eq!(t.repeater_unit(), Some(TimeUnit::Day));
|
||||
/// let t = Org::parse("[2000-01-01 --1y]").first_node::<Timestamp>().unwrap();
|
||||
/// assert_eq!(t.repeater_unit(), None);
|
||||
/// ```
|
||||
pub fn repeater_unit(&self) -> Option<TimeUnit> {
|
||||
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::<Timestamp>().unwrap();
|
||||
/// assert_eq!(t.warning_type(), Some(DelayType::All));
|
||||
/// let t = Org::parse("[2000-01-01]--[2000-01-02 -5w]").first_node::<Timestamp>().unwrap();
|
||||
/// assert_eq!(t.warning_type(), Some(DelayType::All));
|
||||
/// let t = Org::parse("[2000-01-01 01:00-02:00 --10m]").first_node::<Timestamp>().unwrap();
|
||||
/// assert_eq!(t.warning_type(), Some(DelayType::First));
|
||||
/// ```
|
||||
pub fn warning_type(&self) -> Option<DelayType> {
|
||||
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::<Timestamp>().unwrap();
|
||||
/// assert_eq!(t.warning_value(), Some(3));
|
||||
/// let t = Org::parse("[2000-01-01]--[2000-01-02 -5w]").first_node::<Timestamp>().unwrap();
|
||||
/// assert_eq!(t.warning_value(), Some(5));
|
||||
/// let t = Org::parse("[2000-01-01 01:00-02:00 --10m]").first_node::<Timestamp>().unwrap();
|
||||
/// assert_eq!(t.warning_value(), Some(10));
|
||||
/// ```
|
||||
pub fn warning_value(&self) -> Option<u32> {
|
||||
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::<Timestamp>().unwrap();
|
||||
/// assert_eq!(t.warning_unit(), Some(TimeUnit::Year));
|
||||
/// let t = Org::parse("[2000-01-01]--[2000-01-02 -5w]").first_node::<Timestamp>().unwrap();
|
||||
/// assert_eq!(t.warning_unit(), Some(TimeUnit::Week));
|
||||
/// let t = Org::parse("[2000-01-01 01:00-02:00 --10m]").first_node::<Timestamp>().unwrap();
|
||||
/// assert_eq!(t.warning_unit(), Some(TimeUnit::Month));
|
||||
/// ```
|
||||
pub fn warning_unit(&self) -> Option<TimeUnit> {
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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<SyntaxKind> for rowan::SyntaxKind {
|
||||
|
|
|
|||
|
|
@ -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<Input, GreenElement, ()> {
|
|||
&& c != '-'
|
||||
&& c != ']'
|
||||
&& c != '>'
|
||||
&& c != '.'
|
||||
}),
|
||||
|i: Input| i.token(TIMESTAMP_DAYNAME),
|
||||
)(i)
|
||||
|
|
@ -96,6 +98,30 @@ fn time(i: Input) -> IResult<Input, [GreenElement; 3], ()> {
|
|||
)(i)
|
||||
}
|
||||
|
||||
fn repeater_or_delay(
|
||||
input: Input,
|
||||
) -> IResult<Input, (GreenElement, GreenElement, GreenElement), ()> {
|
||||
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<Input, GreenElement, ()>,
|
||||
|
|
@ -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 "]"
|
||||
"###
|
||||
);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue