refactor(parser): wrap some objects and elements with struct
This commit is contained in:
parent
69534576f1
commit
56e289fb48
22 changed files with 1055 additions and 875 deletions
|
|
@ -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()
|
||||
))
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()))?;
|
||||
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
))
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
))
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue