From 9c82b268d6433b7ab4acdf50c2618e2450145e58 Mon Sep 17 00:00:00 2001 From: PoiScript Date: Thu, 11 Apr 2019 23:53:43 +0800 Subject: [PATCH] chore: make the fields of Planning struct public --- src/elements/clock.rs | 4 +- src/elements/planning.rs | 67 +++++--- src/headline.rs | 14 +- src/objects/timestamp.rs | 327 ++++++++++++++++++++------------------- src/parser.rs | 9 +- 5 files changed, 231 insertions(+), 190 deletions(-) diff --git a/src/elements/clock.rs b/src/elements/clock.rs index 1430de3..4465d9f 100644 --- a/src/elements/clock.rs +++ b/src/elements/clock.rs @@ -1,4 +1,4 @@ -use crate::objects::timestamp::{self, Datetime, Delay, Repeater, Timestamp}; +use crate::objects::timestamp::{Datetime, Delay, Repeater, Timestamp}; use memchr::memchr; #[cfg_attr(test, derive(PartialEq))] @@ -32,7 +32,7 @@ impl<'a> Clock<'a> { return None; } - match timestamp::parse_inactive(tail).map(|(t, off)| (t, tail[off..].trim_start())) { + match Timestamp::parse_inactive(tail).map(|(t, off)| (t, tail[off..].trim_start())) { Some(( Timestamp::InactiveRange { start, diff --git a/src/elements/planning.rs b/src/elements/planning.rs index 2ba19db..8cb6015 100644 --- a/src/elements/planning.rs +++ b/src/elements/planning.rs @@ -1,37 +1,36 @@ -use crate::objects::timestamp::{self, Timestamp}; +use crate::objects::timestamp::Timestamp; use memchr::memchr; #[cfg_attr(test, derive(PartialEq))] #[derive(Debug)] pub struct Planning<'a> { - deadline: Option>, - scheduled: Option>, - closed: Option>, + pub deadline: Option>, + pub scheduled: Option>, + pub 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()) + let (mut deadline, mut scheduled, mut closed) = (None, None, None); + let (mut tail, 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()?; + while let Some(i) = memchr(b' ', tail.as_bytes()) { + let next = &tail[i + 1..].trim_start(); 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) + if next.starts_with('<') { + let (timestamp, off) = Timestamp::parse_active(next) + .or_else(|| Timestamp::parse_diary(next))?; + $timestamp = Some(timestamp); + tail = &next[off..].trim_start(); + } else if next.starts_with('<') { + let (timestamp, off) = Timestamp::parse_active(next)?; + $timestamp = Some(timestamp); + tail = &next[off..].trim_start(); } else { return None; } @@ -41,11 +40,11 @@ impl<'a> Planning<'a> { }; } - match word { + match &tail[..i] { "DEADLINE:" => set_timestamp!(deadline), "SCHEDULED:" => set_timestamp!(scheduled), "CLOSED:" => set_timestamp!(closed), - _ => (), + _ => return None, } } @@ -63,3 +62,31 @@ impl<'a> Planning<'a> { } } } + +#[cfg(test)] +mod tests { + #[test] + fn prase() { + use super::Planning; + use crate::objects::timestamp::{Datetime, Timestamp}; + + assert_eq!( + Planning::parse("SCHEDULED: <2019-04-08 Mon>\n"), + Some(( + Planning { + scheduled: Some(Timestamp::Active { + start: Datetime { + date: (2019, 4, 8), + time: None + }, + repeater: None, + delay: None + }), + closed: None, + deadline: None, + }, + "SCHEDULED: <2019-04-08 Mon>\n".len() + )) + ) + } +} diff --git a/src/headline.rs b/src/headline.rs index aef94f7..2e283b1 100644 --- a/src/headline.rs +++ b/src/headline.rs @@ -1,5 +1,6 @@ //! Headline +use jetscii::ByteSubstring; use memchr::{memchr, memchr2, memrchr}; pub(crate) const DEFAULT_KEYWORDS: &[&str] = @@ -28,7 +29,16 @@ 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 + 1, Headline::find_level(&text[i + 1..], level) + i + 1)) + .map(|i| { + ( + i + 1, + if i + 1 == text.len() { + Headline::find_level(&text[i + 1..], level) + i + 1 + } else { + i + 1 + }, + ) + }) .unwrap_or_else(|| (text.len(), text.len())); if level == off { @@ -98,8 +108,6 @@ impl<'a> Headline<'a> { } pub(crate) fn find_level(text: &str, level: usize) -> usize { - use jetscii::ByteSubstring; - let bytes = text.as_bytes(); if bytes[0] == b'*' { if let Some(stars) = memchr2(b'\n', b' ', bytes) { diff --git a/src/objects/timestamp.rs b/src/objects/timestamp.rs index 3502fb3..45e54d3 100644 --- a/src/objects/timestamp.rs +++ b/src/objects/timestamp.rs @@ -76,152 +76,158 @@ pub enum Timestamp<'a> { Diary(&'a str), } -pub fn parse_active(text: &str) -> Option<(Timestamp<'_>, usize)> { - debug_assert!(text.starts_with('<')); +impl<'a> Timestamp<'a> { + pub(crate) fn parse_active(text: &str) -> Option<(Timestamp<'_>, usize)> { + debug_assert!(text.starts_with('<')); - let bytes = text.as_bytes(); - let mut off = memchr(b'>', bytes)?; - let (start, mut end) = parse_datetime(&bytes[1..off])?; - if end.is_none() + let bytes = text.as_bytes(); + let mut off = memchr(b'>', bytes)?; + let (start, mut end) = Self::parse_datetime(&bytes[1..off])?; + if end.is_none() && off <= text.len() - 14 /* -- */ && text[off + 1..].starts_with("--<") - { - if let Some(new_off) = memchr(b'>', &bytes[off + 1..]) { - if let Some((start, _)) = parse_datetime(&bytes[off + 4..off + 1 + new_off]) { - end = Some(start); - off += new_off + 1; + { + if let Some(new_off) = memchr(b'>', &bytes[off + 1..]) { + if let Some((start, _)) = Self::parse_datetime(&bytes[off + 4..off + 1 + new_off]) { + end = Some(start); + off += new_off + 1; + } } } + + Some(( + if let Some(end) = end { + Timestamp::ActiveRange { + start, + end, + repeater: None, + delay: None, + } + } else { + Timestamp::Active { + start, + repeater: None, + delay: None, + } + }, + off + 1, + )) } - Some(( - if let Some(end) = end { - Timestamp::ActiveRange { - start, - end, - repeater: None, - delay: None, - } - } else { - Timestamp::Active { - start, - repeater: None, - delay: None, - } - }, - off + 1, - )) -} + pub(crate) fn parse_inactive(text: &str) -> Option<(Timestamp<'_>, usize)> { + debug_assert!(text.starts_with('[')); -pub fn parse_inactive(text: &str) -> Option<(Timestamp<'_>, usize)> { - debug_assert!(text.starts_with('[')); - - let bytes = text.as_bytes(); - let mut off = memchr(b']', bytes)?; - let (start, mut end) = parse_datetime(&bytes[1..off])?; - if end.is_none() + let bytes = text.as_bytes(); + let mut off = memchr(b']', bytes)?; + let (start, mut end) = Self::parse_datetime(&bytes[1..off])?; + if end.is_none() && off <= text.len() - 14 /* --[YYYY-MM-DD] */ && text[off + 1..].starts_with("--[") - { - if let Some(new_off) = memchr(b']', &bytes[off + 1..]) { - if let Some((start, _)) = parse_datetime(&bytes[off + 4..off + 1 + new_off]) { - end = Some(start); - off += new_off + 1; + { + if let Some(new_off) = memchr(b']', &bytes[off + 1..]) { + if let Some((start, _)) = Self::parse_datetime(&bytes[off + 4..off + 1 + new_off]) { + end = Some(start); + off += new_off + 1; + } } } + + Some(( + if let Some(end) = end { + Timestamp::InactiveRange { + start, + end, + repeater: None, + delay: None, + } + } else { + Timestamp::Inactive { + start, + repeater: None, + delay: None, + } + }, + off + 1, + )) } - Some(( - if let Some(end) = end { - Timestamp::InactiveRange { - start, - end, - repeater: None, - delay: None, - } - } else { - Timestamp::Inactive { - start, - repeater: None, - delay: None, - } - }, - off + 1, - )) -} + fn parse_datetime(bytes: &[u8]) -> Option<(Datetime, Option)> { + if bytes.is_empty() + || !bytes[0].is_ascii_digit() + || !bytes[bytes.len() - 1].is_ascii_alphanumeric() + { + return None; + } -fn parse_datetime(bytes: &[u8]) -> Option<(Datetime, Option)> { - if bytes.is_empty() - || !bytes[0].is_ascii_digit() - || !bytes[bytes.len() - 1].is_ascii_alphanumeric() - { - return None; - } + // similar to str::split_ascii_whitespace, but for &[u8] + let mut words = bytes + .split(u8::is_ascii_whitespace) + .filter(|s| !s.is_empty()); - // similar to str::split_ascii_whitespace, but for &[u8] - let mut words = bytes - .split(u8::is_ascii_whitespace) - .filter(|s| !s.is_empty()); - - let date = words - .next() - .filter(|word| { - word.len() == 10 /* YYYY-MM-DD */ + let date = words + .next() + .filter(|word| { + word.len() == 10 /* YYYY-MM-DD */ && word[0..4].iter().all(u8::is_ascii_digit) && word[4] == b'-' && word[5..7].iter().all(u8::is_ascii_digit) && word[7] == b'-' && word[8..10].iter().all(u8::is_ascii_digit) - }) - .map(|word| { - ( - (u16::from(word[0]) - u16::from(b'0')) * 1000 - + (u16::from(word[1]) - u16::from(b'0')) * 100 - + (u16::from(word[2]) - u16::from(b'0')) * 10 - + (u16::from(word[3]) - u16::from(b'0')), - (word[5] - b'0') * 10 + (word[6] - b'0'), - (word[8] - b'0') * 10 + (word[9] - b'0'), - ) + }) + .map(|word| { + ( + (u16::from(word[0]) - u16::from(b'0')) * 1000 + + (u16::from(word[1]) - u16::from(b'0')) * 100 + + (u16::from(word[2]) - u16::from(b'0')) * 10 + + (u16::from(word[3]) - u16::from(b'0')), + (word[5] - b'0') * 10 + (word[6] - b'0'), + (word[8] - b'0') * 10 + (word[9] - b'0'), + ) + })?; + + let _dayname = words.next().filter(|word| { + word.iter().all(|&c| { + !(c == b'+' + || c == b'-' + || c == b']' + || c == b'>' + || c.is_ascii_digit() + || c == b'\n') + }) })?; - let _dayname = words.next().filter(|word| { - word.iter().all(|&c| { - !(c == b'+' || c == b'-' || c == b']' || c == b'>' || c.is_ascii_digit() || c == b'\n') - }) - })?; + let (start, end) = if let Some(word) = words.next() { + macro_rules! datetime { + ($a:expr, $b:expr, $c:expr) => { + Datetime { + date, + time: Some((word[$a] - b'0', (word[$b] - b'0') * 10 + (word[$c] - b'0'))), + } + }; + ($a:expr, $b:expr, $c:expr, $d:expr) => { + Datetime { + date, + time: Some(( + (word[$a] - b'0') * 10 + (word[$b] - b'0'), + (word[$c] - b'0') * 10 + (word[$d] - b'0'), + )), + } + }; + } - let (start, end) = if let Some(word) = words.next() { - macro_rules! datetime { - ($a:expr, $b:expr, $c:expr) => { - Datetime { - date, - time: Some((word[$a] - b'0', (word[$b] - b'0') * 10 + (word[$c] - b'0'))), - } - }; - ($a:expr, $b:expr, $c:expr, $d:expr) => { - Datetime { - date, - time: Some(( - (word[$a] - b'0') * 10 + (word[$b] - b'0'), - (word[$c] - b'0') * 10 + (word[$d] - b'0'), - )), - } - }; - } - - if word.len() == 4 // H:MM + if word.len() == 4 // H:MM && word[0].is_ascii_digit() && word[1] == b':' && word[2..4].iter().all(u8::is_ascii_digit) - { - (datetime!(0, 2, 3), None) - } else if word.len() == 5 // HH:MM + { + (datetime!(0, 2, 3), None) + } else if word.len() == 5 // HH:MM && word[0..2].iter().all(u8::is_ascii_digit) && word[2] == b':' && word[3..5].iter().all(u8::is_ascii_digit) - { - (datetime!(0, 1, 3, 4), None) - } else if word.len() == 9 // H:MM-H:MM + { + (datetime!(0, 1, 3, 4), None) + } else if word.len() == 9 // H:MM-H:MM && word[0].is_ascii_digit() && word[1] == b':' && word[2..4].iter().all(u8::is_ascii_digit) @@ -229,9 +235,9 @@ fn parse_datetime(bytes: &[u8]) -> Option<(Datetime, Option)> { && word[5].is_ascii_digit() && word[6] == b':' && word[7..9].iter().all(u8::is_ascii_digit) - { - (datetime!(0, 2, 3), Some(datetime!(5, 7, 8))) - } else if word.len() == 10 // H:MM-HH:MM + { + (datetime!(0, 2, 3), Some(datetime!(5, 7, 8))) + } else if word.len() == 10 // H:MM-HH:MM && word[0].is_ascii_digit() && word[1] == b':' && word[2..4].iter().all(u8::is_ascii_digit) @@ -239,9 +245,9 @@ fn parse_datetime(bytes: &[u8]) -> Option<(Datetime, Option)> { && word[5..7].iter().all(u8::is_ascii_digit) && word[7] == b':' && word[8..10].iter().all(u8::is_ascii_digit) - { - (datetime!(0, 2, 3), Some(datetime!(5, 6, 8, 9))) - } else if word.len() == 10 // HH:MM-H:MM + { + (datetime!(0, 2, 3), Some(datetime!(5, 6, 8, 9))) + } else if word.len() == 10 // HH:MM-H:MM && word[0..2].iter().all(u8::is_ascii_digit) && word[2] == b':' && word[3..5].iter().all(u8::is_ascii_digit) @@ -249,9 +255,9 @@ fn parse_datetime(bytes: &[u8]) -> Option<(Datetime, Option)> { && word[6].is_ascii_digit() && word[7] == b':' && word[8..10].iter().all(u8::is_ascii_digit) - { - (datetime!(0, 1, 3, 4), Some(datetime!(6, 8, 9))) - } else if word.len() == 11 // HH:MM-HH:MM + { + (datetime!(0, 1, 3, 4), Some(datetime!(6, 8, 9))) + } else if word.len() == 11 // HH:MM-HH:MM && word[0..2].iter().all(u8::is_ascii_digit) && word[2] == b':' && word[3..5].iter().all(u8::is_ascii_digit) @@ -259,37 +265,38 @@ fn parse_datetime(bytes: &[u8]) -> Option<(Datetime, Option)> { && word[6..8].iter().all(u8::is_ascii_digit) && word[8] == b':' && word[9..11].iter().all(u8::is_ascii_digit) - { - (datetime!(0, 1, 3, 4), Some(datetime!(6, 7, 9, 10))) + { + (datetime!(0, 1, 3, 4), Some(datetime!(6, 7, 9, 10))) + } else { + return None; + } } else { + (Datetime { date, time: None }, None) + }; + + // TODO: repeater and delay + if words.next().is_some() { + None + } else { + Some((start, end)) + } + } + + pub(crate) fn parse_diary(text: &str) -> Option<(Timestamp<'_>, usize)> { + debug_assert!(text.starts_with('<')); + + if text.len() <= 6 /* <%%()> */ || &text[1..4] != "%%(" { return None; } - } else { - (Datetime { date, time: None }, None) - }; - // TODO: repeater and delay - if words.next().is_some() { - None - } else { - Some((start, end)) + let bytes = text.as_bytes(); + + memchr(b'>', bytes) + .filter(|i| bytes[i - 1] == b')' && bytes[4..i - 1].iter().all(|&c| c != b'\n')) + .map(|i| (Timestamp::Diary(&text[4..i - 1]), i)) } } -pub fn parse_diary(text: &str) -> Option<(Timestamp<'_>, usize)> { - debug_assert!(text.starts_with('<')); - - if text.len() <= 6 /* <%%()> */ || &text[1..4] != "%%(" { - return None; - } - - let bytes = text.as_bytes(); - - memchr(b'>', bytes) - .filter(|i| bytes[i - 1] == b')' && bytes[4..i - 1].iter().all(|&c| c != b'\n')) - .map(|i| (Timestamp::Diary(&text[4..i - 1]), i)) -} - #[cfg(test)] mod tests { #[test] @@ -297,7 +304,7 @@ mod tests { use super::*; assert_eq!( - parse_inactive("[2003-09-16 Tue]"), + Timestamp::parse_inactive("[2003-09-16 Tue]"), Some(( Timestamp::Inactive { start: Datetime { @@ -311,7 +318,7 @@ mod tests { )) ); assert_eq!( - parse_inactive("[2003-09-16 Tue 09:39]--[2003-09-16 Tue 10:39]"), + Timestamp::parse_inactive("[2003-09-16 Tue 09:39]--[2003-09-16 Tue 10:39]"), Some(( Timestamp::InactiveRange { start: Datetime { @@ -329,7 +336,7 @@ mod tests { )) ); assert_eq!( - parse_active("<2003-09-16 Tue 09:39-10:39>"), + Timestamp::parse_active("<2003-09-16 Tue 09:39-10:39>"), Some(( Timestamp::ActiveRange { start: Datetime { @@ -353,7 +360,7 @@ mod tests { use super::*; assert_eq!( - parse_datetime(b"2003-09-16 Tue"), + Timestamp::parse_datetime(b"2003-09-16 Tue"), Some(( Datetime { date: (2003, 9, 16), @@ -363,7 +370,7 @@ mod tests { )) ); assert_eq!( - parse_datetime(b"2003-09-16 Tue 9:39"), + Timestamp::parse_datetime(b"2003-09-16 Tue 9:39"), Some(( Datetime { date: (2003, 9, 16), @@ -373,7 +380,7 @@ mod tests { )) ); assert_eq!( - parse_datetime(b"2003-09-16 Tue 09:39"), + Timestamp::parse_datetime(b"2003-09-16 Tue 09:39"), Some(( Datetime { date: (2003, 9, 16), @@ -383,7 +390,7 @@ mod tests { )) ); assert_eq!( - parse_datetime(b"2003-09-16 Tue 9:39-10:39"), + Timestamp::parse_datetime(b"2003-09-16 Tue 9:39-10:39"), Some(( Datetime { date: (2003, 9, 16), @@ -396,9 +403,9 @@ mod tests { )) ); - assert_eq!(parse_datetime(b"2003-9-16 Tue"), None); - assert_eq!(parse_datetime(b"2003-09-16"), None); - assert_eq!(parse_datetime(b"2003-09-16 09:39"), None); - assert_eq!(parse_datetime(b"2003-09-16 Tue 0939"), None); + assert_eq!(Timestamp::parse_datetime(b"2003-9-16 Tue"), None); + assert_eq!(Timestamp::parse_datetime(b"2003-09-16"), None); + assert_eq!(Timestamp::parse_datetime(b"2003-09-16 09:39"), None); + assert_eq!(Timestamp::parse_datetime(b"2003-09-16 Tue 0939"), None); } } diff --git a/src/parser.rs b/src/parser.rs index 1775197..ce0472e 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -485,10 +485,10 @@ impl<'a> Parser<'a> { target::parse(text).map(|(target, off)| (Event::Target { target }, off, 0, 0)) } } - b'<' => timestamp::parse_active(text) + b'<' => Timestamp::parse_active(text) .map(|(timestamp, off)| (Event::Timestamp(timestamp), off, 0, 0)) .or_else(|| { - timestamp::parse_diary(text) + Timestamp::parse_diary(text) .map(|(timestamp, off)| (Event::Timestamp(timestamp), off, 0, 0)) }), b'[' => { @@ -502,7 +502,7 @@ impl<'a> Parser<'a> { cookie::parse(text) .map(|(cookie, off)| (Event::Cookie(cookie), off, 0, 0)) .or_else(|| { - timestamp::parse_inactive(text) + Timestamp::parse_inactive(text) .map(|(timestamp, off)| (Event::Timestamp(timestamp), off, 0, 0)) }) } @@ -601,7 +601,6 @@ impl<'a> Iterator for Parser<'a> { Some(match container { Container::Headline(beg) => { - debug_assert!(self.off >= beg); if self.off >= limit { self.off = end; self.stack.pop(); @@ -645,7 +644,7 @@ impl<'a> Iterator for Parser<'a> { Container::List(ident, ordered) => { if let Some(bullet) = self.next_item.pop().unwrap() { self.off += bullet.len() + ident; - let (limit, end, next) = list::parse(&self.text[self.off..limit], ident); + let (limit, end, next) = list::parse(tail, ident); self.push_stack(Container::ListItem, limit, end); self.next_item.push(next); Event::ListItemBeg { bullet }