feat(parser): clock parsing
This commit is contained in:
parent
406fd22aee
commit
fe591d2143
183
src/elements/clock.rs
Normal file
183
src/elements/clock.rs
Normal 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()
|
||||
))
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue