refactor(parser): wrap some objects and elements with struct

This commit is contained in:
PoiScript 2019-04-24 17:42:21 +08:00
parent 69534576f1
commit 56e289fb48
22 changed files with 1055 additions and 875 deletions

View file

@ -1,4 +1,4 @@
use crate::objects::timestamp::{Datetime, Delay, Repeater, Timestamp};
use crate::objects::timestamp::{Datetime, Timestamp};
use memchr::memchr;
/// clock elements
@ -11,15 +11,15 @@ pub enum Clock<'a> {
Closed {
start: Datetime<'a>,
end: Datetime<'a>,
repeater: Option<Repeater>,
delay: Option<Delay>,
repeater: Option<&'a str>,
delay: Option<&'a str>,
duration: &'a str,
},
/// running Clock
Running {
start: Datetime<'a>,
repeater: Option<Repeater>,
delay: Option<Delay>,
repeater: Option<&'a str>,
delay: Option<&'a str>,
},
}
@ -37,60 +37,57 @@ impl<'a> Clock<'a> {
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,
));
}
let (timestamp, tail) =
Timestamp::parse_inactive(tail).map(|(t, off)| (t, tail[off..].trim_start()))?;
match timestamp {
Timestamp::InactiveRange {
start,
end,
repeater,
delay,
} 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()
{
Some((
Clock::Closed {
start,
end,
repeater,
delay,
duration,
},
off,
))
} else {
None
}
}
Some((
Timestamp::Inactive {
start,
repeater,
delay,
},
tail,
)) => {
Timestamp::Inactive {
start,
repeater,
delay,
} => {
if tail.as_bytes().iter().all(u8::is_ascii_whitespace) {
return Some((
Some((
Clock::Running {
start,
repeater,
delay,
},
off,
));
))
} else {
None
}
}
_ => (),
_ => None,
}
None
}
/// returns `true` if the clock is running
@ -146,48 +143,42 @@ impl<'a> Clock<'a> {
}
}
#[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)),
dayname: "Tue"
},
repeater: None,
delay: None,
#[test]
fn parse() {
assert_eq!(
Clock::parse("CLOCK: [2003-09-16 Tue 09:39]"),
Some((
Clock::Running {
start: Datetime {
date: "2003-09-16",
time: Some("09:39"),
dayname: "Tue"
},
"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)),
dayname: "Tue"
},
end: Datetime {
date: (2003, 9, 16),
time: Some((10, 39)),
dayname: "Tue"
},
repeater: None,
delay: None,
duration: "1:00",
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-09-16",
time: Some("09:39"),
dayname: "Tue"
},
"CLOCK: [2003-09-16 Tue 09:39]--[2003-09-16 Tue 10:39] => 1:00".len()
))
);
}
end: Datetime {
date: "2003-09-16",
time: Some("10:39"),
dayname: "Tue"
},
repeater: None,
delay: None,
duration: "1:00",
},
"CLOCK: [2003-09-16 Tue 09:39]--[2003-09-16 Tue 10:39] => 1:00".len()
))
);
}

View file

@ -5,7 +5,7 @@ use memchr::{memchr, memchr_iter};
pub fn parse(text: &str) -> Option<(&str, Option<&str>, usize, usize, usize)> {
debug_assert!(text.starts_with("#+"));
if text.len() <= 9 || !text[2..9].eq_ignore_ascii_case("BEGIN: ") {
if text.len() <= "#+BEGIN: ".len() || !text[2..9].eq_ignore_ascii_case("BEGIN: ") {
return None;
}
@ -15,9 +15,15 @@ pub fn parse(text: &str) -> Option<(&str, Option<&str>, usize, usize, usize)> {
let (name, para, off) = lines
.next()
.map(|i| {
memchr(b' ', &bytes[9..i])
.map(|x| (&text[9..9 + x], Some(text[9 + x..i].trim()), i + 1))
.unwrap_or((&text[9..i], None, i + 1))
memchr(b' ', &bytes["#+BEGIN: ".len()..i])
.map(|x| {
(
&text["#+BEGIN: ".len().."#+BEGIN: ".len() + x],
Some(text["#+BEGIN: ".len() + x..i].trim()),
i + 1,
)
})
.unwrap_or((&text["#+BEGIN: ".len()..i], None, i + 1))
})
.filter(|(name, _, _)| name.as_bytes().iter().all(|&c| c.is_ascii_alphabetic()))?;

View file

@ -7,11 +7,11 @@ pub fn parse(text: &str) -> Option<(&str, &str, usize)> {
let (label, off) = memchr(b']', text.as_bytes())
.filter(|&i| {
i != 4
&& text.as_bytes()[4..i]
&& text.as_bytes()["[fn:".len()..i]
.iter()
.all(|&c| c.is_ascii_alphanumeric() || c == b'-' || c == b'_')
})
.map(|i| (&text[4..i], i + 1))?;
.map(|i| (&text["[fn:".len()..i], i + 1))?;
let (content, off) = memchr(b'\n', text.as_bytes())
.map(|i| (&text[off..i], i))

View file

@ -17,112 +17,124 @@ pub enum Key<'a> {
Date,
Title,
Custom(&'a str),
// Babel Call
Call,
}
pub fn parse(text: &str) -> Option<(Key<'_>, &str, usize)> {
debug_assert!(text.starts_with("#+"));
#[cfg_attr(test, derive(PartialEq))]
#[derive(Debug)]
pub struct Keyword<'a> {
pub key: Key<'a>,
pub value: &'a str,
}
let bytes = text.as_bytes();
let (key, off) = memchr2(b':', b'[', bytes)
.filter(|&i| {
bytes[2..i]
.iter()
.all(|&c| c.is_ascii_alphabetic() || c == b'_')
})
.map(|i| (&text[2..i], i + 1))?;
let (option, off) = if bytes[off - 1] == b'[' {
memchr(b']', bytes)
.filter(|&i| {
bytes[off..i].iter().all(|&c| c != b'\n') && i < text.len() && bytes[i + 1] == b':'
})
.map(|i| (Some(&text[off..i]), i + 2 /* ]: */))?
} else {
(None, off)
};
let (value, off) = memchr(b'\n', bytes)
.map(|i| (&text[off..i], i + 1))
.unwrap_or_else(|| (&text[off..], text.len()));
Some((
match &*key.to_uppercase() {
"AUTHOR" => Key::Author,
"CALL" => Key::Call,
"DATE" => Key::Date,
"HEADER" => Key::Header,
"NAME" => Key::Name,
"PLOT" => Key::Plot,
"TITLE" => Key::Title,
"RESULTS" => Key::Results { option },
"CAPTION" => Key::Caption { option },
k if k.starts_with("ATTR_") => Key::Attr {
backend: &key["ATTR_".len()..],
impl<'a> Keyword<'a> {
#[inline]
pub(crate) fn new(key: &'a str, option: Option<&'a str>, value: &'a str) -> Keyword<'a> {
Keyword {
key: match &*key.to_uppercase() {
"AUTHOR" => Key::Author,
"DATE" => Key::Date,
"HEADER" => Key::Header,
"NAME" => Key::Name,
"PLOT" => Key::Plot,
"TITLE" => Key::Title,
"RESULTS" => Key::Results { option },
"CAPTION" => Key::Caption { option },
k => {
if k.starts_with("ATTR_") {
Key::Attr {
backend: &key["ATTR_".len()..],
}
} else {
Key::Custom(key)
}
}
},
_ => Key::Custom(key),
},
value.trim(),
off,
))
}
value,
}
}
#[cfg(test)]
mod tests {
#[test]
fn parse() {
use super::*;
#[inline]
pub(crate) fn parse(text: &str) -> Option<(&str, Option<&str>, &str, usize)> {
debug_assert!(text.starts_with("#+"));
assert_eq!(
parse("#+KEY:"),
Some((Key::Custom("KEY"), "", "#+KEY:".len()))
);
assert_eq!(
parse("#+KEY: VALUE"),
Some((Key::Custom("KEY"), "VALUE", "#+KEY: VALUE".len()))
);
assert_eq!(
parse("#+K_E_Y: VALUE"),
Some((Key::Custom("K_E_Y"), "VALUE", "#+K_E_Y: VALUE".len()))
);
assert_eq!(
parse("#+KEY:VALUE\n"),
Some((Key::Custom("KEY"), "VALUE", "#+KEY:VALUE\n".len()))
);
assert_eq!(parse("#+KE Y: VALUE"), None);
assert_eq!(parse("#+ KEY: VALUE"), None);
let bytes = text.as_bytes();
assert_eq!(
parse("#+RESULTS:"),
Some((Key::Results { option: None }, "", "#+RESULTS:".len()))
);
let (key, off) = memchr2(b':', b'[', bytes)
.filter(|&i| {
bytes[2..i]
.iter()
.all(|&c| c.is_ascii_alphabetic() || c == b'_')
})
.map(|i| (&text[2..i], i + 1))?;
assert_eq!(
parse("#+ATTR_LATEX: :width 5cm"),
Some((
Key::Attr { backend: "LATEX" },
":width 5cm",
"#+ATTR_LATEX: :width 5cm".len()
))
);
let (option, off) = if bytes[off - 1] == b'[' {
memchr(b']', bytes)
.filter(|&i| {
bytes[off..i].iter().all(|&c| c != b'\n')
&& i < text.len()
&& bytes[i + 1] == b':'
})
.map(|i| (Some(&text[off..i]), i + "]:".len()))?
} else {
(None, off)
};
assert_eq!(
parse("#+CALL: double(n=4)"),
Some((Key::Call, "double(n=4)", "#+CALL: double(n=4)".len()))
);
let (value, off) = memchr(b'\n', bytes)
.map(|i| (&text[off..i], i + 1))
.unwrap_or_else(|| (&text[off..], text.len()));
assert_eq!(
parse("#+CAPTION[Short caption]: Longer caption."),
Some((
Key::Caption {
option: Some("Short caption")
},
"Longer caption.",
"#+CAPTION[Short caption]: Longer caption.".len()
))
);
Some((key, option, value.trim(), off))
}
}
#[test]
fn parse() {
assert_eq!(
Keyword::parse("#+KEY:"),
Some(("KEY", None, "", "#+KEY:".len()))
);
assert_eq!(
Keyword::parse("#+KEY: VALUE"),
Some(("KEY", None, "VALUE", "#+KEY: VALUE".len()))
);
assert_eq!(
Keyword::parse("#+K_E_Y: VALUE"),
Some(("K_E_Y", None, "VALUE", "#+K_E_Y: VALUE".len()))
);
assert_eq!(
Keyword::parse("#+KEY:VALUE\n"),
Some(("KEY", None, "VALUE", "#+KEY:VALUE\n".len()))
);
assert_eq!(Keyword::parse("#+KE Y: VALUE"), None);
assert_eq!(Keyword::parse("#+ KEY: VALUE"), None);
assert_eq!(
Keyword::parse("#+RESULTS:"),
Some(("RESULTS", None, "", "#+RESULTS:".len()))
);
assert_eq!(
Keyword::parse("#+ATTR_LATEX: :width 5cm"),
Some((
"ATTR_LATEX",
None,
":width 5cm",
"#+ATTR_LATEX: :width 5cm".len()
))
);
assert_eq!(
Keyword::parse("#+CALL: double(n=4)"),
Some(("CALL", None, "double(n=4)", "#+CALL: double(n=4)".len()))
);
assert_eq!(
Keyword::parse("#+CAPTION[Short caption]: Longer caption."),
Some((
"CAPTION",
Some("Short caption"),
"Longer caption.",
"#+CAPTION[Short caption]: Longer caption.".len()
))
);
}

View file

@ -12,5 +12,5 @@ pub(crate) mod planning;
pub(crate) mod rule;
pub use self::clock::Clock;
pub use self::keyword::Key;
pub use self::keyword::{Key, Keyword};
pub use self::planning::Planning;

View file

@ -1,4 +1,4 @@
use crate::objects::timestamp::Timestamp;
use crate::objects::Timestamp;
use memchr::memchr;
/// palnning elements
@ -58,31 +58,27 @@ impl<'a> Planning<'a> {
}
}
#[cfg(test)]
mod tests {
#[test]
fn prase() {
use super::Planning;
use crate::objects::timestamp::{Datetime, Timestamp};
#[test]
fn prase() {
use crate::objects::Datetime;
assert_eq!(
Planning::parse("SCHEDULED: <2019-04-08 Mon>\n"),
Some((
Planning {
scheduled: Some(Timestamp::Active {
start: Datetime {
date: (2019, 4, 8),
time: None,
dayname: "Mon"
},
repeater: None,
delay: None
}),
closed: None,
deadline: None,
},
"SCHEDULED: <2019-04-08 Mon>\n".len()
))
)
}
assert_eq!(
Planning::parse("SCHEDULED: <2019-04-08 Mon>\n"),
Some((
Planning {
scheduled: Some(Timestamp::Active {
start: Datetime {
date: "2019-04-08",
time: None,
dayname: "Mon"
},
repeater: None,
delay: None
}),
closed: None,
deadline: None,
},
"SCHEDULED: <2019-04-08 Mon>\n".len()
))
)
}

View file

@ -1,11 +1,11 @@
#[inline]
pub fn parse(src: &str) -> usize {
let end = memchr::memchr(b'\n', src.as_bytes())
.map(|i| i + 1)
.unwrap_or_else(|| src.len());
let rules = &src[0..end].trim();
if rules.len() >= 5 && rules.chars().all(|c| c == '-') {
end
pub fn parse(text: &str) -> usize {
let (text, off) = memchr::memchr(b'\n', text.as_bytes())
.map(|i| (text[..i].trim(), i + 1))
.unwrap_or_else(|| (text.trim(), text.len()));
if text.len() >= 5 && text.as_bytes().iter().all(|&c| c == b'-') {
off
} else {
0
}