diff --git a/src/elements/clock.rs b/src/elements/clock.rs new file mode 100644 index 0000000..4a43aff --- /dev/null +++ b/src/elements/clock.rs @@ -0,0 +1,183 @@ +use crate::objects::timestamp::{self, Datetime, Delay, Repeater, Timestamp}; +use memchr::memchr; + +#[cfg_attr(test, derive(PartialEq))] +#[derive(Debug)] +pub enum Clock<'a> { + Closed { + start: Datetime, + end: Datetime, + repeater: Option, + delay: Option, + duration: &'a str, + }, + Running { + start: Datetime, + repeater: Option, + delay: Option, + }, +} + +impl<'a> Clock<'a> { + pub(crate) fn parse(text: &'a str) -> Option<(Clock<'a>, usize)> { + let (text, off) = memchr(b'\n', text.as_bytes()) + .map(|i| (text[..i].trim(), i + 1)) + .unwrap_or_else(|| (text.trim(), text.len())); + + let tail = memchr(b' ', text.as_bytes()) + .filter(|&i| &text[0..i] == "CLOCK:") + .map(|i| text[i..].trim_start())?; + + dbg!(tail); + + if !tail.starts_with('[') { + return None; + } + + match timestamp::parse_inactive(tail).map(|(t, off)| (t, tail[off..].trim_start())) { + Some(( + Timestamp::InactiveRange { + start, + end, + repeater, + delay, + }, + tail, + )) => { + if tail.starts_with("=>") { + let duration = &tail[3..].trim(); + let colon = memchr(b':', duration.as_bytes())?; + if duration.as_bytes()[0..colon].iter().all(u8::is_ascii_digit) + && colon == duration.len() - 3 + && duration.as_bytes()[colon + 1].is_ascii_digit() + && duration.as_bytes()[colon + 2].is_ascii_digit() + { + return Some(( + Clock::Closed { + start, + end, + repeater, + delay, + duration, + }, + off, + )); + } + } + } + Some(( + Timestamp::Inactive { + start, + repeater, + delay, + }, + tail, + )) => { + if tail.as_bytes().iter().all(u8::is_ascii_whitespace) { + return Some(( + Clock::Running { + start, + repeater, + delay, + }, + off, + )); + } + } + _ => (), + } + + None + } + + pub fn is_running(&self) -> bool { + match self { + Clock::Closed { .. } => false, + Clock::Running { .. } => true, + } + } + + pub fn is_closed(&self) -> bool { + match self { + Clock::Closed { .. } => true, + Clock::Running { .. } => false, + } + } + + pub fn duration(&self) -> Option<&'a str> { + match self { + Clock::Closed { duration, .. } => Some(duration), + Clock::Running { .. } => None, + } + } + + pub fn value(&self) -> Timestamp<'_> { + match *self { + Clock::Closed { + start, + end, + repeater, + delay, + .. + } => Timestamp::InactiveRange { + start, + end, + repeater, + delay, + }, + Clock::Running { + start, + repeater, + delay, + .. + } => Timestamp::Inactive { + start, + repeater, + delay, + }, + } + } +} + +#[cfg(test)] +mod tests { + use super::Clock; + use crate::objects::timestamp::Datetime; + + #[test] + fn parse() { + assert_eq!( + Clock::parse("CLOCK: [2003-09-16 Tue 09:39]"), + Some(( + Clock::Running { + start: Datetime { + date: (2003, 9, 16), + time: Some((9, 39)) + }, + repeater: None, + delay: None, + }, + "CLOCK: [2003-09-16 Tue 09:39]".len() + )) + ); + assert_eq!( + Clock::parse("CLOCK: [2003-09-16 Tue 09:39]--[2003-09-16 Tue 10:39] => 1:00"), + Some(( + Clock::Closed { + start: Datetime { + date: (2003, 9, 16), + time: Some((9, 39)) + }, + end: Datetime { + date: (2003, 9, 16), + time: Some((10, 39)) + }, + repeater: None, + delay: None, + duration: "1:00", + }, + "CLOCK: [2003-09-16 Tue 09:39]--[2003-09-16 Tue 10:39] => 1:00".len() + )) + ); + } +} diff --git a/src/elements/mod.rs b/src/elements/mod.rs index 043a7ab..6824e22 100644 --- a/src/elements/mod.rs +++ b/src/elements/mod.rs @@ -1,4 +1,5 @@ pub(crate) mod block; +pub(crate) mod clock; pub(crate) mod dyn_block; pub(crate) mod fn_def; pub(crate) mod keyword; @@ -6,5 +7,6 @@ pub(crate) mod list; pub(crate) mod planning; pub(crate) mod rule; +pub use self::clock::Clock; pub use self::keyword::Key; pub use self::planning::Planning; diff --git a/src/headline.rs b/src/headline.rs index 5d95f67..aef94f7 100644 --- a/src/headline.rs +++ b/src/headline.rs @@ -28,7 +28,7 @@ impl<'a> Headline<'a> { debug_assert!(text.as_bytes()[0..level].iter().all(|&c| c == b'*')); let (off, end) = memchr(b'\n', text.as_bytes()) - .map(|i| (i, Headline::find_level(&text[i..], level) + i)) + .map(|i| (i + 1, Headline::find_level(&text[i + 1..], level) + i + 1)) .unwrap_or_else(|| (text.len(), text.len())); if level == off { diff --git a/src/objects/timestamp.rs b/src/objects/timestamp.rs index 1ff8eb0..3502fb3 100644 --- a/src/objects/timestamp.rs +++ b/src/objects/timestamp.rs @@ -1,14 +1,14 @@ use memchr::memchr; #[cfg_attr(test, derive(PartialEq))] -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] pub struct Datetime { pub date: (u16, u8, u8), pub time: Option<(u8, u8)>, } #[cfg_attr(test, derive(PartialEq))] -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] pub enum RepeaterType { Cumulate, CatchUp, @@ -16,14 +16,14 @@ pub enum RepeaterType { } #[cfg_attr(test, derive(PartialEq))] -#[derive(Debug)] +#[derive(Debug, Copy, Clone)] pub enum DelayType { All, First, } #[cfg_attr(test, derive(PartialEq))] -#[derive(Debug)] +#[derive(Debug, Copy, Clone)] pub enum TimeUnit { Hour, Day, @@ -33,7 +33,7 @@ pub enum TimeUnit { } #[cfg_attr(test, derive(PartialEq))] -#[derive(Debug)] +#[derive(Debug, Copy, Clone)] pub struct Repeater { pub ty: RepeaterType, pub value: usize, @@ -41,7 +41,7 @@ pub struct Repeater { } #[cfg_attr(test, derive(PartialEq))] -#[derive(Debug)] +#[derive(Debug, Copy, Clone)] pub struct Delay { pub ty: DelayType, pub value: usize, diff --git a/src/parser.rs b/src/parser.rs index f47920a..8e97095 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -195,22 +195,20 @@ impl<'a> Parser<'a> { self.keywords = keywords; } - fn next_section_or_headline(&mut self) -> Event<'a> { - let end = Headline::find_level(&self.text[self.off..], std::usize::MAX); - debug_assert!(end <= self.text[self.off..].len()); + fn next_section_or_headline(&mut self, text: &'a str) -> Event<'a> { + let end = Headline::find_level(text, std::usize::MAX); if end != 0 { self.push_stack(Container::Section(self.off), end, end); Event::SectionBeg } else { - self.next_headline() + self.next_headline(text) } } - fn next_headline(&mut self) -> Event<'a> { - let (hdl, off, end) = Headline::parse(&self.text[self.off..], self.keywords); - debug_assert!(end <= self.text[self.off..].len()); + fn next_headline(&mut self, text: &'a str) -> Event<'a> { + let (hdl, off, end) = Headline::parse(text, self.keywords); + self.push_stack(Container::Headline(self.off + off), end, end); self.off += off; - self.push_stack(Container::Headline(self.off), end, end); Event::HeadlineBeg(hdl) } @@ -250,11 +248,9 @@ impl<'a> Parser<'a> { .all(u8::is_ascii_whitespace) { return (Event::ParagraphBeg, 0, pos + start, off + start); - } else { - if let Some(buf) = self.real_next_ele(&tail[pos + 1..]) { - self.ele_buf = Some(buf); - return (Event::ParagraphBeg, 0, pos + start, pos + start); - } + } else if let Some(buf) = self.real_next_ele(&tail[pos + 1..]) { + self.ele_buf = Some(buf); + return (Event::ParagraphBeg, 0, pos + start, pos + start); } pos = off; } @@ -535,27 +531,35 @@ impl<'a> Iterator for Parser<'a> { fn next(&mut self) -> Option> { if let Some(&(container, limit, end)) = self.stack.last() { + debug_assert!( + self.off <= limit && limit <= end && end <= self.text.len(), + "{} <= {} <= {} <= {}", + self.off, + limit, + end, + self.text.len() + ); Some(if self.off >= limit { - debug_assert!(self.off <= limit && self.off <= end); self.off = end; self.end() } else { + let tail = &self.text[self.off..limit]; match container { Container::Headline(beg) => { debug_assert!(self.off >= beg); if self.off == beg { - self.next_section_or_headline() + self.next_section_or_headline(tail) } else { - self.next_headline() + self.next_headline(tail) } } Container::DynBlock | Container::CtrBlock | Container::QteBlock | Container::SplBlock - | Container::ListItem => self.next_ele(&self.text[self.off..limit]), + | Container::ListItem => self.next_ele(tail), Container::Section(beg) => { - let tail = &self.text[self.off..limit]; + // planning should be the first line of section if self.off == beg { if let Some((planning, off)) = Planning::parse(tail) { self.off += off; @@ -569,7 +573,7 @@ impl<'a> Iterator for Parser<'a> { } Container::List(ident, _) => { if self.list_more_item { - self.next_list_item(ident, &self.text[self.off..limit]) + self.next_list_item(ident, tail) } else { self.end() } @@ -578,11 +582,11 @@ impl<'a> Iterator for Parser<'a> { | Container::Bold | Container::Underline | Container::Italic - | Container::Strike => self.next_obj(&self.text[self.off..limit]), + | Container::Strike => self.next_obj(tail), } }) } else if self.off < self.text.len() { - Some(self.next_section_or_headline()) + Some(self.next_section_or_headline(&self.text[self.off..])) } else { None }