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 block;
|
||||||
|
pub(crate) mod clock;
|
||||||
pub(crate) mod dyn_block;
|
pub(crate) mod dyn_block;
|
||||||
pub(crate) mod fn_def;
|
pub(crate) mod fn_def;
|
||||||
pub(crate) mod keyword;
|
pub(crate) mod keyword;
|
||||||
|
@ -6,5 +7,6 @@ pub(crate) mod list;
|
||||||
pub(crate) mod planning;
|
pub(crate) mod planning;
|
||||||
pub(crate) mod rule;
|
pub(crate) mod rule;
|
||||||
|
|
||||||
|
pub use self::clock::Clock;
|
||||||
pub use self::keyword::Key;
|
pub use self::keyword::Key;
|
||||||
pub use self::planning::Planning;
|
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'*'));
|
debug_assert!(text.as_bytes()[0..level].iter().all(|&c| c == b'*'));
|
||||||
|
|
||||||
let (off, end) = memchr(b'\n', text.as_bytes())
|
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()));
|
.unwrap_or_else(|| (text.len(), text.len()));
|
||||||
|
|
||||||
if level == off {
|
if level == off {
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
use memchr::memchr;
|
use memchr::memchr;
|
||||||
|
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct Datetime {
|
pub struct Datetime {
|
||||||
pub date: (u16, u8, u8),
|
pub date: (u16, u8, u8),
|
||||||
pub time: Option<(u8, u8)>,
|
pub time: Option<(u8, u8)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub enum RepeaterType {
|
pub enum RepeaterType {
|
||||||
Cumulate,
|
Cumulate,
|
||||||
CatchUp,
|
CatchUp,
|
||||||
|
@ -16,14 +16,14 @@ pub enum RepeaterType {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub enum DelayType {
|
pub enum DelayType {
|
||||||
All,
|
All,
|
||||||
First,
|
First,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub enum TimeUnit {
|
pub enum TimeUnit {
|
||||||
Hour,
|
Hour,
|
||||||
Day,
|
Day,
|
||||||
|
@ -33,7 +33,7 @@ pub enum TimeUnit {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub struct Repeater {
|
pub struct Repeater {
|
||||||
pub ty: RepeaterType,
|
pub ty: RepeaterType,
|
||||||
pub value: usize,
|
pub value: usize,
|
||||||
|
@ -41,7 +41,7 @@ pub struct Repeater {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub struct Delay {
|
pub struct Delay {
|
||||||
pub ty: DelayType,
|
pub ty: DelayType,
|
||||||
pub value: usize,
|
pub value: usize,
|
||||||
|
|
|
@ -195,22 +195,20 @@ impl<'a> Parser<'a> {
|
||||||
self.keywords = keywords;
|
self.keywords = keywords;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next_section_or_headline(&mut self) -> Event<'a> {
|
fn next_section_or_headline(&mut self, text: &'a str) -> Event<'a> {
|
||||||
let end = Headline::find_level(&self.text[self.off..], std::usize::MAX);
|
let end = Headline::find_level(text, std::usize::MAX);
|
||||||
debug_assert!(end <= self.text[self.off..].len());
|
|
||||||
if end != 0 {
|
if end != 0 {
|
||||||
self.push_stack(Container::Section(self.off), end, end);
|
self.push_stack(Container::Section(self.off), end, end);
|
||||||
Event::SectionBeg
|
Event::SectionBeg
|
||||||
} else {
|
} else {
|
||||||
self.next_headline()
|
self.next_headline(text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next_headline(&mut self) -> Event<'a> {
|
fn next_headline(&mut self, text: &'a str) -> Event<'a> {
|
||||||
let (hdl, off, end) = Headline::parse(&self.text[self.off..], self.keywords);
|
let (hdl, off, end) = Headline::parse(text, self.keywords);
|
||||||
debug_assert!(end <= self.text[self.off..].len());
|
self.push_stack(Container::Headline(self.off + off), end, end);
|
||||||
self.off += off;
|
self.off += off;
|
||||||
self.push_stack(Container::Headline(self.off), end, end);
|
|
||||||
Event::HeadlineBeg(hdl)
|
Event::HeadlineBeg(hdl)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -250,12 +248,10 @@ impl<'a> Parser<'a> {
|
||||||
.all(u8::is_ascii_whitespace)
|
.all(u8::is_ascii_whitespace)
|
||||||
{
|
{
|
||||||
return (Event::ParagraphBeg, 0, pos + start, off + start);
|
return (Event::ParagraphBeg, 0, pos + start, off + start);
|
||||||
} else {
|
} else if let Some(buf) = self.real_next_ele(&tail[pos + 1..]) {
|
||||||
if let Some(buf) = self.real_next_ele(&tail[pos + 1..]) {
|
|
||||||
self.ele_buf = Some(buf);
|
self.ele_buf = Some(buf);
|
||||||
return (Event::ParagraphBeg, 0, pos + start, pos + start);
|
return (Event::ParagraphBeg, 0, pos + start, pos + start);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
pos = off;
|
pos = off;
|
||||||
}
|
}
|
||||||
let len = text.len();
|
let len = text.len();
|
||||||
|
@ -535,27 +531,35 @@ impl<'a> Iterator for Parser<'a> {
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Event<'a>> {
|
fn next(&mut self) -> Option<Event<'a>> {
|
||||||
if let Some(&(container, limit, end)) = self.stack.last() {
|
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 {
|
Some(if self.off >= limit {
|
||||||
debug_assert!(self.off <= limit && self.off <= end);
|
|
||||||
self.off = end;
|
self.off = end;
|
||||||
self.end()
|
self.end()
|
||||||
} else {
|
} else {
|
||||||
|
let tail = &self.text[self.off..limit];
|
||||||
match container {
|
match container {
|
||||||
Container::Headline(beg) => {
|
Container::Headline(beg) => {
|
||||||
debug_assert!(self.off >= beg);
|
debug_assert!(self.off >= beg);
|
||||||
if self.off == beg {
|
if self.off == beg {
|
||||||
self.next_section_or_headline()
|
self.next_section_or_headline(tail)
|
||||||
} else {
|
} else {
|
||||||
self.next_headline()
|
self.next_headline(tail)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Container::DynBlock
|
Container::DynBlock
|
||||||
| Container::CtrBlock
|
| Container::CtrBlock
|
||||||
| Container::QteBlock
|
| Container::QteBlock
|
||||||
| Container::SplBlock
|
| Container::SplBlock
|
||||||
| Container::ListItem => self.next_ele(&self.text[self.off..limit]),
|
| Container::ListItem => self.next_ele(tail),
|
||||||
Container::Section(beg) => {
|
Container::Section(beg) => {
|
||||||
let tail = &self.text[self.off..limit];
|
// planning should be the first line of section
|
||||||
if self.off == beg {
|
if self.off == beg {
|
||||||
if let Some((planning, off)) = Planning::parse(tail) {
|
if let Some((planning, off)) = Planning::parse(tail) {
|
||||||
self.off += off;
|
self.off += off;
|
||||||
|
@ -569,7 +573,7 @@ impl<'a> Iterator for Parser<'a> {
|
||||||
}
|
}
|
||||||
Container::List(ident, _) => {
|
Container::List(ident, _) => {
|
||||||
if self.list_more_item {
|
if self.list_more_item {
|
||||||
self.next_list_item(ident, &self.text[self.off..limit])
|
self.next_list_item(ident, tail)
|
||||||
} else {
|
} else {
|
||||||
self.end()
|
self.end()
|
||||||
}
|
}
|
||||||
|
@ -578,11 +582,11 @@ impl<'a> Iterator for Parser<'a> {
|
||||||
| Container::Bold
|
| Container::Bold
|
||||||
| Container::Underline
|
| Container::Underline
|
||||||
| Container::Italic
|
| 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() {
|
} else if self.off < self.text.len() {
|
||||||
Some(self.next_section_or_headline())
|
Some(self.next_section_or_headline(&self.text[self.off..]))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue