From 406fd22aee1708e034194c533497faa9393f897b Mon Sep 17 00:00:00 2001 From: PoiScript Date: Sat, 6 Apr 2019 14:49:47 +0800 Subject: [PATCH] feat(parser): planning parsing --- src/elements/mod.rs | 2 ++ src/elements/planning.rs | 65 ++++++++++++++++++++++++++++++++++++++++ src/export/html.rs | 23 +++++++++----- src/export/mod.rs | 1 + src/parser.rs | 26 ++++++++++++---- 5 files changed, 103 insertions(+), 14 deletions(-) create mode 100644 src/elements/planning.rs diff --git a/src/elements/mod.rs b/src/elements/mod.rs index 63ef60e..043a7ab 100644 --- a/src/elements/mod.rs +++ b/src/elements/mod.rs @@ -3,6 +3,8 @@ pub(crate) mod dyn_block; pub(crate) mod fn_def; pub(crate) mod keyword; pub(crate) mod list; +pub(crate) mod planning; pub(crate) mod rule; pub use self::keyword::Key; +pub use self::planning::Planning; diff --git a/src/elements/planning.rs b/src/elements/planning.rs new file mode 100644 index 0000000..2ba19db --- /dev/null +++ b/src/elements/planning.rs @@ -0,0 +1,65 @@ +use crate::objects::timestamp::{self, Timestamp}; +use memchr::memchr; + +#[cfg_attr(test, derive(PartialEq))] +#[derive(Debug)] +pub struct Planning<'a> { + deadline: Option>, + scheduled: Option>, + closed: Option>, +} + +impl<'a> Planning<'a> { + pub(crate) fn parse(text: &'a str) -> Option<(Planning<'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 mut words = text.split_ascii_whitespace(); + let (mut deadline, mut scheduled, mut closed) = (None, None, None); + + while let Some(word) = words.next() { + let next = words.next()?; + + macro_rules! set_timestamp { + ($timestamp:expr) => { + if $timestamp.is_none() { + $timestamp = if next.starts_with('<') { + Some( + timestamp::parse_active(next) + .or_else(|| timestamp::parse_diary(next))? + .0, + ) + } else if next.starts_with('[') { + Some(timestamp::parse_inactive(next)?.0) + } else { + return None; + } + } else { + return None; + } + }; + } + + match word { + "DEADLINE:" => set_timestamp!(deadline), + "SCHEDULED:" => set_timestamp!(scheduled), + "CLOSED:" => set_timestamp!(closed), + _ => (), + } + } + + if deadline.is_none() && scheduled.is_none() && closed.is_none() { + None + } else { + Some(( + Planning { + deadline, + scheduled, + closed, + }, + off, + )) + } + } +} diff --git a/src/export/html.rs b/src/export/html.rs index 64c217f..c81a393 100644 --- a/src/export/html.rs +++ b/src/export/html.rs @@ -1,14 +1,18 @@ #![allow(unused_variables)] -use crate::elements::Key; -use crate::headline::Headline; -use crate::objects::{Cookie, Timestamp}; -use crate::parser::Parser; +use crate::{ + elements::{Key, Planning}, + headline::Headline, + objects::{Cookie, Timestamp}, + parser::Parser, +}; use jetscii::ascii_chars; -use std::convert::From; -use std::fmt; -use std::io::{Error, Write}; -use std::marker::PhantomData; +use std::{ + convert::From, + fmt, + io::{Error, Write}, + marker::PhantomData, +}; pub trait HtmlHandler> { fn headline_beg(&mut self, w: &mut W, hdl: Headline) -> Result<(), E> { @@ -216,6 +220,9 @@ pub trait HtmlHandler> { fn text(&mut self, w: &mut W, cont: &str) -> Result<(), E> { Ok(write!(w, "{}", Escape(cont))?) } + fn planning(&mut self, w: &mut W, planning: Planning) -> Result<(), E> { + Ok(()) + } } pub struct DefaultHtmlHandler; diff --git a/src/export/mod.rs b/src/export/mod.rs index 14c0943..a235d44 100644 --- a/src/export/mod.rs +++ b/src/export/mod.rs @@ -28,6 +28,7 @@ macro_rules! handle_event { ListItemBeg { bullet } => $handler.list_beg_item($writer, bullet)?, ListItemEnd => $handler.list_end_item($writer)?, Call { value } => $handler.call($writer, value)?, + Planning(p) => $handler.planning($writer, p)?, Clock => $handler.clock($writer)?, Timestamp(t) => $handler.timestamp($writer, t)?, Comment(c) => $handler.comment($writer, c)?, diff --git a/src/parser.rs b/src/parser.rs index aeed002..f47920a 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -8,7 +8,7 @@ use memchr::memchr_iter; #[derive(Copy, Clone, Debug)] enum Container { Headline(usize), - Section, + Section(usize), Paragraph, CtrBlock, QteBlock, @@ -90,6 +90,8 @@ pub enum Event<'a> { Comment(&'a str), FixedWidth(&'a str), + Planning(Planning<'a>), + TableStart, TableEnd, TableCell, @@ -197,7 +199,7 @@ impl<'a> Parser<'a> { let end = Headline::find_level(&self.text[self.off..], std::usize::MAX); debug_assert!(end <= self.text[self.off..].len()); if end != 0 { - self.push_stack(Container::Section, end, end); + self.push_stack(Container::Section(self.off), end, end); Event::SectionBeg } else { self.next_headline() @@ -207,8 +209,8 @@ impl<'a> Parser<'a> { 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()); - 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) } @@ -520,7 +522,7 @@ impl<'a> Parser<'a> { Container::ListItem => Event::ListItemEnd, Container::Paragraph => Event::ParagraphEnd, Container::QteBlock => Event::QteBlockEnd, - Container::Section => Event::SectionEnd, + Container::Section(_) => Event::SectionEnd, Container::SplBlock => Event::SplBlockEnd, Container::Strike => Event::StrikeEnd, Container::Underline => Event::UnderlineEnd, @@ -551,8 +553,20 @@ impl<'a> Iterator for Parser<'a> { | Container::CtrBlock | Container::QteBlock | Container::SplBlock - | Container::ListItem - | Container::Section => self.next_ele(&self.text[self.off..limit]), + | Container::ListItem => self.next_ele(&self.text[self.off..limit]), + Container::Section(beg) => { + let tail = &self.text[self.off..limit]; + if self.off == beg { + if let Some((planning, off)) = Planning::parse(tail) { + self.off += off; + Event::Planning(planning) + } else { + self.next_ele(tail) + } + } else { + self.next_ele(tail) + } + } Container::List(ident, _) => { if self.list_more_item { self.next_list_item(ident, &self.text[self.off..limit])