feat(parser): clock parsing

This commit is contained in:
PoiScript 2019-04-07 15:33:18 +08:00
parent 406fd22aee
commit fe591d2143
5 changed files with 217 additions and 28 deletions

183
src/elements/clock.rs Normal file
View file

@ -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<Repeater>,
delay: Option<Delay>,
duration: &'a str,
},
Running {
start: Datetime,
repeater: Option<Repeater>,
delay: Option<Delay>,
},
}
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()
))
);
}
}

View file

@ -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;

View file

@ -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 {

View file

@ -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,

View file

@ -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<Event<'a>> {
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
}