refactor(parser): wrap some objects and elements with struct
This commit is contained in:
parent
69534576f1
commit
56e289fb48
|
@ -1,4 +1,4 @@
|
||||||
use crate::objects::timestamp::{Datetime, Delay, Repeater, Timestamp};
|
use crate::objects::timestamp::{Datetime, Timestamp};
|
||||||
use memchr::memchr;
|
use memchr::memchr;
|
||||||
|
|
||||||
/// clock elements
|
/// clock elements
|
||||||
|
@ -11,15 +11,15 @@ pub enum Clock<'a> {
|
||||||
Closed {
|
Closed {
|
||||||
start: Datetime<'a>,
|
start: Datetime<'a>,
|
||||||
end: Datetime<'a>,
|
end: Datetime<'a>,
|
||||||
repeater: Option<Repeater>,
|
repeater: Option<&'a str>,
|
||||||
delay: Option<Delay>,
|
delay: Option<&'a str>,
|
||||||
duration: &'a str,
|
duration: &'a str,
|
||||||
},
|
},
|
||||||
/// running Clock
|
/// running Clock
|
||||||
Running {
|
Running {
|
||||||
start: Datetime<'a>,
|
start: Datetime<'a>,
|
||||||
repeater: Option<Repeater>,
|
repeater: Option<&'a str>,
|
||||||
delay: Option<Delay>,
|
delay: Option<&'a str>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,60 +37,57 @@ impl<'a> Clock<'a> {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
match Timestamp::parse_inactive(tail).map(|(t, off)| (t, tail[off..].trim_start())) {
|
let (timestamp, tail) =
|
||||||
Some((
|
Timestamp::parse_inactive(tail).map(|(t, off)| (t, tail[off..].trim_start()))?;
|
||||||
Timestamp::InactiveRange {
|
|
||||||
start,
|
match timestamp {
|
||||||
end,
|
Timestamp::InactiveRange {
|
||||||
repeater,
|
start,
|
||||||
delay,
|
end,
|
||||||
},
|
repeater,
|
||||||
tail,
|
delay,
|
||||||
)) => {
|
} if tail.starts_with("=>") => {
|
||||||
if tail.starts_with("=>") {
|
let duration = &tail[3..].trim();
|
||||||
let duration = &tail[3..].trim();
|
let colon = memchr(b':', duration.as_bytes())?;
|
||||||
let colon = memchr(b':', duration.as_bytes())?;
|
if duration.as_bytes()[0..colon].iter().all(u8::is_ascii_digit)
|
||||||
if duration.as_bytes()[0..colon].iter().all(u8::is_ascii_digit)
|
&& colon == duration.len() - 3
|
||||||
&& colon == duration.len() - 3
|
&& duration.as_bytes()[colon + 1].is_ascii_digit()
|
||||||
&& duration.as_bytes()[colon + 1].is_ascii_digit()
|
&& duration.as_bytes()[colon + 2].is_ascii_digit()
|
||||||
&& duration.as_bytes()[colon + 2].is_ascii_digit()
|
{
|
||||||
{
|
Some((
|
||||||
return Some((
|
Clock::Closed {
|
||||||
Clock::Closed {
|
start,
|
||||||
start,
|
end,
|
||||||
end,
|
repeater,
|
||||||
repeater,
|
delay,
|
||||||
delay,
|
duration,
|
||||||
duration,
|
},
|
||||||
},
|
off,
|
||||||
off,
|
))
|
||||||
));
|
} else {
|
||||||
}
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some((
|
Timestamp::Inactive {
|
||||||
Timestamp::Inactive {
|
start,
|
||||||
start,
|
repeater,
|
||||||
repeater,
|
delay,
|
||||||
delay,
|
} => {
|
||||||
},
|
|
||||||
tail,
|
|
||||||
)) => {
|
|
||||||
if tail.as_bytes().iter().all(u8::is_ascii_whitespace) {
|
if tail.as_bytes().iter().all(u8::is_ascii_whitespace) {
|
||||||
return Some((
|
Some((
|
||||||
Clock::Running {
|
Clock::Running {
|
||||||
start,
|
start,
|
||||||
repeater,
|
repeater,
|
||||||
delay,
|
delay,
|
||||||
},
|
},
|
||||||
off,
|
off,
|
||||||
));
|
))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => None,
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// returns `true` if the clock is running
|
/// returns `true` if the clock is running
|
||||||
|
@ -146,48 +143,42 @@ impl<'a> Clock<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[test]
|
||||||
mod tests {
|
fn parse() {
|
||||||
use super::Clock;
|
assert_eq!(
|
||||||
use crate::objects::timestamp::Datetime;
|
Clock::parse("CLOCK: [2003-09-16 Tue 09:39]"),
|
||||||
|
Some((
|
||||||
#[test]
|
Clock::Running {
|
||||||
fn parse() {
|
start: Datetime {
|
||||||
assert_eq!(
|
date: "2003-09-16",
|
||||||
Clock::parse("CLOCK: [2003-09-16 Tue 09:39]"),
|
time: Some("09:39"),
|
||||||
Some((
|
dayname: "Tue"
|
||||||
Clock::Running {
|
|
||||||
start: Datetime {
|
|
||||||
date: (2003, 9, 16),
|
|
||||||
time: Some((9, 39)),
|
|
||||||
dayname: "Tue"
|
|
||||||
},
|
|
||||||
repeater: None,
|
|
||||||
delay: None,
|
|
||||||
},
|
},
|
||||||
"CLOCK: [2003-09-16 Tue 09:39]".len()
|
repeater: None,
|
||||||
))
|
delay: None,
|
||||||
);
|
},
|
||||||
assert_eq!(
|
"CLOCK: [2003-09-16 Tue 09:39]".len()
|
||||||
Clock::parse("CLOCK: [2003-09-16 Tue 09:39]--[2003-09-16 Tue 10:39] => 1:00"),
|
))
|
||||||
Some((
|
);
|
||||||
Clock::Closed {
|
assert_eq!(
|
||||||
start: Datetime {
|
Clock::parse("CLOCK: [2003-09-16 Tue 09:39]--[2003-09-16 Tue 10:39] => 1:00"),
|
||||||
date: (2003, 9, 16),
|
Some((
|
||||||
time: Some((9, 39)),
|
Clock::Closed {
|
||||||
dayname: "Tue"
|
start: Datetime {
|
||||||
},
|
date: "2003-09-16",
|
||||||
end: Datetime {
|
time: Some("09:39"),
|
||||||
date: (2003, 9, 16),
|
dayname: "Tue"
|
||||||
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()
|
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)> {
|
pub fn parse(text: &str) -> Option<(&str, Option<&str>, usize, usize, usize)> {
|
||||||
debug_assert!(text.starts_with("#+"));
|
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;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,9 +15,15 @@ pub fn parse(text: &str) -> Option<(&str, Option<&str>, usize, usize, usize)> {
|
||||||
let (name, para, off) = lines
|
let (name, para, off) = lines
|
||||||
.next()
|
.next()
|
||||||
.map(|i| {
|
.map(|i| {
|
||||||
memchr(b' ', &bytes[9..i])
|
memchr(b' ', &bytes["#+BEGIN: ".len()..i])
|
||||||
.map(|x| (&text[9..9 + x], Some(text[9 + x..i].trim()), i + 1))
|
.map(|x| {
|
||||||
.unwrap_or((&text[9..i], None, i + 1))
|
(
|
||||||
|
&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()))?;
|
.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())
|
let (label, off) = memchr(b']', text.as_bytes())
|
||||||
.filter(|&i| {
|
.filter(|&i| {
|
||||||
i != 4
|
i != 4
|
||||||
&& text.as_bytes()[4..i]
|
&& text.as_bytes()["[fn:".len()..i]
|
||||||
.iter()
|
.iter()
|
||||||
.all(|&c| c.is_ascii_alphanumeric() || c == b'-' || c == b'_')
|
.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())
|
let (content, off) = memchr(b'\n', text.as_bytes())
|
||||||
.map(|i| (&text[off..i], i))
|
.map(|i| (&text[off..i], i))
|
||||||
|
|
|
@ -17,112 +17,124 @@ pub enum Key<'a> {
|
||||||
Date,
|
Date,
|
||||||
Title,
|
Title,
|
||||||
Custom(&'a str),
|
Custom(&'a str),
|
||||||
|
|
||||||
// Babel Call
|
|
||||||
Call,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse(text: &str) -> Option<(Key<'_>, &str, usize)> {
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
debug_assert!(text.starts_with("#+"));
|
#[derive(Debug)]
|
||||||
|
pub struct Keyword<'a> {
|
||||||
|
pub key: Key<'a>,
|
||||||
|
pub value: &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
let bytes = text.as_bytes();
|
impl<'a> Keyword<'a> {
|
||||||
|
#[inline]
|
||||||
let (key, off) = memchr2(b':', b'[', bytes)
|
pub(crate) fn new(key: &'a str, option: Option<&'a str>, value: &'a str) -> Keyword<'a> {
|
||||||
.filter(|&i| {
|
Keyword {
|
||||||
bytes[2..i]
|
key: match &*key.to_uppercase() {
|
||||||
.iter()
|
"AUTHOR" => Key::Author,
|
||||||
.all(|&c| c.is_ascii_alphabetic() || c == b'_')
|
"DATE" => Key::Date,
|
||||||
})
|
"HEADER" => Key::Header,
|
||||||
.map(|i| (&text[2..i], i + 1))?;
|
"NAME" => Key::Name,
|
||||||
|
"PLOT" => Key::Plot,
|
||||||
let (option, off) = if bytes[off - 1] == b'[' {
|
"TITLE" => Key::Title,
|
||||||
memchr(b']', bytes)
|
"RESULTS" => Key::Results { option },
|
||||||
.filter(|&i| {
|
"CAPTION" => Key::Caption { option },
|
||||||
bytes[off..i].iter().all(|&c| c != b'\n') && i < text.len() && bytes[i + 1] == b':'
|
k => {
|
||||||
})
|
if k.starts_with("ATTR_") {
|
||||||
.map(|i| (Some(&text[off..i]), i + 2 /* ]: */))?
|
Key::Attr {
|
||||||
} else {
|
backend: &key["ATTR_".len()..],
|
||||||
(None, off)
|
}
|
||||||
};
|
} else {
|
||||||
|
Key::Custom(key)
|
||||||
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()..],
|
|
||||||
},
|
},
|
||||||
_ => Key::Custom(key),
|
value,
|
||||||
},
|
}
|
||||||
value.trim(),
|
}
|
||||||
off,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[inline]
|
||||||
mod tests {
|
pub(crate) fn parse(text: &str) -> Option<(&str, Option<&str>, &str, usize)> {
|
||||||
#[test]
|
debug_assert!(text.starts_with("#+"));
|
||||||
fn parse() {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
assert_eq!(
|
let bytes = text.as_bytes();
|
||||||
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);
|
|
||||||
|
|
||||||
assert_eq!(
|
let (key, off) = memchr2(b':', b'[', bytes)
|
||||||
parse("#+RESULTS:"),
|
.filter(|&i| {
|
||||||
Some((Key::Results { option: None }, "", "#+RESULTS:".len()))
|
bytes[2..i]
|
||||||
);
|
.iter()
|
||||||
|
.all(|&c| c.is_ascii_alphabetic() || c == b'_')
|
||||||
|
})
|
||||||
|
.map(|i| (&text[2..i], i + 1))?;
|
||||||
|
|
||||||
assert_eq!(
|
let (option, off) = if bytes[off - 1] == b'[' {
|
||||||
parse("#+ATTR_LATEX: :width 5cm"),
|
memchr(b']', bytes)
|
||||||
Some((
|
.filter(|&i| {
|
||||||
Key::Attr { backend: "LATEX" },
|
bytes[off..i].iter().all(|&c| c != b'\n')
|
||||||
":width 5cm",
|
&& i < text.len()
|
||||||
"#+ATTR_LATEX: :width 5cm".len()
|
&& bytes[i + 1] == b':'
|
||||||
))
|
})
|
||||||
);
|
.map(|i| (Some(&text[off..i]), i + "]:".len()))?
|
||||||
|
} else {
|
||||||
|
(None, off)
|
||||||
|
};
|
||||||
|
|
||||||
assert_eq!(
|
let (value, off) = memchr(b'\n', bytes)
|
||||||
parse("#+CALL: double(n=4)"),
|
.map(|i| (&text[off..i], i + 1))
|
||||||
Some((Key::Call, "double(n=4)", "#+CALL: double(n=4)".len()))
|
.unwrap_or_else(|| (&text[off..], text.len()));
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
Some((key, option, value.trim(), off))
|
||||||
parse("#+CAPTION[Short caption]: Longer caption."),
|
|
||||||
Some((
|
|
||||||
Key::Caption {
|
|
||||||
option: Some("Short caption")
|
|
||||||
},
|
|
||||||
"Longer caption.",
|
|
||||||
"#+CAPTION[Short caption]: Longer caption.".len()
|
|
||||||
))
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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(crate) mod rule;
|
||||||
|
|
||||||
pub use self::clock::Clock;
|
pub use self::clock::Clock;
|
||||||
pub use self::keyword::Key;
|
pub use self::keyword::{Key, Keyword};
|
||||||
pub use self::planning::Planning;
|
pub use self::planning::Planning;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::objects::timestamp::Timestamp;
|
use crate::objects::Timestamp;
|
||||||
use memchr::memchr;
|
use memchr::memchr;
|
||||||
|
|
||||||
/// palnning elements
|
/// palnning elements
|
||||||
|
@ -58,31 +58,27 @@ impl<'a> Planning<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[test]
|
||||||
mod tests {
|
fn prase() {
|
||||||
#[test]
|
use crate::objects::Datetime;
|
||||||
fn prase() {
|
|
||||||
use super::Planning;
|
|
||||||
use crate::objects::timestamp::{Datetime, Timestamp};
|
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Planning::parse("SCHEDULED: <2019-04-08 Mon>\n"),
|
Planning::parse("SCHEDULED: <2019-04-08 Mon>\n"),
|
||||||
Some((
|
Some((
|
||||||
Planning {
|
Planning {
|
||||||
scheduled: Some(Timestamp::Active {
|
scheduled: Some(Timestamp::Active {
|
||||||
start: Datetime {
|
start: Datetime {
|
||||||
date: (2019, 4, 8),
|
date: "2019-04-08",
|
||||||
time: None,
|
time: None,
|
||||||
dayname: "Mon"
|
dayname: "Mon"
|
||||||
},
|
},
|
||||||
repeater: None,
|
repeater: None,
|
||||||
delay: None
|
delay: None
|
||||||
}),
|
}),
|
||||||
closed: None,
|
closed: None,
|
||||||
deadline: None,
|
deadline: None,
|
||||||
},
|
},
|
||||||
"SCHEDULED: <2019-04-08 Mon>\n".len()
|
"SCHEDULED: <2019-04-08 Mon>\n".len()
|
||||||
))
|
))
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn parse(src: &str) -> usize {
|
pub fn parse(text: &str) -> usize {
|
||||||
let end = memchr::memchr(b'\n', src.as_bytes())
|
let (text, off) = memchr::memchr(b'\n', text.as_bytes())
|
||||||
.map(|i| i + 1)
|
.map(|i| (text[..i].trim(), i + 1))
|
||||||
.unwrap_or_else(|| src.len());
|
.unwrap_or_else(|| (text.trim(), text.len()));
|
||||||
let rules = &src[0..end].trim();
|
|
||||||
if rules.len() >= 5 && rules.chars().all(|c| c == '-') {
|
if text.len() >= 5 && text.as_bytes().iter().all(|&c| c == b'-') {
|
||||||
end
|
off
|
||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ pub trait HtmlHandler<W: Write, E: From<Error>> {
|
||||||
fn headline_beg(&mut self, w: &mut W, hdl: Headline) -> Result<(), E> {
|
fn headline_beg(&mut self, w: &mut W, hdl: Headline) -> Result<(), E> {
|
||||||
let level = if hdl.level <= 6 { hdl.level } else { 6 };
|
let level = if hdl.level <= 6 { hdl.level } else { 6 };
|
||||||
write!(w, "<h{}>", level)?;
|
write!(w, "<h{}>", level)?;
|
||||||
self.escape(w, hdl.title)?;
|
self.text(w, hdl.title)?;
|
||||||
write!(w, "</h{}>", level)?;
|
write!(w, "</h{}>", level)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -155,7 +155,7 @@ pub trait HtmlHandler<W: Write, E: From<Error>> {
|
||||||
fn fn_def(&mut self, w: &mut W, label: &str, cont: &str) -> Result<(), E> {
|
fn fn_def(&mut self, w: &mut W, label: &str, cont: &str) -> Result<(), E> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
fn keyword(&mut self, w: &mut W, key: Key<'_>, value: &str) -> Result<(), E> {
|
fn keyword(&mut self, w: &mut W, keyword: Keyword<'_>) -> Result<(), E> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
fn rule(&mut self, w: &mut W) -> Result<(), E> {
|
fn rule(&mut self, w: &mut W) -> Result<(), E> {
|
||||||
|
@ -164,42 +164,35 @@ pub trait HtmlHandler<W: Write, E: From<Error>> {
|
||||||
fn cookie(&mut self, w: &mut W, cookie: Cookie) -> Result<(), E> {
|
fn cookie(&mut self, w: &mut W, cookie: Cookie) -> Result<(), E> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
fn fn_ref(&mut self, w: &mut W, label: Option<&str>, def: Option<&str>) -> Result<(), E> {
|
fn fn_ref(&mut self, w: &mut W, fn_ref: FnRef<'_>) -> Result<(), E> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
fn inline_call(
|
fn inline_call(&mut self, w: &mut W, call: InlineCall<'_>) -> Result<(), E> {
|
||||||
&mut self,
|
|
||||||
w: &mut W,
|
|
||||||
name: &str,
|
|
||||||
args: &str,
|
|
||||||
inside_header: Option<&str>,
|
|
||||||
end_header: Option<&str>,
|
|
||||||
) -> Result<(), E> {
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
fn inline_src(&mut self, w: &mut W, _: &str, _: Option<&str>, body: &str) -> Result<(), E> {
|
fn inline_src(&mut self, w: &mut W, src: InlineSrc<'_>) -> Result<(), E> {
|
||||||
write!(w, "<code>")?;
|
write!(w, "<code>")?;
|
||||||
self.escape(w, body)?;
|
self.text(w, src.body)?;
|
||||||
write!(w, "</code>")?;
|
write!(w, "</code>")?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
fn link(&mut self, w: &mut W, path: &str, desc: Option<&str>) -> Result<(), E> {
|
fn link(&mut self, w: &mut W, link: Link<'_>) -> Result<(), E> {
|
||||||
write!(w, r#"<a href=""#)?;
|
write!(w, r#"<a href=""#)?;
|
||||||
self.escape(w, path)?;
|
self.text(w, link.path)?;
|
||||||
write!(w, r#"">"#)?;
|
write!(w, r#"">"#)?;
|
||||||
self.escape(w, desc.unwrap_or(path))?;
|
self.text(w, link.desc.unwrap_or(link.path))?;
|
||||||
write!(w, "</a>")?;
|
write!(w, "</a>")?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
fn macros(&mut self, w: &mut W, name: &str, args: Option<&str>) -> Result<(), E> {
|
fn macros(&mut self, w: &mut W, macros: Macros<'_>) -> Result<(), E> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
fn radio_target(&mut self, w: &mut W, target: &str) -> Result<(), E> {
|
fn radio_target(&mut self, w: &mut W, target: &str) -> Result<(), E> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
fn snippet(&mut self, w: &mut W, name: &str, value: &str) -> Result<(), E> {
|
fn snippet(&mut self, w: &mut W, snippet: Snippet<'_>) -> Result<(), E> {
|
||||||
if name.eq_ignore_ascii_case("HTML") {
|
if snippet.name.eq_ignore_ascii_case("HTML") {
|
||||||
Ok(write!(w, "{}", value)?)
|
Ok(write!(w, "{}", snippet.value)?)
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -236,13 +229,13 @@ pub trait HtmlHandler<W: Write, E: From<Error>> {
|
||||||
}
|
}
|
||||||
fn verbatim(&mut self, w: &mut W, cont: &str) -> Result<(), E> {
|
fn verbatim(&mut self, w: &mut W, cont: &str) -> Result<(), E> {
|
||||||
write!(w, "<code>")?;
|
write!(w, "<code>")?;
|
||||||
self.escape(w, cont)?;
|
self.text(w, cont)?;
|
||||||
write!(w, "</code>")?;
|
write!(w, "</code>")?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
fn code(&mut self, w: &mut W, cont: &str) -> Result<(), E> {
|
fn code(&mut self, w: &mut W, cont: &str) -> Result<(), E> {
|
||||||
write!(w, "<code>")?;
|
write!(w, "<code>")?;
|
||||||
self.escape(w, cont)?;
|
self.text(w, cont)?;
|
||||||
write!(w, "</code>")?;
|
write!(w, "</code>")?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,21 +40,16 @@ macro_rules! handle_event {
|
||||||
TableCell => $handler.table_cell($writer)?,
|
TableCell => $handler.table_cell($writer)?,
|
||||||
LatexEnv => $handler.latex_env($writer)?,
|
LatexEnv => $handler.latex_env($writer)?,
|
||||||
FnDef { label, cont } => $handler.fn_def($writer, label, cont)?,
|
FnDef { label, cont } => $handler.fn_def($writer, label, cont)?,
|
||||||
Keyword { key, value } => $handler.keyword($writer, key, value)?,
|
Keyword(keyword) => $handler.keyword($writer, keyword)?,
|
||||||
Rule => $handler.rule($writer)?,
|
Rule => $handler.rule($writer)?,
|
||||||
Cookie(cookie) => $handler.cookie($writer, cookie)?,
|
Cookie(cookie) => $handler.cookie($writer, cookie)?,
|
||||||
FnRef { label, def } => $handler.fn_ref($writer, label, def)?,
|
FnRef(fn_ref) => $handler.fn_ref($writer, fn_ref)?,
|
||||||
InlineSrc { lang, option, body } => $handler.inline_src($writer, lang, option, body)?,
|
InlineSrc(src) => $handler.inline_src($writer, src)?,
|
||||||
InlineCall {
|
InlineCall(call) => $handler.inline_call($writer, call)?,
|
||||||
name,
|
Link(link) => $handler.link($writer, link)?,
|
||||||
args,
|
Macros(macros) => $handler.macros($writer, macros)?,
|
||||||
inside_header,
|
|
||||||
end_header,
|
|
||||||
} => $handler.inline_call($writer, name, args, inside_header, end_header)?,
|
|
||||||
Link { path, desc } => $handler.link($writer, path, desc)?,
|
|
||||||
Macros { name, args } => $handler.macros($writer, name, args)?,
|
|
||||||
RadioTarget { target } => $handler.radio_target($writer, target)?,
|
RadioTarget { target } => $handler.radio_target($writer, target)?,
|
||||||
Snippet { name, value } => $handler.snippet($writer, name, value)?,
|
Snippet(snippet) => $handler.snippet($writer, snippet)?,
|
||||||
Target { target } => $handler.target($writer, target)?,
|
Target { target } => $handler.target($writer, target)?,
|
||||||
BoldBeg => $handler.bold_beg($writer)?,
|
BoldBeg => $handler.bold_beg($writer)?,
|
||||||
BoldEnd => $handler.bold_end($writer)?,
|
BoldEnd => $handler.bold_end($writer)?,
|
||||||
|
|
|
@ -7,45 +7,59 @@ pub enum Cookie<'a> {
|
||||||
Slash(&'a str, &'a str),
|
Slash(&'a str, &'a str),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
impl<'a> Cookie<'a> {
|
||||||
pub fn parse(src: &str) -> Option<(Cookie<'_>, usize)> {
|
#[inline]
|
||||||
debug_assert!(src.starts_with('['));
|
pub(crate) fn parse(src: &str) -> Option<(Cookie<'_>, usize)> {
|
||||||
|
debug_assert!(src.starts_with('['));
|
||||||
|
|
||||||
let bytes = src.as_bytes();
|
let bytes = src.as_bytes();
|
||||||
let num1 =
|
let num1 =
|
||||||
memchr2(b'%', b'/', bytes).filter(|&i| bytes[1..i].iter().all(u8::is_ascii_digit))?;
|
memchr2(b'%', b'/', bytes).filter(|&i| bytes[1..i].iter().all(u8::is_ascii_digit))?;
|
||||||
|
|
||||||
if bytes[num1] == b'%' && *bytes.get(num1 + 1)? == b']' {
|
if bytes[num1] == b'%' && *bytes.get(num1 + 1)? == b']' {
|
||||||
Some((Cookie::Percent(&src[1..num1]), num1 + 2))
|
Some((Cookie::Percent(&src[1..num1]), num1 + 2))
|
||||||
} else {
|
} else {
|
||||||
let num2 =
|
let num2 = memchr(b']', bytes)
|
||||||
memchr(b']', bytes).filter(|&i| bytes[num1 + 1..i].iter().all(u8::is_ascii_digit))?;
|
.filter(|&i| bytes[num1 + 1..i].iter().all(u8::is_ascii_digit))?;
|
||||||
|
|
||||||
Some((Cookie::Slash(&src[1..num1], &src[num1 + 1..num2]), num2 + 1))
|
Some((Cookie::Slash(&src[1..num1], &src[num1 + 1..num2]), num2 + 1))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[test]
|
||||||
mod tests {
|
fn parse() {
|
||||||
#[test]
|
assert_eq!(
|
||||||
fn parse() {
|
Cookie::parse("[1/10]"),
|
||||||
use super::parse;
|
Some((Cookie::Slash("1", "10"), "[1/10]".len()))
|
||||||
use super::Cookie::*;
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Cookie::parse("[1/1000]"),
|
||||||
|
Some((Cookie::Slash("1", "1000"), "[1/1000]".len()))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Cookie::parse("[10%]"),
|
||||||
|
Some((Cookie::Percent("10"), "[10%]".len()))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Cookie::parse("[%]"),
|
||||||
|
Some((Cookie::Percent(""), "[%]".len()))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Cookie::parse("[/]"),
|
||||||
|
Some((Cookie::Slash("", ""), "[/]".len()))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Cookie::parse("[100/]"),
|
||||||
|
Some((Cookie::Slash("100", ""), "[100/]".len()))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Cookie::parse("[/100]"),
|
||||||
|
Some((Cookie::Slash("", "100"), "[/100]".len()))
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(parse("[1/10]"), Some((Slash("1", "10"), "[1/10]".len())));
|
assert_eq!(Cookie::parse("[10% ]"), None);
|
||||||
assert_eq!(
|
assert_eq!(Cookie::parse("[1//100]"), None);
|
||||||
parse("[1/1000]"),
|
assert_eq!(Cookie::parse("[1\\100]"), None);
|
||||||
Some((Slash("1", "1000"), "[1/1000]".len()))
|
assert_eq!(Cookie::parse("[10%%]"), None);
|
||||||
);
|
|
||||||
assert_eq!(parse("[10%]"), Some((Percent("10"), "[10%]".len())));
|
|
||||||
assert_eq!(parse("[%]"), Some((Percent(""), "[%]".len())));
|
|
||||||
assert_eq!(parse("[/]"), Some((Slash("", ""), "[/]".len())));
|
|
||||||
assert_eq!(parse("[100/]"), Some((Slash("100", ""), "[100/]".len())));
|
|
||||||
assert_eq!(parse("[/100]"), Some((Slash("", "100"), "[/100]".len())));
|
|
||||||
|
|
||||||
assert_eq!(parse("[10% ]"), None);
|
|
||||||
assert_eq!(parse("[1//100]"), None);
|
|
||||||
assert_eq!(parse("[1\\100]"), None);
|
|
||||||
assert_eq!(parse("[10%%]"), None);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,54 +1,96 @@
|
||||||
use memchr::{memchr2, memchr2_iter};
|
use memchr::{memchr2, memchr2_iter};
|
||||||
|
|
||||||
/// returns (footnote reference label, footnote reference definition, offset)
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
#[inline]
|
#[derive(Debug)]
|
||||||
pub fn parse(text: &str) -> Option<(Option<&str>, Option<&str>, usize)> {
|
pub struct FnRef<'a> {
|
||||||
debug_assert!(text.starts_with("[fn:"));
|
pub label: Option<&'a str>,
|
||||||
|
pub definition: Option<&'a str>,
|
||||||
let bytes = text.as_bytes();
|
|
||||||
let (label, off) = memchr2(b']', b':', &bytes[4..])
|
|
||||||
.filter(|&i| {
|
|
||||||
bytes[4..i + 4]
|
|
||||||
.iter()
|
|
||||||
.all(|&c| c.is_ascii_alphanumeric() || c == b'-' || c == b'_')
|
|
||||||
})
|
|
||||||
.map(|i| (if i == 0 { None } else { Some(&text[4..i + 4]) }, i + 4))?;
|
|
||||||
|
|
||||||
let (def, off) = if bytes[off] == b':' {
|
|
||||||
let mut pairs = 1;
|
|
||||||
memchr2_iter(b'[', b']', &bytes[off..])
|
|
||||||
.find(|&i| {
|
|
||||||
if bytes[i + off] == b'[' {
|
|
||||||
pairs += 1;
|
|
||||||
} else {
|
|
||||||
pairs -= 1;
|
|
||||||
}
|
|
||||||
pairs == 0
|
|
||||||
})
|
|
||||||
.map(|i| (Some(&text[off + 1..off + i]), i + off + 1))?
|
|
||||||
} else {
|
|
||||||
(None, off + 1)
|
|
||||||
};
|
|
||||||
|
|
||||||
Some((label, def, off))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
impl<'a> FnRef<'a> {
|
||||||
mod tests {
|
#[inline]
|
||||||
#[test]
|
pub fn parse(text: &str) -> Option<(FnRef<'_>, usize)> {
|
||||||
fn parse() {
|
debug_assert!(text.starts_with("[fn:"));
|
||||||
use super::parse;
|
|
||||||
|
|
||||||
assert_eq!(parse("[fn:1]"), Some((Some("1"), None, "[fn:1]".len())));
|
let bytes = text.as_bytes();
|
||||||
assert_eq!(
|
let (label, off) = memchr2(b']', b':', &bytes["[fn:".len()..])
|
||||||
parse("[fn:1:2]"),
|
.filter(|&i| {
|
||||||
Some((Some("1"), Some("2"), "[fn:1:2]".len()))
|
bytes["[fn:".len().."[fn:".len() + i]
|
||||||
);
|
.iter()
|
||||||
assert_eq!(parse("[fn::2]"), Some((None, Some("2"), "[fn::2]".len())));
|
.all(|&c| c.is_ascii_alphanumeric() || c == b'-' || c == b'_')
|
||||||
assert_eq!(
|
})
|
||||||
parse("[fn::[]]"),
|
.map(|i| {
|
||||||
Some((None, Some("[]"), "[fn::[]]".len()))
|
(
|
||||||
);
|
if i == 0 {
|
||||||
assert_eq!(parse("[fn::[]"), None);
|
None
|
||||||
|
} else {
|
||||||
|
Some(&text["[fn:".len().."[fn:".len() + i])
|
||||||
|
},
|
||||||
|
"[fn:".len() + i,
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let (definition, off) = if bytes[off] == b':' {
|
||||||
|
let mut pairs = 1;
|
||||||
|
memchr2_iter(b'[', b']', &bytes[off..])
|
||||||
|
.find(|&i| {
|
||||||
|
if bytes[i + off] == b'[' {
|
||||||
|
pairs += 1;
|
||||||
|
} else {
|
||||||
|
pairs -= 1;
|
||||||
|
}
|
||||||
|
pairs == 0
|
||||||
|
})
|
||||||
|
.map(|i| (Some(&text[off + 1..off + i]), i + off + 1))?
|
||||||
|
} else {
|
||||||
|
(None, off + 1)
|
||||||
|
};
|
||||||
|
|
||||||
|
Some((FnRef { label, definition }, off))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse() {
|
||||||
|
assert_eq!(
|
||||||
|
FnRef::parse("[fn:1]"),
|
||||||
|
Some((
|
||||||
|
FnRef {
|
||||||
|
label: Some("1"),
|
||||||
|
definition: None
|
||||||
|
},
|
||||||
|
"[fn:1]".len()
|
||||||
|
))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
FnRef::parse("[fn:1:2]"),
|
||||||
|
Some((
|
||||||
|
FnRef {
|
||||||
|
label: Some("1"),
|
||||||
|
definition: Some("2")
|
||||||
|
},
|
||||||
|
"[fn:1:2]".len()
|
||||||
|
))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
FnRef::parse("[fn::2]"),
|
||||||
|
Some((
|
||||||
|
FnRef {
|
||||||
|
label: None,
|
||||||
|
definition: Some("2")
|
||||||
|
},
|
||||||
|
"[fn::2]".len()
|
||||||
|
))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
FnRef::parse("[fn::[]]"),
|
||||||
|
Some((
|
||||||
|
FnRef {
|
||||||
|
label: None,
|
||||||
|
definition: Some("[]")
|
||||||
|
},
|
||||||
|
"[fn::[]]".len()
|
||||||
|
))
|
||||||
|
);
|
||||||
|
assert_eq!(FnRef::parse("[fn::[]"), None);
|
||||||
|
}
|
||||||
|
|
|
@ -1,80 +1,108 @@
|
||||||
use memchr::{memchr, memchr2};
|
use memchr::{memchr, memchr2};
|
||||||
|
|
||||||
// returns (name, args, inside_header, end_header)
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
#[inline]
|
#[derive(Debug)]
|
||||||
pub fn parse(text: &str) -> Option<(&str, &str, Option<&str>, Option<&str>, usize)> {
|
pub struct InlineCall<'a> {
|
||||||
debug_assert!(text.starts_with("call_"));
|
pub name: &'a str,
|
||||||
|
pub inside_header: Option<&'a str>,
|
||||||
let bytes = text.as_bytes();
|
pub args: &'a str,
|
||||||
|
pub end_header: Option<&'a str>,
|
||||||
let (name, off) = memchr2(b'[', b'(', bytes)
|
|
||||||
.map(|i| (&text[5..i], i))
|
|
||||||
.filter(|(name, _)| name.as_bytes().iter().all(u8::is_ascii_graphic))?;
|
|
||||||
|
|
||||||
let (inside_header, off) = if bytes[off] == b'[' {
|
|
||||||
memchr(b']', &bytes[off..])
|
|
||||||
.filter(|&i| {
|
|
||||||
bytes[off + i + 1] == b'(' && bytes[off + 1..off + i].iter().all(|&c| c != b'\n')
|
|
||||||
})
|
|
||||||
.map(|i| (Some(&text[off + 1..off + i]), off + i + 1))?
|
|
||||||
} else {
|
|
||||||
(None, off)
|
|
||||||
};
|
|
||||||
|
|
||||||
let (args, off) = memchr(b')', &bytes[off..])
|
|
||||||
.map(|i| (&text[off + 1..off + i], off + i + 1))
|
|
||||||
.filter(|(args, _)| args.as_bytes().iter().all(|&c| c != b'\n'))?;
|
|
||||||
|
|
||||||
let (end_header, off) = if text.len() > off && text.as_bytes()[off] == b'[' {
|
|
||||||
memchr(b']', &bytes[off..])
|
|
||||||
.filter(|&i| bytes[off + 1..off + i].iter().all(|&c| c != b'\n'))
|
|
||||||
.map(|i| (Some(&text[off + 1..off + i]), off + i + 1))?
|
|
||||||
} else {
|
|
||||||
(None, off)
|
|
||||||
};
|
|
||||||
|
|
||||||
Some((name, args, inside_header, end_header, off))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
impl<'a> InlineCall<'a> {
|
||||||
mod tests {
|
#[inline]
|
||||||
#[test]
|
pub(crate) fn parse(text: &str) -> Option<(InlineCall<'_>, usize)> {
|
||||||
fn parse() {
|
debug_assert!(text.starts_with("call_"));
|
||||||
use super::parse;
|
|
||||||
|
|
||||||
assert_eq!(
|
let bytes = text.as_bytes();
|
||||||
parse("call_square(4)"),
|
|
||||||
Some(("square", "4", None, None, "call_square(4)".len()))
|
let (name, off) = memchr2(b'[', b'(', bytes)
|
||||||
);
|
.map(|i| (&text["call_".len()..i], i))
|
||||||
assert_eq!(
|
.filter(|(name, _)| name.as_bytes().iter().all(u8::is_ascii_graphic))?;
|
||||||
parse("call_square[:results output](4)"),
|
|
||||||
Some((
|
let (inside_header, off) = if bytes[off] == b'[' {
|
||||||
"square",
|
memchr(b']', &bytes[off..])
|
||||||
"4",
|
.filter(|&i| {
|
||||||
Some(":results output"),
|
bytes[off + i + 1] == b'('
|
||||||
None,
|
&& bytes[off + 1..off + i].iter().all(|&c| c != b'\n')
|
||||||
"call_square[:results output](4)".len()
|
})
|
||||||
))
|
.map(|i| (Some(&text[off + 1..off + i]), off + i + 1))?
|
||||||
);
|
} else {
|
||||||
assert_eq!(
|
(None, off)
|
||||||
parse("call_square(4)[:results html]"),
|
};
|
||||||
Some((
|
|
||||||
"square",
|
let (args, off) = memchr(b')', &bytes[off..])
|
||||||
"4",
|
.map(|i| (&text[off + 1..off + i], off + i + 1))
|
||||||
None,
|
.filter(|(args, _)| args.as_bytes().iter().all(|&c| c != b'\n'))?;
|
||||||
Some(":results html"),
|
|
||||||
"call_square(4)[:results html]".len()
|
let (end_header, off) = if text.len() > off && text.as_bytes()[off] == b'[' {
|
||||||
))
|
memchr(b']', &bytes[off..])
|
||||||
);
|
.filter(|&i| bytes[off + 1..off + i].iter().all(|&c| c != b'\n'))
|
||||||
assert_eq!(
|
.map(|i| (Some(&text[off + 1..off + i]), off + i + 1))?
|
||||||
parse("call_square[:results output](4)[:results html]"),
|
} else {
|
||||||
Some((
|
(None, off)
|
||||||
"square",
|
};
|
||||||
"4",
|
|
||||||
Some(":results output"),
|
Some((
|
||||||
Some(":results html"),
|
InlineCall {
|
||||||
"call_square[:results output](4)[:results html]".len()
|
name,
|
||||||
))
|
args,
|
||||||
);
|
inside_header,
|
||||||
|
end_header,
|
||||||
|
},
|
||||||
|
off,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse() {
|
||||||
|
assert_eq!(
|
||||||
|
InlineCall::parse("call_square(4)"),
|
||||||
|
Some((
|
||||||
|
InlineCall {
|
||||||
|
name: "square",
|
||||||
|
args: "4",
|
||||||
|
inside_header: None,
|
||||||
|
end_header: None,
|
||||||
|
},
|
||||||
|
"call_square(4)".len()
|
||||||
|
))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
InlineCall::parse("call_square[:results output](4)"),
|
||||||
|
Some((
|
||||||
|
InlineCall {
|
||||||
|
name: "square",
|
||||||
|
args: "4",
|
||||||
|
inside_header: Some(":results output"),
|
||||||
|
end_header: None,
|
||||||
|
},
|
||||||
|
"call_square[:results output](4)".len()
|
||||||
|
))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
InlineCall::parse("call_square(4)[:results html]"),
|
||||||
|
Some((
|
||||||
|
InlineCall {
|
||||||
|
name: "square",
|
||||||
|
args: "4",
|
||||||
|
inside_header: None,
|
||||||
|
end_header: Some(":results html"),
|
||||||
|
},
|
||||||
|
"call_square(4)[:results html]".len()
|
||||||
|
))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
InlineCall::parse("call_square[:results output](4)[:results html]"),
|
||||||
|
Some((
|
||||||
|
InlineCall {
|
||||||
|
name: "square",
|
||||||
|
args: "4",
|
||||||
|
inside_header: Some(":results output"),
|
||||||
|
end_header: Some(":results html"),
|
||||||
|
},
|
||||||
|
"call_square[:results output](4)[:results html]".len()
|
||||||
|
))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -1,52 +1,71 @@
|
||||||
use memchr::{memchr, memchr2};
|
use memchr::{memchr, memchr2};
|
||||||
|
|
||||||
/// returns (language, option, body, offset)
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
#[inline]
|
#[derive(Debug)]
|
||||||
pub fn parse(text: &str) -> Option<(&str, Option<&str>, &str, usize)> {
|
pub struct InlineSrc<'a> {
|
||||||
debug_assert!(text.starts_with("src_"));
|
pub lang: &'a str,
|
||||||
|
pub option: Option<&'a str>,
|
||||||
let (lang, off) = memchr2(b'[', b'{', text.as_bytes())
|
pub body: &'a str,
|
||||||
.map(|i| (&text[4..i], i))
|
|
||||||
.filter(|(lang, off)| {
|
|
||||||
*off != 4 && lang.as_bytes().iter().all(|c| !c.is_ascii_whitespace())
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let (option, off) = if text.as_bytes()[off] == b'[' {
|
|
||||||
memchr(b']', text[off..].as_bytes())
|
|
||||||
.filter(|&i| text[off..off + i].as_bytes().iter().all(|c| *c != b'\n'))
|
|
||||||
.map(|i| (Some(&text[off + 1..off + i]), off + i + 1))?
|
|
||||||
} else {
|
|
||||||
(None, off)
|
|
||||||
};
|
|
||||||
|
|
||||||
let (body, off) = memchr(b'}', text[off..].as_bytes())
|
|
||||||
.map(|i| (&text[off + 1..off + i], off + i + 1))
|
|
||||||
.filter(|(body, _)| body.as_bytes().iter().all(|c| *c != b'\n'))?;
|
|
||||||
|
|
||||||
Some((lang, option, body, off))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
impl<'a> InlineSrc<'a> {
|
||||||
mod tests {
|
#[inline]
|
||||||
#[test]
|
pub(crate) fn parse(text: &str) -> Option<(InlineSrc<'_>, usize)> {
|
||||||
fn parse() {
|
debug_assert!(text.starts_with("src_"));
|
||||||
use super::parse;
|
|
||||||
|
|
||||||
assert_eq!(
|
let (lang, off) = memchr2(b'[', b'{', text.as_bytes())
|
||||||
parse("src_C{int a = 0;}"),
|
.map(|i| (&text["src_".len()..i], i))
|
||||||
Some(("C", None, "int a = 0;", "src_C{int a = 0;}".len()))
|
.filter(|(lang, off)| {
|
||||||
);
|
*off != 4 && lang.as_bytes().iter().all(|c| !c.is_ascii_whitespace())
|
||||||
assert_eq!(
|
})?;
|
||||||
parse("src_xml[:exports code]{<tag>text</tag>}"),
|
|
||||||
Some((
|
let (option, off) = if text.as_bytes()[off] == b'[' {
|
||||||
"xml",
|
memchr(b']', text[off..].as_bytes())
|
||||||
Some(":exports code"),
|
.filter(|&i| text[off..off + i].as_bytes().iter().all(|c| *c != b'\n'))
|
||||||
"<tag>text</tag>",
|
.map(|i| (Some(&text[off + 1..off + i]), off + i + 1))?
|
||||||
"src_xml[:exports code]{<tag>text</tag>}".len()
|
} else {
|
||||||
))
|
(None, off)
|
||||||
);
|
};
|
||||||
assert_eq!(parse("src_xml[:exports code]{<tag>text</tag>"), None);
|
|
||||||
assert_eq!(parse("src_[:exports code]{<tag>text</tag>}"), None);
|
let (body, off) = memchr(b'}', text[off..].as_bytes())
|
||||||
// assert_eq!(parse("src_xml[:exports code]"), None);
|
.map(|i| (&text[off + 1..off + i], off + i + 1))
|
||||||
|
.filter(|(body, _)| body.as_bytes().iter().all(|c| *c != b'\n'))?;
|
||||||
|
|
||||||
|
Some((InlineSrc { lang, option, body }, off))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse() {
|
||||||
|
assert_eq!(
|
||||||
|
InlineSrc::parse("src_C{int a = 0;}"),
|
||||||
|
Some((
|
||||||
|
InlineSrc {
|
||||||
|
lang: "C",
|
||||||
|
option: None,
|
||||||
|
body: "int a = 0;"
|
||||||
|
},
|
||||||
|
"src_C{int a = 0;}".len()
|
||||||
|
))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
InlineSrc::parse("src_xml[:exports code]{<tag>text</tag>}"),
|
||||||
|
Some((
|
||||||
|
InlineSrc {
|
||||||
|
lang: "xml",
|
||||||
|
option: Some(":exports code"),
|
||||||
|
body: "<tag>text</tag>",
|
||||||
|
},
|
||||||
|
"src_xml[:exports code]{<tag>text</tag>}".len()
|
||||||
|
))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
InlineSrc::parse("src_xml[:exports code]{<tag>text</tag>"),
|
||||||
|
None
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
InlineSrc::parse("src_[:exports code]{<tag>text</tag>}"),
|
||||||
|
None
|
||||||
|
);
|
||||||
|
// assert_eq!(parse("src_xml[:exports code]"), None);
|
||||||
|
}
|
||||||
|
|
|
@ -1,43 +1,67 @@
|
||||||
use jetscii::Substring;
|
use jetscii::Substring;
|
||||||
use memchr::memchr;
|
use memchr::memchr;
|
||||||
|
|
||||||
/// returns (link path, link description, offset)
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
#[inline]
|
#[derive(Debug)]
|
||||||
pub fn parse(text: &str) -> Option<(&str, Option<&str>, usize)> {
|
pub struct Link<'a> {
|
||||||
debug_assert!(text.starts_with("[["));
|
pub path: &'a str,
|
||||||
|
pub desc: Option<&'a str>,
|
||||||
|
}
|
||||||
|
|
||||||
let (path, off) = memchr(b']', text.as_bytes())
|
impl<'a> Link<'a> {
|
||||||
.map(|i| (&text[2..i], i))
|
#[inline]
|
||||||
.filter(|(path, _)| {
|
pub(crate) fn parse(text: &str) -> Option<(Link<'_>, usize)> {
|
||||||
path.as_bytes()
|
debug_assert!(text.starts_with("[["));
|
||||||
.iter()
|
|
||||||
.all(|&c| c != b'<' && c != b'>' && c != b'\n')
|
|
||||||
})?;
|
|
||||||
|
|
||||||
if *text.as_bytes().get(off + 1)? == b']' {
|
let (path, off) = memchr(b']', text.as_bytes())
|
||||||
Some((path, None, off + 2))
|
.map(|i| (&text["[[".len()..i], i))
|
||||||
} else if text.as_bytes()[off + 1] == b'[' {
|
.filter(|(path, _)| {
|
||||||
let (desc, off) = Substring::new("]]")
|
path.as_bytes()
|
||||||
.find(&text[off + 1..])
|
.iter()
|
||||||
.map(|i| (&text[off + 2..off + i + 1], off + i + 3))
|
.all(|&c| c != b'<' && c != b'>' && c != b'\n')
|
||||||
.filter(|(desc, _)| desc.as_bytes().iter().all(|&c| c != b'[' && c != b']'))?;
|
})?;
|
||||||
Some((path, Some(desc), off))
|
|
||||||
} else {
|
if *text.as_bytes().get(off + 1)? == b']' {
|
||||||
None
|
Some((Link { path, desc: None }, off + 2))
|
||||||
|
} else if text.as_bytes()[off + 1] == b'[' {
|
||||||
|
let (desc, off) = Substring::new("]]")
|
||||||
|
.find(&text[off + 1..])
|
||||||
|
.map(|i| (&text[off + 2..off + 1 + i], off + 1 + i + "]]".len()))
|
||||||
|
.filter(|(desc, _)| desc.as_bytes().iter().all(|&c| c != b'[' && c != b']'))?;
|
||||||
|
Some((
|
||||||
|
Link {
|
||||||
|
path,
|
||||||
|
desc: Some(desc),
|
||||||
|
},
|
||||||
|
off,
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[test]
|
||||||
mod tests {
|
fn parse() {
|
||||||
#[test]
|
assert_eq!(
|
||||||
fn parse() {
|
Link::parse("[[#id]]"),
|
||||||
use super::parse;
|
Some((
|
||||||
|
Link {
|
||||||
assert_eq!(parse("[[#id]]"), Some(("#id", None, "[[#id]]".len())));
|
path: "#id",
|
||||||
assert_eq!(
|
desc: None
|
||||||
parse("[[#id][desc]]"),
|
},
|
||||||
Some(("#id", Some("desc"), "[[#id][desc]]".len()))
|
"[[#id]]".len()
|
||||||
);
|
))
|
||||||
assert_eq!(parse("[[#id][desc]"), None);
|
);
|
||||||
}
|
assert_eq!(
|
||||||
|
Link::parse("[[#id][desc]]"),
|
||||||
|
Some((
|
||||||
|
Link {
|
||||||
|
path: "#id",
|
||||||
|
desc: Some("desc")
|
||||||
|
},
|
||||||
|
"[[#id][desc]]".len()
|
||||||
|
))
|
||||||
|
);
|
||||||
|
assert_eq!(Link::parse("[[#id][desc]"), None);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,60 +1,80 @@
|
||||||
use jetscii::Substring;
|
use jetscii::Substring;
|
||||||
use memchr::memchr2;
|
use memchr::memchr2;
|
||||||
|
|
||||||
/// returns (macros name, macros arguments, offset)
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
#[inline]
|
#[derive(Debug)]
|
||||||
pub fn parse(text: &str) -> Option<(&str, Option<&str>, usize)> {
|
pub struct Macros<'a> {
|
||||||
debug_assert!(text.starts_with("{{{"));
|
pub name: &'a str,
|
||||||
|
pub arguments: Option<&'a str>,
|
||||||
|
}
|
||||||
|
|
||||||
let bytes = text.as_bytes();
|
impl<'a> Macros<'a> {
|
||||||
if text.len() <= 3 || !bytes[3].is_ascii_alphabetic() {
|
#[inline]
|
||||||
return None;
|
pub fn parse(text: &str) -> Option<(Macros<'_>, usize)> {
|
||||||
}
|
debug_assert!(text.starts_with("{{{"));
|
||||||
|
|
||||||
let (name, off) = memchr2(b'}', b'(', bytes)
|
let bytes = text.as_bytes();
|
||||||
.filter(|&i| {
|
if text.len() <= 3 || !bytes[3].is_ascii_alphabetic() {
|
||||||
bytes[3..i]
|
|
||||||
.iter()
|
|
||||||
.all(|&c| c.is_ascii_alphanumeric() || c == b'-' || c == b'_')
|
|
||||||
})
|
|
||||||
.map(|i| (&text[3..i], i))?;
|
|
||||||
|
|
||||||
let (args, off) = if bytes[off] == b'}' {
|
|
||||||
if text.len() <= off + 2 || bytes[off + 1] != b'}' || bytes[off + 2] != b'}' {
|
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
(None, off + 3 /* }}} */)
|
|
||||||
} else {
|
|
||||||
Substring::new(")}}}")
|
|
||||||
.find(&text[off..])
|
|
||||||
.map(|i| (Some(&text[off + 1..off + i]), off + i + 4 /* )}}} */))?
|
|
||||||
};
|
|
||||||
|
|
||||||
Some((name, args, off))
|
let (name, off) = memchr2(b'}', b'(', bytes)
|
||||||
}
|
.filter(|&i| {
|
||||||
|
bytes[3..i]
|
||||||
|
.iter()
|
||||||
|
.all(|&c| c.is_ascii_alphanumeric() || c == b'-' || c == b'_')
|
||||||
|
})
|
||||||
|
.map(|i| (&text[3..i], i))?;
|
||||||
|
|
||||||
#[cfg(test)]
|
let (arguments, off) = if bytes[off] == b'}' {
|
||||||
mod tests {
|
if text.len() <= off + 2 || bytes[off + 1] != b'}' || bytes[off + 2] != b'}' {
|
||||||
#[test]
|
return None;
|
||||||
fn parse() {
|
}
|
||||||
use super::parse;
|
(None, off + "}}}".len())
|
||||||
|
} else {
|
||||||
|
Substring::new(")}}}")
|
||||||
|
.find(&text[off..])
|
||||||
|
.map(|i| (Some(&text[off + 1..off + i]), off + i + ")}}}".len()))?
|
||||||
|
};
|
||||||
|
|
||||||
assert_eq!(
|
Some((Macros { name, arguments }, off))
|
||||||
parse("{{{poem(red,blue)}}}"),
|
|
||||||
Some(("poem", Some("red,blue"), "{{{poem(red,blue)}}}".len()))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
parse("{{{poem())}}}"),
|
|
||||||
Some(("poem", Some(")"), "{{{poem())}}}".len()))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
parse("{{{author}}}"),
|
|
||||||
Some(("author", None, "{{{author}}}".len()))
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(parse("{{{0uthor}}}"), None);
|
|
||||||
assert_eq!(parse("{{{author}}"), None);
|
|
||||||
assert_eq!(parse("{{{poem(}}}"), None);
|
|
||||||
assert_eq!(parse("{{{poem)}}}"), None);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse() {
|
||||||
|
assert_eq!(
|
||||||
|
Macros::parse("{{{poem(red,blue)}}}"),
|
||||||
|
Some((
|
||||||
|
Macros {
|
||||||
|
name: "poem",
|
||||||
|
arguments: Some("red,blue")
|
||||||
|
},
|
||||||
|
"{{{poem(red,blue)}}}".len()
|
||||||
|
))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Macros::parse("{{{poem())}}}"),
|
||||||
|
Some((
|
||||||
|
Macros {
|
||||||
|
name: "poem",
|
||||||
|
arguments: Some(")")
|
||||||
|
},
|
||||||
|
"{{{poem())}}}".len()
|
||||||
|
))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Macros::parse("{{{author}}}"),
|
||||||
|
Some((
|
||||||
|
Macros {
|
||||||
|
name: "author",
|
||||||
|
arguments: None
|
||||||
|
},
|
||||||
|
"{{{author}}}".len()
|
||||||
|
))
|
||||||
|
);
|
||||||
|
assert_eq!(Macros::parse("{{{0uthor}}}"), None);
|
||||||
|
assert_eq!(Macros::parse("{{{author}}"), None);
|
||||||
|
assert_eq!(Macros::parse("{{{poem(}}}"), None);
|
||||||
|
assert_eq!(Macros::parse("{{{poem)}}}"), None);
|
||||||
|
}
|
||||||
|
|
|
@ -14,4 +14,10 @@ pub(crate) mod target;
|
||||||
pub(crate) mod timestamp;
|
pub(crate) mod timestamp;
|
||||||
|
|
||||||
pub use self::cookie::Cookie;
|
pub use self::cookie::Cookie;
|
||||||
|
pub use self::fn_ref::FnRef;
|
||||||
|
pub use self::inline_call::InlineCall;
|
||||||
|
pub use self::inline_src::InlineSrc;
|
||||||
|
pub use self::link::Link;
|
||||||
|
pub use self::macros::Macros;
|
||||||
|
pub use self::snippet::Snippet;
|
||||||
pub use self::timestamp::*;
|
pub use self::timestamp::*;
|
||||||
|
|
|
@ -15,7 +15,7 @@ pub fn parse(src: &str) -> Option<(&str, usize)> {
|
||||||
.iter()
|
.iter()
|
||||||
.all(|&c| c != b'<' && c != b'\n' && c != b'>')
|
.all(|&c| c != b'<' && c != b'\n' && c != b'>')
|
||||||
})
|
})
|
||||||
.map(|i| (&src[3..i], i + 3 /* >>> */))?;
|
.map(|i| (&src[3..i], i + ">>>".len()))?;
|
||||||
|
|
||||||
Some((target, off))
|
Some((target, off))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,48 +1,68 @@
|
||||||
use jetscii::Substring;
|
use jetscii::Substring;
|
||||||
use memchr::memchr;
|
use memchr::memchr;
|
||||||
|
|
||||||
/// returns (snippet name, snippet value, offset)
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
#[inline]
|
#[derive(Debug)]
|
||||||
pub fn parse(text: &str) -> Option<(&str, &str, usize)> {
|
pub struct Snippet<'a> {
|
||||||
debug_assert!(text.starts_with("@@"));
|
pub name: &'a str,
|
||||||
|
pub value: &'a str,
|
||||||
let (name, off) = memchr(b':', text.as_bytes())
|
|
||||||
.filter(|&i| {
|
|
||||||
i != 2
|
|
||||||
&& text.as_bytes()[2..i]
|
|
||||||
.iter()
|
|
||||||
.all(|&c| c.is_ascii_alphanumeric() || c == b'-')
|
|
||||||
})
|
|
||||||
.map(|i| (&text[2..i], i + 1))?;
|
|
||||||
|
|
||||||
let (value, off) = Substring::new("@@")
|
|
||||||
.find(&text[off..])
|
|
||||||
.map(|i| (&text[off..off + i], off + i + 2 /* @@ */))?;
|
|
||||||
|
|
||||||
Some((name, value, off))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
impl<'a> Snippet<'a> {
|
||||||
mod tests {
|
#[inline]
|
||||||
#[test]
|
pub(crate) fn parse(text: &str) -> Option<(Snippet<'_>, usize)> {
|
||||||
fn parse() {
|
debug_assert!(text.starts_with("@@"));
|
||||||
use super::parse;
|
|
||||||
|
|
||||||
assert_eq!(
|
let (name, off) = memchr(b':', text.as_bytes())
|
||||||
parse("@@html:<b>@@"),
|
.filter(|&i| {
|
||||||
Some(("html", "<b>", "@@html:<b>@@".len()))
|
i != 2
|
||||||
);
|
&& text.as_bytes()[2..i]
|
||||||
assert_eq!(
|
.iter()
|
||||||
parse("@@latex:any arbitrary LaTeX code@@"),
|
.all(|&c| c.is_ascii_alphanumeric() || c == b'-')
|
||||||
Some((
|
})
|
||||||
"latex",
|
.map(|i| (&text[2..i], i + 1))?;
|
||||||
"any arbitrary LaTeX code",
|
|
||||||
"@@latex:any arbitrary LaTeX code@@".len()
|
let (value, off) = Substring::new("@@")
|
||||||
))
|
.find(&text[off..])
|
||||||
);
|
.map(|i| (&text[off..off + i], off + i + "@@".len()))?;
|
||||||
assert_eq!(parse("@@html:@@"), Some(("html", "", "@@html:@@".len())));
|
|
||||||
assert_eq!(parse("@@html:<b>@"), None);
|
Some((Snippet { name, value }, off))
|
||||||
assert_eq!(parse("@@html<b>@@"), None);
|
|
||||||
assert_eq!(parse("@@:<b>@@"), None);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse() {
|
||||||
|
assert_eq!(
|
||||||
|
Snippet::parse("@@html:<b>@@"),
|
||||||
|
Some((
|
||||||
|
Snippet {
|
||||||
|
name: "html",
|
||||||
|
value: "<b>"
|
||||||
|
},
|
||||||
|
"@@html:<b>@@".len()
|
||||||
|
))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Snippet::parse("@@latex:any arbitrary LaTeX code@@"),
|
||||||
|
Some((
|
||||||
|
Snippet {
|
||||||
|
name: "latex",
|
||||||
|
value: "any arbitrary LaTeX code",
|
||||||
|
},
|
||||||
|
"@@latex:any arbitrary LaTeX code@@".len()
|
||||||
|
))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Snippet::parse("@@html:@@"),
|
||||||
|
Some((
|
||||||
|
Snippet {
|
||||||
|
name: "html",
|
||||||
|
value: "",
|
||||||
|
},
|
||||||
|
"@@html:@@".len()
|
||||||
|
))
|
||||||
|
);
|
||||||
|
assert_eq!(Snippet::parse("@@html:<b>@"), None);
|
||||||
|
assert_eq!(Snippet::parse("@@html<b>@@"), None);
|
||||||
|
assert_eq!(Snippet::parse("@@:<b>@@"), None);
|
||||||
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ pub fn parse(text: &str) -> Option<(&str, usize)> {
|
||||||
.iter()
|
.iter()
|
||||||
.all(|&c| c != b'<' && c != b'\n' && c != b'>')
|
.all(|&c| c != b'<' && c != b'\n' && c != b'>')
|
||||||
})
|
})
|
||||||
.map(|i| (&text[2..i], i + 2 /* >> */))
|
.map(|i| (&text[2..i], i + ">>".len()))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -1,11 +1,50 @@
|
||||||
use memchr::memchr;
|
use memchr::memchr;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct Datetime<'a> {
|
pub struct Datetime<'a> {
|
||||||
pub date: (u16, u8, u8),
|
pub(crate) date: &'a str,
|
||||||
pub time: Option<(u8, u8)>,
|
pub(crate) time: Option<&'a str>,
|
||||||
pub dayname: &'a str,
|
pub(crate) dayname: &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Datetime<'a> {
|
||||||
|
pub fn year(&self) -> u32 {
|
||||||
|
u32::from_str(&self.date[0..4]).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn month(&self) -> u32 {
|
||||||
|
u32::from_str(&self.date[5..7]).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn day(&self) -> u32 {
|
||||||
|
u32::from_str(&self.date[8..10]).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hour(&self) -> Option<u32> {
|
||||||
|
self.time.map(|time| {
|
||||||
|
if time.len() == 4 {
|
||||||
|
u32::from_str(&time[0..1]).unwrap()
|
||||||
|
} else {
|
||||||
|
u32::from_str(&time[0..2]).unwrap()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn minute(&self) -> Option<u32> {
|
||||||
|
self.time.map(|time| {
|
||||||
|
if time.len() == 4 {
|
||||||
|
u32::from_str(&time[2..4]).unwrap()
|
||||||
|
} else {
|
||||||
|
u32::from_str(&time[3..5]).unwrap()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dayname(&self) -> &str {
|
||||||
|
self.dayname
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "chrono")]
|
#[cfg(feature = "chrono")]
|
||||||
|
@ -15,16 +54,11 @@ mod chrono {
|
||||||
|
|
||||||
impl<'a> Datetime<'a> {
|
impl<'a> Datetime<'a> {
|
||||||
pub fn naive_date(&self) -> NaiveDate {
|
pub fn naive_date(&self) -> NaiveDate {
|
||||||
let (y, m, d) = self.date;
|
NaiveDate::from_ymd(self.year() as i32, self.month(), self.day())
|
||||||
NaiveDate::from_ymd(y.into(), m.into(), d.into())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn naive_time(&self) -> NaiveTime {
|
pub fn naive_time(&self) -> NaiveTime {
|
||||||
if let Some((h, m)) = self.time {
|
NaiveTime::from_hms(self.hour().unwrap_or(0), self.minute().unwrap_or(0), 0)
|
||||||
NaiveTime::from_hms(h.into(), m.into(), 0)
|
|
||||||
} else {
|
|
||||||
NaiveTime::from_hms(0, 0, 0)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn naive_date_time(&self) -> NaiveDateTime {
|
pub fn naive_date_time(&self) -> NaiveDateTime {
|
||||||
|
@ -88,25 +122,25 @@ pub struct Delay {
|
||||||
pub enum Timestamp<'a> {
|
pub enum Timestamp<'a> {
|
||||||
Active {
|
Active {
|
||||||
start: Datetime<'a>,
|
start: Datetime<'a>,
|
||||||
repeater: Option<Repeater>,
|
repeater: Option<&'a str>,
|
||||||
delay: Option<Delay>,
|
delay: Option<&'a str>,
|
||||||
},
|
},
|
||||||
Inactive {
|
Inactive {
|
||||||
start: Datetime<'a>,
|
start: Datetime<'a>,
|
||||||
repeater: Option<Repeater>,
|
repeater: Option<&'a str>,
|
||||||
delay: Option<Delay>,
|
delay: Option<&'a str>,
|
||||||
},
|
},
|
||||||
ActiveRange {
|
ActiveRange {
|
||||||
start: Datetime<'a>,
|
start: Datetime<'a>,
|
||||||
end: Datetime<'a>,
|
end: Datetime<'a>,
|
||||||
repeater: Option<Repeater>,
|
repeater: Option<&'a str>,
|
||||||
delay: Option<Delay>,
|
delay: Option<&'a str>,
|
||||||
},
|
},
|
||||||
InactiveRange {
|
InactiveRange {
|
||||||
start: Datetime<'a>,
|
start: Datetime<'a>,
|
||||||
end: Datetime<'a>,
|
end: Datetime<'a>,
|
||||||
repeater: Option<Repeater>,
|
repeater: Option<&'a str>,
|
||||||
delay: Option<Delay>,
|
delay: Option<&'a str>,
|
||||||
},
|
},
|
||||||
Diary(&'a str),
|
Diary(&'a str),
|
||||||
}
|
}
|
||||||
|
@ -129,7 +163,9 @@ impl<'a> Timestamp<'a> {
|
||||||
let mut off = memchr(b'>', bytes)?;
|
let mut off = memchr(b'>', bytes)?;
|
||||||
let (start, mut end) = Self::parse_datetime(&text[1..off])?;
|
let (start, mut end) = Self::parse_datetime(&text[1..off])?;
|
||||||
|
|
||||||
if end.is_none() && off <= text.len() - 14 /* --<YYYY-MM-DD> */ && text[off + 1..].starts_with("--<")
|
if end.is_none()
|
||||||
|
&& off + "--<YYYY-MM-DD >".len() <= text.len()
|
||||||
|
&& text[off + 1..].starts_with("--<")
|
||||||
{
|
{
|
||||||
if let Some(new_off) = memchr(b'>', &bytes[off + 1..]) {
|
if let Some(new_off) = memchr(b'>', &bytes[off + 1..]) {
|
||||||
if let Some((start, _)) = Self::parse_datetime(&text[off + 4..off + 1 + new_off]) {
|
if let Some((start, _)) = Self::parse_datetime(&text[off + 4..off + 1 + new_off]) {
|
||||||
|
@ -164,7 +200,9 @@ impl<'a> Timestamp<'a> {
|
||||||
let bytes = text.as_bytes();
|
let bytes = text.as_bytes();
|
||||||
let mut off = memchr(b']', bytes)?;
|
let mut off = memchr(b']', bytes)?;
|
||||||
let (start, mut end) = Self::parse_datetime(&text[1..off])?;
|
let (start, mut end) = Self::parse_datetime(&text[1..off])?;
|
||||||
if end.is_none() && off <= text.len() - 14 /* --[YYYY-MM-DD] */ && text[off + 1..].starts_with("--[")
|
if end.is_none()
|
||||||
|
&& off + "--[YYYY-MM-DD ]".len() <= text.len()
|
||||||
|
&& text[off + 1..].starts_with("--[")
|
||||||
{
|
{
|
||||||
if let Some(new_off) = memchr(b']', &bytes[off + 1..]) {
|
if let Some(new_off) = memchr(b']', &bytes[off + 1..]) {
|
||||||
if let Some((start, _)) = Self::parse_datetime(&text[off + 4..off + 1 + new_off]) {
|
if let Some((start, _)) = Self::parse_datetime(&text[off + 4..off + 1 + new_off]) {
|
||||||
|
@ -203,29 +241,16 @@ impl<'a> Timestamp<'a> {
|
||||||
|
|
||||||
let mut words = text.split_ascii_whitespace();
|
let mut words = text.split_ascii_whitespace();
|
||||||
|
|
||||||
let date = words
|
let date = words.next().filter(|word| {
|
||||||
.next()
|
let word = word.as_bytes();
|
||||||
.filter(|word| {
|
// YYYY-MM-DD
|
||||||
let word = word.as_bytes();
|
word.len() == 10
|
||||||
// YYYY-MM-DD
|
&& word[0..4].iter().all(u8::is_ascii_digit)
|
||||||
word.len() == 10
|
&& word[4] == b'-'
|
||||||
&& word[0..4].iter().all(u8::is_ascii_digit)
|
&& word[5..7].iter().all(u8::is_ascii_digit)
|
||||||
&& word[4] == b'-'
|
&& word[7] == b'-'
|
||||||
&& word[5..7].iter().all(u8::is_ascii_digit)
|
&& word[8..10].iter().all(u8::is_ascii_digit)
|
||||||
&& word[7] == b'-'
|
})?;
|
||||||
&& word[8..10].iter().all(u8::is_ascii_digit)
|
|
||||||
})
|
|
||||||
.map(|word| {
|
|
||||||
let word = word.as_bytes();
|
|
||||||
(
|
|
||||||
(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| {
|
let dayname = words.next().filter(|word| {
|
||||||
word.as_bytes().iter().all(|&c| {
|
word.as_bytes().iter().all(|&c| {
|
||||||
|
@ -239,86 +264,109 @@ impl<'a> Timestamp<'a> {
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let (start, end) = if let Some(word) = words.next() {
|
let (start, end) = if let Some(word) = words.next() {
|
||||||
let word = word.as_bytes();
|
let time = word.as_bytes();
|
||||||
|
|
||||||
macro_rules! datetime {
|
if (time.len() == "H:MM".len()
|
||||||
($a:expr, $b:expr, $c:expr) => {
|
&& time[0].is_ascii_digit()
|
||||||
|
&& time[1] == b':'
|
||||||
|
&& time[2..4].iter().all(u8::is_ascii_digit))
|
||||||
|
|| (time.len() == "HH:MM".len()
|
||||||
|
&& time[0..2].iter().all(u8::is_ascii_digit)
|
||||||
|
&& time[2] == b':'
|
||||||
|
&& time[3..5].iter().all(u8::is_ascii_digit))
|
||||||
|
{
|
||||||
|
(
|
||||||
Datetime {
|
Datetime {
|
||||||
date,
|
date,
|
||||||
dayname,
|
dayname,
|
||||||
time: Some((word[$a] - b'0', (word[$b] - b'0') * 10 + (word[$c] - b'0'))),
|
time: Some(word),
|
||||||
}
|
},
|
||||||
};
|
None,
|
||||||
($a:expr, $b:expr, $c:expr, $d:expr) => {
|
)
|
||||||
|
} else if time.len() == "H:MM-H:MM".len()
|
||||||
|
&& time[0].is_ascii_digit()
|
||||||
|
&& time[1] == b':'
|
||||||
|
&& time[2..4].iter().all(u8::is_ascii_digit)
|
||||||
|
&& time[4] == b'-'
|
||||||
|
&& time[5].is_ascii_digit()
|
||||||
|
&& time[6] == b':'
|
||||||
|
&& time[7..9].iter().all(u8::is_ascii_digit)
|
||||||
|
{
|
||||||
|
(
|
||||||
Datetime {
|
Datetime {
|
||||||
date,
|
date,
|
||||||
dayname,
|
dayname,
|
||||||
time: Some((
|
time: Some(&word[0..4]),
|
||||||
(word[$a] - b'0') * 10 + (word[$b] - b'0'),
|
},
|
||||||
(word[$c] - b'0') * 10 + (word[$d] - b'0'),
|
Some(Datetime {
|
||||||
)),
|
date,
|
||||||
}
|
dayname,
|
||||||
};
|
time: Some(&word[5..9]),
|
||||||
}
|
}),
|
||||||
|
)
|
||||||
if word.len() == 4
|
} else if time.len() == "H:MM-HH:MM".len()
|
||||||
&& word[0].is_ascii_digit()
|
&& time[0].is_ascii_digit()
|
||||||
&& word[1] == b':'
|
&& time[1] == b':'
|
||||||
&& word[2..4].iter().all(u8::is_ascii_digit)
|
&& time[2..4].iter().all(u8::is_ascii_digit)
|
||||||
|
&& time[4] == b'-'
|
||||||
|
&& time[5..7].iter().all(u8::is_ascii_digit)
|
||||||
|
&& time[7] == b':'
|
||||||
|
&& time[8..10].iter().all(u8::is_ascii_digit)
|
||||||
{
|
{
|
||||||
// H:MM
|
(
|
||||||
(datetime!(0, 2, 3), None)
|
Datetime {
|
||||||
} else if word.len() == 5
|
date,
|
||||||
&& word[0..2].iter().all(u8::is_ascii_digit)
|
dayname,
|
||||||
&& word[2] == b':'
|
time: Some(&word[0..4]),
|
||||||
&& word[3..5].iter().all(u8::is_ascii_digit)
|
},
|
||||||
|
Some(Datetime {
|
||||||
|
date,
|
||||||
|
dayname,
|
||||||
|
time: Some(&word[5..10]),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
} else if time.len() == "HH:MM-H:MM".len()
|
||||||
|
&& time[0..2].iter().all(u8::is_ascii_digit)
|
||||||
|
&& time[2] == b':'
|
||||||
|
&& time[3..5].iter().all(u8::is_ascii_digit)
|
||||||
|
&& time[5] == b'-'
|
||||||
|
&& time[6].is_ascii_digit()
|
||||||
|
&& time[7] == b':'
|
||||||
|
&& time[8..10].iter().all(u8::is_ascii_digit)
|
||||||
{
|
{
|
||||||
// HH:MM
|
(
|
||||||
(datetime!(0, 1, 3, 4), None)
|
Datetime {
|
||||||
} else if word.len() == 9
|
date,
|
||||||
&& word[0].is_ascii_digit()
|
dayname,
|
||||||
&& word[1] == b':'
|
time: Some(&word[0..5]),
|
||||||
&& word[2..4].iter().all(u8::is_ascii_digit)
|
},
|
||||||
&& word[4] == b'-'
|
Some(Datetime {
|
||||||
&& word[5].is_ascii_digit()
|
date,
|
||||||
&& word[6] == b':'
|
dayname,
|
||||||
&& word[7..9].iter().all(u8::is_ascii_digit)
|
time: Some(&word[6..10]),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
} else if time.len() == "HH:MM-HH:MM".len()
|
||||||
|
&& time[0..2].iter().all(u8::is_ascii_digit)
|
||||||
|
&& time[2] == b':'
|
||||||
|
&& time[3..5].iter().all(u8::is_ascii_digit)
|
||||||
|
&& time[5] == b'-'
|
||||||
|
&& time[6..8].iter().all(u8::is_ascii_digit)
|
||||||
|
&& time[8] == b':'
|
||||||
|
&& time[9..11].iter().all(u8::is_ascii_digit)
|
||||||
{
|
{
|
||||||
// H:MM-H:MM
|
(
|
||||||
(datetime!(0, 2, 3), Some(datetime!(5, 7, 8)))
|
Datetime {
|
||||||
} else if word.len() == 10
|
date,
|
||||||
&& word[0].is_ascii_digit()
|
dayname,
|
||||||
&& word[1] == b':'
|
time: Some(&word[0..5]),
|
||||||
&& word[2..4].iter().all(u8::is_ascii_digit)
|
},
|
||||||
&& word[4] == b'-'
|
Some(Datetime {
|
||||||
&& word[5..7].iter().all(u8::is_ascii_digit)
|
date,
|
||||||
&& word[7] == b':'
|
dayname,
|
||||||
&& word[8..10].iter().all(u8::is_ascii_digit)
|
time: Some(&word[6..11]),
|
||||||
{
|
}),
|
||||||
// H:MM-HH:MM
|
)
|
||||||
(datetime!(0, 2, 3), Some(datetime!(5, 6, 8, 9)))
|
|
||||||
} else if word.len() == 10
|
|
||||||
&& word[0..2].iter().all(u8::is_ascii_digit)
|
|
||||||
&& word[2] == b':'
|
|
||||||
&& word[3..5].iter().all(u8::is_ascii_digit)
|
|
||||||
&& word[5] == b'-'
|
|
||||||
&& word[6].is_ascii_digit()
|
|
||||||
&& word[7] == b':'
|
|
||||||
&& word[8..10].iter().all(u8::is_ascii_digit)
|
|
||||||
{
|
|
||||||
// HH:MM-H:MM
|
|
||||||
(datetime!(0, 1, 3, 4), Some(datetime!(6, 8, 9)))
|
|
||||||
} else if word.len() == 11
|
|
||||||
&& word[0..2].iter().all(u8::is_ascii_digit)
|
|
||||||
&& word[2] == b':'
|
|
||||||
&& word[3..5].iter().all(u8::is_ascii_digit)
|
|
||||||
&& word[5] == b'-'
|
|
||||||
&& word[6..8].iter().all(u8::is_ascii_digit)
|
|
||||||
&& word[8] == b':'
|
|
||||||
&& word[9..11].iter().all(u8::is_ascii_digit)
|
|
||||||
{
|
|
||||||
// HH:MM-HH:MM
|
|
||||||
(datetime!(0, 1, 3, 4), Some(datetime!(6, 7, 9, 10)))
|
|
||||||
} else {
|
} else {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
@ -344,137 +392,136 @@ impl<'a> Timestamp<'a> {
|
||||||
pub(crate) fn parse_diary(text: &str) -> Option<(Timestamp<'_>, usize)> {
|
pub(crate) fn parse_diary(text: &str) -> Option<(Timestamp<'_>, usize)> {
|
||||||
debug_assert!(text.starts_with('<'));
|
debug_assert!(text.starts_with('<'));
|
||||||
|
|
||||||
if text.len() <= 6 /* <%%()> */ || &text[1..4] != "%%(" {
|
if text.len() <= "<%%()>".len() || &text[1..4] != "%%(" {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let bytes = text.as_bytes();
|
let bytes = text.as_bytes();
|
||||||
|
|
||||||
memchr(b'>', bytes)
|
memchr(b'>', bytes)
|
||||||
.filter(|i| bytes[i - 1] == b')' && bytes[4..i - 1].iter().all(|&c| c != b'\n'))
|
.filter(|i| {
|
||||||
.map(|i| (Timestamp::Diary(&text[4..i - 1]), i))
|
bytes[i - 1] == b')' && bytes["<%%(".len()..i - 1].iter().all(|&c| c != b'\n')
|
||||||
|
})
|
||||||
|
.map(|i| (Timestamp::Diary(&text["<%%(".len()..i - 1]), i))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[test]
|
||||||
mod tests {
|
fn parse_range() {
|
||||||
#[test]
|
use super::*;
|
||||||
fn parse_range() {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Timestamp::parse_inactive("[2003-09-16 Tue]"),
|
Timestamp::parse_inactive("[2003-09-16 Tue]"),
|
||||||
Some((
|
Some((
|
||||||
Timestamp::Inactive {
|
Timestamp::Inactive {
|
||||||
start: Datetime {
|
start: Datetime {
|
||||||
date: (2003, 9, 16),
|
date: "2003-09-16",
|
||||||
time: None,
|
|
||||||
dayname: "Tue"
|
|
||||||
},
|
|
||||||
repeater: None,
|
|
||||||
delay: None,
|
|
||||||
},
|
|
||||||
"[2003-09-16 Tue]".len()
|
|
||||||
))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
Timestamp::parse_inactive("[2003-09-16 Tue 09:39]--[2003-09-16 Tue 10:39]"),
|
|
||||||
Some((
|
|
||||||
Timestamp::InactiveRange {
|
|
||||||
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
|
|
||||||
},
|
|
||||||
"[2003-09-16 Tue 09:39]--[2003-09-16 Tue 10:39]".len()
|
|
||||||
))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
Timestamp::parse_active("<2003-09-16 Tue 09:39-10:39>"),
|
|
||||||
Some((
|
|
||||||
Timestamp::ActiveRange {
|
|
||||||
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
|
|
||||||
},
|
|
||||||
"<2003-09-16 Tue 09:39-10:39>".len()
|
|
||||||
))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse_datetime() {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
Timestamp::parse_datetime("2003-09-16 Tue"),
|
|
||||||
Some((
|
|
||||||
Datetime {
|
|
||||||
date: (2003, 9, 16),
|
|
||||||
time: None,
|
time: None,
|
||||||
dayname: "Tue"
|
dayname: "Tue"
|
||||||
},
|
},
|
||||||
None
|
repeater: None,
|
||||||
))
|
delay: None,
|
||||||
);
|
},
|
||||||
assert_eq!(
|
"[2003-09-16 Tue]".len()
|
||||||
Timestamp::parse_datetime("2003-09-16 Tue 9:39"),
|
))
|
||||||
Some((
|
);
|
||||||
Datetime {
|
assert_eq!(
|
||||||
date: (2003, 9, 16),
|
Timestamp::parse_inactive("[2003-09-16 Tue 09:39]--[2003-09-16 Tue 10:39]"),
|
||||||
time: Some((9, 39)),
|
Some((
|
||||||
|
Timestamp::InactiveRange {
|
||||||
|
start: Datetime {
|
||||||
|
date: "2003-09-16",
|
||||||
|
time: Some("09:39"),
|
||||||
dayname: "Tue"
|
dayname: "Tue"
|
||||||
},
|
},
|
||||||
None
|
end: Datetime {
|
||||||
))
|
date: "2003-09-16",
|
||||||
);
|
time: Some("10:39"),
|
||||||
assert_eq!(
|
|
||||||
Timestamp::parse_datetime("2003-09-16 Tue 09:39"),
|
|
||||||
Some((
|
|
||||||
Datetime {
|
|
||||||
date: (2003, 9, 16),
|
|
||||||
time: Some((9, 39)),
|
|
||||||
dayname: "Tue"
|
dayname: "Tue"
|
||||||
},
|
},
|
||||||
None
|
repeater: None,
|
||||||
))
|
delay: None
|
||||||
);
|
},
|
||||||
assert_eq!(
|
"[2003-09-16 Tue 09:39]--[2003-09-16 Tue 10:39]".len()
|
||||||
Timestamp::parse_datetime("2003-09-16 Tue 9:39-10:39"),
|
))
|
||||||
Some((
|
);
|
||||||
Datetime {
|
assert_eq!(
|
||||||
date: (2003, 9, 16),
|
Timestamp::parse_active("<2003-09-16 Tue 09:39-10:39>"),
|
||||||
time: Some((9, 39)),
|
Some((
|
||||||
|
Timestamp::ActiveRange {
|
||||||
|
start: Datetime {
|
||||||
|
date: "2003-09-16",
|
||||||
|
time: Some("09:39"),
|
||||||
dayname: "Tue"
|
dayname: "Tue"
|
||||||
},
|
},
|
||||||
Some(Datetime {
|
end: Datetime {
|
||||||
date: (2003, 9, 16),
|
date: "2003-09-16",
|
||||||
time: Some((10, 39)),
|
time: Some("10:39"),
|
||||||
dayname: "Tue"
|
dayname: "Tue"
|
||||||
}),
|
},
|
||||||
))
|
repeater: None,
|
||||||
);
|
delay: None
|
||||||
|
},
|
||||||
assert_eq!(Timestamp::parse_datetime("2003-9-16 Tue"), None);
|
"<2003-09-16 Tue 09:39-10:39>".len()
|
||||||
assert_eq!(Timestamp::parse_datetime("2003-09-16"), None);
|
))
|
||||||
assert_eq!(Timestamp::parse_datetime("2003-09-16 09:39"), None);
|
);
|
||||||
assert_eq!(Timestamp::parse_datetime("2003-09-16 Tue 0939"), None);
|
}
|
||||||
}
|
|
||||||
|
#[test]
|
||||||
|
fn parse_datetime() {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
Timestamp::parse_datetime("2003-09-16 Tue"),
|
||||||
|
Some((
|
||||||
|
Datetime {
|
||||||
|
date: "2003-09-16",
|
||||||
|
time: None,
|
||||||
|
dayname: "Tue"
|
||||||
|
},
|
||||||
|
None
|
||||||
|
))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Timestamp::parse_datetime("2003-09-16 Tue 9:39"),
|
||||||
|
Some((
|
||||||
|
Datetime {
|
||||||
|
date: "2003-09-16",
|
||||||
|
time: Some("9:39"),
|
||||||
|
dayname: "Tue"
|
||||||
|
},
|
||||||
|
None
|
||||||
|
))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Timestamp::parse_datetime("2003-09-16 Tue 09:39"),
|
||||||
|
Some((
|
||||||
|
Datetime {
|
||||||
|
date: "2003-09-16",
|
||||||
|
time: Some("09:39"),
|
||||||
|
dayname: "Tue"
|
||||||
|
},
|
||||||
|
None
|
||||||
|
))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Timestamp::parse_datetime("2003-09-16 Tue 9:39-10:39"),
|
||||||
|
Some((
|
||||||
|
Datetime {
|
||||||
|
date: "2003-09-16",
|
||||||
|
time: Some("9:39"),
|
||||||
|
dayname: "Tue"
|
||||||
|
},
|
||||||
|
Some(Datetime {
|
||||||
|
date: "2003-09-16",
|
||||||
|
time: Some("10:39"),
|
||||||
|
dayname: "Tue"
|
||||||
|
}),
|
||||||
|
))
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(Timestamp::parse_datetime("2003-9-16 Tue"), None);
|
||||||
|
assert_eq!(Timestamp::parse_datetime("2003-09-16"), None);
|
||||||
|
assert_eq!(Timestamp::parse_datetime("2003-09-16 09:39"), None);
|
||||||
|
assert_eq!(Timestamp::parse_datetime("2003-09-16 Tue 0939"), None);
|
||||||
}
|
}
|
||||||
|
|
|
@ -105,44 +105,20 @@ pub enum Event<'a> {
|
||||||
label: &'a str,
|
label: &'a str,
|
||||||
cont: &'a str,
|
cont: &'a str,
|
||||||
},
|
},
|
||||||
Keyword {
|
Keyword(Keyword<'a>),
|
||||||
key: Key<'a>,
|
|
||||||
value: &'a str,
|
|
||||||
},
|
|
||||||
Rule,
|
Rule,
|
||||||
|
|
||||||
Timestamp(Timestamp<'a>),
|
Timestamp(Timestamp<'a>),
|
||||||
Cookie(Cookie<'a>),
|
Cookie(Cookie<'a>),
|
||||||
FnRef {
|
FnRef(FnRef<'a>),
|
||||||
label: Option<&'a str>,
|
InlineCall(InlineCall<'a>),
|
||||||
def: Option<&'a str>,
|
InlineSrc(InlineSrc<'a>),
|
||||||
},
|
Link(Link<'a>),
|
||||||
InlineCall {
|
Macros(Macros<'a>),
|
||||||
name: &'a str,
|
|
||||||
args: &'a str,
|
|
||||||
inside_header: Option<&'a str>,
|
|
||||||
end_header: Option<&'a str>,
|
|
||||||
},
|
|
||||||
InlineSrc {
|
|
||||||
lang: &'a str,
|
|
||||||
option: Option<&'a str>,
|
|
||||||
body: &'a str,
|
|
||||||
},
|
|
||||||
Link {
|
|
||||||
path: &'a str,
|
|
||||||
desc: Option<&'a str>,
|
|
||||||
},
|
|
||||||
Macros {
|
|
||||||
name: &'a str,
|
|
||||||
args: Option<&'a str>,
|
|
||||||
},
|
|
||||||
RadioTarget {
|
RadioTarget {
|
||||||
target: &'a str,
|
target: &'a str,
|
||||||
},
|
},
|
||||||
Snippet {
|
Snippet(Snippet<'a>),
|
||||||
name: &'a str,
|
|
||||||
value: &'a str,
|
|
||||||
},
|
|
||||||
Target {
|
Target {
|
||||||
target: &'a str,
|
target: &'a str,
|
||||||
},
|
},
|
||||||
|
@ -430,12 +406,17 @@ impl<'a> Parser<'a> {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.or_else(|| {
|
.or_else(|| {
|
||||||
keyword::parse(tail).map(|(key, value, off)| {
|
Keyword::parse(tail).map(|(key, option, value, off)| {
|
||||||
if let Key::Call = key {
|
(
|
||||||
(Event::Call { value }, off + line_begin, 0, 0)
|
if key.eq_ignore_ascii_case("CALL") {
|
||||||
} else {
|
Event::Call { value }
|
||||||
(Event::Keyword { key, value }, off + line_begin, 0, 0)
|
} else {
|
||||||
}
|
Event::Keyword(Keyword::new(key, option, value))
|
||||||
|
},
|
||||||
|
off + line_begin,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
@ -494,10 +475,12 @@ impl<'a> Parser<'a> {
|
||||||
|
|
||||||
let bytes = text.as_bytes();
|
let bytes = text.as_bytes();
|
||||||
match bytes[0] {
|
match bytes[0] {
|
||||||
b'@' if bytes[1] == b'@' => snippet::parse(text)
|
b'@' if bytes[1] == b'@' => {
|
||||||
.map(|(name, value, off)| (Event::Snippet { name, value }, off, 0, 0)),
|
Snippet::parse(text).map(|(snippet, off)| (Event::Snippet(snippet), off, 0, 0))
|
||||||
b'{' if bytes[1] == b'{' && bytes[2] == b'{' => macros::parse(text)
|
}
|
||||||
.map(|(name, args, off)| (Event::Macros { name, args }, off, 0, 0)),
|
b'{' if bytes[1] == b'{' && bytes[2] == b'{' => {
|
||||||
|
Macros::parse(text).map(|(macros, off)| (Event::Macros(macros), off, 0, 0))
|
||||||
|
}
|
||||||
b'<' if bytes[1] == b'<' => {
|
b'<' if bytes[1] == b'<' => {
|
||||||
if bytes[2] == b'<' {
|
if bytes[2] == b'<' {
|
||||||
radio_target::parse(text)
|
radio_target::parse(text)
|
||||||
|
@ -514,13 +497,11 @@ impl<'a> Parser<'a> {
|
||||||
}),
|
}),
|
||||||
b'[' => {
|
b'[' => {
|
||||||
if text[1..].starts_with("fn:") {
|
if text[1..].starts_with("fn:") {
|
||||||
fn_ref::parse(text)
|
FnRef::parse(text).map(|(fn_ref, off)| (Event::FnRef(fn_ref), off, 0, 0))
|
||||||
.map(|(label, def, off)| (Event::FnRef { label, def }, off, 0, 0))
|
|
||||||
} else if bytes[1] == b'[' {
|
} else if bytes[1] == b'[' {
|
||||||
link::parse(text)
|
Link::parse(text).map(|(link, off)| (Event::Link(link), off, 0, 0))
|
||||||
.map(|(path, desc, off)| (Event::Link { path, desc }, off, 0, 0))
|
|
||||||
} else {
|
} else {
|
||||||
cookie::parse(text)
|
Cookie::parse(text)
|
||||||
.map(|(cookie, off)| (Event::Cookie(cookie), off, 0, 0))
|
.map(|(cookie, off)| (Event::Cookie(cookie), off, 0, 0))
|
||||||
.or_else(|| {
|
.or_else(|| {
|
||||||
Timestamp::parse_inactive(text)
|
Timestamp::parse_inactive(text)
|
||||||
|
@ -545,24 +526,10 @@ impl<'a> Parser<'a> {
|
||||||
emphasis::parse(text, b'~').map(|end| (Event::Code(&text[1..end]), end + 1, 0, 0))
|
emphasis::parse(text, b'~').map(|end| (Event::Code(&text[1..end]), end + 1, 0, 0))
|
||||||
}
|
}
|
||||||
b's' if text.starts_with("src_") => {
|
b's' if text.starts_with("src_") => {
|
||||||
inline_src::parse(text).map(|(lang, option, body, off)| {
|
InlineSrc::parse(text).map(|(src, off)| (Event::InlineSrc(src), off, 0, 0))
|
||||||
(Event::InlineSrc { lang, option, body }, off, 0, 0)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
b'c' if text.starts_with("call_") => {
|
b'c' if text.starts_with("call_") => {
|
||||||
inline_call::parse(text).map(|(name, args, inside_header, end_header, off)| {
|
InlineCall::parse(text).map(|(call, off)| (Event::InlineCall(call), off, 0, 0))
|
||||||
(
|
|
||||||
Event::InlineCall {
|
|
||||||
name,
|
|
||||||
args,
|
|
||||||
inside_header,
|
|
||||||
end_header,
|
|
||||||
},
|
|
||||||
off,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
use crate::elements::{fn_def, keyword, Key};
|
use crate::elements::{fn_def, Keyword};
|
||||||
use crate::headline::{Headline, DEFAULT_KEYWORDS};
|
use crate::headline::{Headline, DEFAULT_KEYWORDS};
|
||||||
use memchr::memchr;
|
use memchr::memchr;
|
||||||
|
|
||||||
type Headlines<'a> = Vec<Headline<'a>>;
|
type Headlines<'a> = Vec<Headline<'a>>;
|
||||||
type Keywords<'a> = Vec<(Key<'a>, &'a str)>;
|
type Keywords<'a> = Vec<(&'a str, &'a str)>;
|
||||||
type Footnotes<'a> = Vec<&'a str>;
|
type Footnotes<'a> = Vec<&'a str>;
|
||||||
|
|
||||||
pub fn metadata(src: &str) -> (Headlines<'_>, Keywords<'_>, Footnotes<'_>) {
|
pub fn metadata(src: &str) -> (Headlines<'_>, Keywords<'_>, Footnotes<'_>) {
|
||||||
|
@ -16,7 +16,7 @@ pub fn metadata(src: &str) -> (Headlines<'_>, Keywords<'_>, Footnotes<'_>) {
|
||||||
headlines.push(Headline::parse(line, DEFAULT_KEYWORDS).0)
|
headlines.push(Headline::parse(line, DEFAULT_KEYWORDS).0)
|
||||||
}
|
}
|
||||||
} else if line.starts_with("#+") {
|
} else if line.starts_with("#+") {
|
||||||
if let Some((key, value, _)) = keyword::parse(line) {
|
if let Some((key, _, value, _)) = Keyword::parse(line) {
|
||||||
keywords.push((key, value))
|
keywords.push((key, value))
|
||||||
}
|
}
|
||||||
} else if line.starts_with("[fn:") {
|
} else if line.starts_with("[fn:") {
|
||||||
|
|
Loading…
Reference in a new issue