chore: make the fields of Planning struct public

This commit is contained in:
PoiScript 2019-04-11 23:53:43 +08:00
parent c2c554a3d5
commit 9c82b268d6
5 changed files with 231 additions and 190 deletions

View file

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

View file

@ -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<Timestamp<'a>>,
scheduled: Option<Timestamp<'a>>,
closed: Option<Timestamp<'a>>,
pub deadline: Option<Timestamp<'a>>,
pub scheduled: Option<Timestamp<'a>>,
pub closed: Option<Timestamp<'a>>,
}
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()
))
)
}
}

View file

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

View file

@ -76,18 +76,19 @@ pub enum Timestamp<'a> {
Diary(&'a str),
}
pub fn parse_active(text: &str) -> Option<(Timestamp<'_>, usize)> {
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])?;
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]) {
if let Some((start, _)) = Self::parse_datetime(&bytes[off + 4..off + 1 + new_off]) {
end = Some(start);
off += new_off + 1;
}
@ -111,20 +112,20 @@ pub fn parse_active(text: &str) -> Option<(Timestamp<'_>, usize)> {
},
off + 1,
))
}
}
pub fn parse_inactive(text: &str) -> Option<(Timestamp<'_>, usize)> {
pub(crate) 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])?;
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]) {
if let Some((start, _)) = Self::parse_datetime(&bytes[off + 4..off + 1 + new_off]) {
end = Some(start);
off += new_off + 1;
}
@ -148,9 +149,9 @@ pub fn parse_inactive(text: &str) -> Option<(Timestamp<'_>, usize)> {
},
off + 1,
))
}
}
fn parse_datetime(bytes: &[u8]) -> Option<(Datetime, Option<Datetime>)> {
fn parse_datetime(bytes: &[u8]) -> Option<(Datetime, Option<Datetime>)> {
if bytes.is_empty()
|| !bytes[0].is_ascii_digit()
|| !bytes[bytes.len() - 1].is_ascii_alphanumeric()
@ -186,7 +187,12 @@ fn parse_datetime(bytes: &[u8]) -> Option<(Datetime, Option<Datetime>)> {
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')
!(c == b'+'
|| c == b'-'
|| c == b']'
|| c == b'>'
|| c.is_ascii_digit()
|| c == b'\n')
})
})?;
@ -274,9 +280,9 @@ fn parse_datetime(bytes: &[u8]) -> Option<(Datetime, Option<Datetime>)> {
} else {
Some((start, end))
}
}
}
pub fn parse_diary(text: &str) -> Option<(Timestamp<'_>, usize)> {
pub(crate) fn parse_diary(text: &str) -> Option<(Timestamp<'_>, usize)> {
debug_assert!(text.starts_with('<'));
if text.len() <= 6 /* <%%()> */ || &text[1..4] != "%%(" {
@ -288,6 +294,7 @@ pub fn parse_diary(text: &str) -> Option<(Timestamp<'_>, usize)> {
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)]
@ -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);
}
}

View file

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