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;
|
||||
|
||||
/// 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,17 +37,16 @@ impl<'a> Clock<'a> {
|
|||
return None;
|
||||
}
|
||||
|
||||
match Timestamp::parse_inactive(tail).map(|(t, off)| (t, tail[off..].trim_start())) {
|
||||
Some((
|
||||
let (timestamp, tail) =
|
||||
Timestamp::parse_inactive(tail).map(|(t, off)| (t, tail[off..].trim_start()))?;
|
||||
|
||||
match timestamp {
|
||||
Timestamp::InactiveRange {
|
||||
start,
|
||||
end,
|
||||
repeater,
|
||||
delay,
|
||||
},
|
||||
tail,
|
||||
)) => {
|
||||
if tail.starts_with("=>") {
|
||||
} 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)
|
||||
|
@ -55,7 +54,7 @@ impl<'a> Clock<'a> {
|
|||
&& duration.as_bytes()[colon + 1].is_ascii_digit()
|
||||
&& duration.as_bytes()[colon + 2].is_ascii_digit()
|
||||
{
|
||||
return Some((
|
||||
Some((
|
||||
Clock::Closed {
|
||||
start,
|
||||
end,
|
||||
|
@ -64,34 +63,32 @@ impl<'a> Clock<'a> {
|
|||
duration,
|
||||
},
|
||||
off,
|
||||
));
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
Some((
|
||||
Timestamp::Inactive {
|
||||
start,
|
||||
repeater,
|
||||
delay,
|
||||
},
|
||||
tail,
|
||||
)) => {
|
||||
} => {
|
||||
if tail.as_bytes().iter().all(u8::is_ascii_whitespace) {
|
||||
return Some((
|
||||
Some((
|
||||
Clock::Running {
|
||||
start,
|
||||
repeater,
|
||||
delay,
|
||||
},
|
||||
off,
|
||||
));
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// returns `true` if the clock is running
|
||||
pub fn is_running(&self) -> bool {
|
||||
|
@ -146,20 +143,15 @@ impl<'a> Clock<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Clock;
|
||||
use crate::objects::timestamp::Datetime;
|
||||
|
||||
#[test]
|
||||
fn parse() {
|
||||
#[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)),
|
||||
date: "2003-09-16",
|
||||
time: Some("09:39"),
|
||||
dayname: "Tue"
|
||||
},
|
||||
repeater: None,
|
||||
|
@ -173,13 +165,13 @@ mod tests {
|
|||
Some((
|
||||
Clock::Closed {
|
||||
start: Datetime {
|
||||
date: (2003, 9, 16),
|
||||
time: Some((9, 39)),
|
||||
date: "2003-09-16",
|
||||
time: Some("09:39"),
|
||||
dayname: "Tue"
|
||||
},
|
||||
end: Datetime {
|
||||
date: (2003, 9, 16),
|
||||
time: Some((10, 39)),
|
||||
date: "2003-09-16",
|
||||
time: Some("10:39"),
|
||||
dayname: "Tue"
|
||||
},
|
||||
repeater: None,
|
||||
|
@ -189,5 +181,4 @@ mod tests {
|
|||
"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,12 +17,44 @@ pub enum Key<'a> {
|
|||
Date,
|
||||
Title,
|
||||
Custom(&'a str),
|
||||
|
||||
// Babel Call
|
||||
Call,
|
||||
}
|
||||
|
||||
pub fn parse(text: &str) -> Option<(Key<'_>, &str, usize)> {
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
#[derive(Debug)]
|
||||
pub struct Keyword<'a> {
|
||||
pub key: Key<'a>,
|
||||
pub value: &'a str,
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
},
|
||||
value,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn parse(text: &str) -> Option<(&str, Option<&str>, &str, usize)> {
|
||||
debug_assert!(text.starts_with("#+"));
|
||||
|
||||
let bytes = text.as_bytes();
|
||||
|
@ -38,9 +70,11 @@ pub fn parse(text: &str) -> Option<(Key<'_>, &str, usize)> {
|
|||
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':'
|
||||
bytes[off..i].iter().all(|&c| c != b'\n')
|
||||
&& i < text.len()
|
||||
&& bytes[i + 1] == b':'
|
||||
})
|
||||
.map(|i| (Some(&text[off..i]), i + 2 /* ]: */))?
|
||||
.map(|i| (Some(&text[off..i]), i + "]:".len()))?
|
||||
} else {
|
||||
(None, off)
|
||||
};
|
||||
|
@ -49,80 +83,58 @@ pub fn parse(text: &str) -> Option<(Key<'_>, &str, usize)> {
|
|||
.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.trim(),
|
||||
off,
|
||||
))
|
||||
Some((key, option, value.trim(), off))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn parse() {
|
||||
use super::*;
|
||||
#[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!(
|
||||
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!(
|
||||
parse("#+RESULTS:"),
|
||||
Some((Key::Results { option: None }, "", "#+RESULTS:".len()))
|
||||
Keyword::parse("#+RESULTS:"),
|
||||
Some(("RESULTS", None, "", "#+RESULTS:".len()))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
parse("#+ATTR_LATEX: :width 5cm"),
|
||||
Keyword::parse("#+ATTR_LATEX: :width 5cm"),
|
||||
Some((
|
||||
Key::Attr { backend: "LATEX" },
|
||||
"ATTR_LATEX",
|
||||
None,
|
||||
":width 5cm",
|
||||
"#+ATTR_LATEX: :width 5cm".len()
|
||||
))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
parse("#+CALL: double(n=4)"),
|
||||
Some((Key::Call, "double(n=4)", "#+CALL: double(n=4)".len()))
|
||||
Keyword::parse("#+CALL: double(n=4)"),
|
||||
Some(("CALL", None, "double(n=4)", "#+CALL: double(n=4)".len()))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
parse("#+CAPTION[Short caption]: Longer caption."),
|
||||
Keyword::parse("#+CAPTION[Short caption]: Longer caption."),
|
||||
Some((
|
||||
Key::Caption {
|
||||
option: Some("Short caption")
|
||||
},
|
||||
"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,12 +58,9 @@ 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"),
|
||||
|
@ -71,7 +68,7 @@ mod tests {
|
|||
Planning {
|
||||
scheduled: Some(Timestamp::Active {
|
||||
start: Datetime {
|
||||
date: (2019, 4, 8),
|
||||
date: "2019-04-08",
|
||||
time: None,
|
||||
dayname: "Mon"
|
||||
},
|
||||
|
@ -84,5 +81,4 @@ mod tests {
|
|||
"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
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ pub trait HtmlHandler<W: Write, E: From<Error>> {
|
|||
fn headline_beg(&mut self, w: &mut W, hdl: Headline) -> Result<(), E> {
|
||||
let level = if hdl.level <= 6 { hdl.level } else { 6 };
|
||||
write!(w, "<h{}>", level)?;
|
||||
self.escape(w, hdl.title)?;
|
||||
self.text(w, hdl.title)?;
|
||||
write!(w, "</h{}>", level)?;
|
||||
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> {
|
||||
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(())
|
||||
}
|
||||
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> {
|
||||
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(())
|
||||
}
|
||||
fn inline_call(
|
||||
&mut self,
|
||||
w: &mut W,
|
||||
name: &str,
|
||||
args: &str,
|
||||
inside_header: Option<&str>,
|
||||
end_header: Option<&str>,
|
||||
) -> Result<(), E> {
|
||||
fn inline_call(&mut self, w: &mut W, call: InlineCall<'_>) -> Result<(), E> {
|
||||
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>")?;
|
||||
self.escape(w, body)?;
|
||||
self.text(w, src.body)?;
|
||||
write!(w, "</code>")?;
|
||||
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=""#)?;
|
||||
self.escape(w, path)?;
|
||||
self.text(w, link.path)?;
|
||||
write!(w, r#"">"#)?;
|
||||
self.escape(w, desc.unwrap_or(path))?;
|
||||
self.text(w, link.desc.unwrap_or(link.path))?;
|
||||
write!(w, "</a>")?;
|
||||
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(())
|
||||
}
|
||||
fn radio_target(&mut self, w: &mut W, target: &str) -> Result<(), E> {
|
||||
Ok(())
|
||||
}
|
||||
fn snippet(&mut self, w: &mut W, name: &str, value: &str) -> Result<(), E> {
|
||||
if name.eq_ignore_ascii_case("HTML") {
|
||||
Ok(write!(w, "{}", value)?)
|
||||
fn snippet(&mut self, w: &mut W, snippet: Snippet<'_>) -> Result<(), E> {
|
||||
if snippet.name.eq_ignore_ascii_case("HTML") {
|
||||
Ok(write!(w, "{}", snippet.value)?)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
|
@ -236,13 +229,13 @@ pub trait HtmlHandler<W: Write, E: From<Error>> {
|
|||
}
|
||||
fn verbatim(&mut self, w: &mut W, cont: &str) -> Result<(), E> {
|
||||
write!(w, "<code>")?;
|
||||
self.escape(w, cont)?;
|
||||
self.text(w, cont)?;
|
||||
write!(w, "</code>")?;
|
||||
Ok(())
|
||||
}
|
||||
fn code(&mut self, w: &mut W, cont: &str) -> Result<(), E> {
|
||||
write!(w, "<code>")?;
|
||||
self.escape(w, cont)?;
|
||||
self.text(w, cont)?;
|
||||
write!(w, "</code>")?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -40,21 +40,16 @@ macro_rules! handle_event {
|
|||
TableCell => $handler.table_cell($writer)?,
|
||||
LatexEnv => $handler.latex_env($writer)?,
|
||||
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)?,
|
||||
Cookie(cookie) => $handler.cookie($writer, cookie)?,
|
||||
FnRef { label, def } => $handler.fn_ref($writer, label, def)?,
|
||||
InlineSrc { lang, option, body } => $handler.inline_src($writer, lang, option, body)?,
|
||||
InlineCall {
|
||||
name,
|
||||
args,
|
||||
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)?,
|
||||
FnRef(fn_ref) => $handler.fn_ref($writer, fn_ref)?,
|
||||
InlineSrc(src) => $handler.inline_src($writer, src)?,
|
||||
InlineCall(call) => $handler.inline_call($writer, call)?,
|
||||
Link(link) => $handler.link($writer, link)?,
|
||||
Macros(macros) => $handler.macros($writer, macros)?,
|
||||
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)?,
|
||||
BoldBeg => $handler.bold_beg($writer)?,
|
||||
BoldEnd => $handler.bold_end($writer)?,
|
||||
|
|
|
@ -7,8 +7,9 @@ pub enum Cookie<'a> {
|
|||
Slash(&'a str, &'a str),
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn parse(src: &str) -> Option<(Cookie<'_>, usize)> {
|
||||
impl<'a> Cookie<'a> {
|
||||
#[inline]
|
||||
pub(crate) fn parse(src: &str) -> Option<(Cookie<'_>, usize)> {
|
||||
debug_assert!(src.starts_with('['));
|
||||
|
||||
let bytes = src.as_bytes();
|
||||
|
@ -18,34 +19,47 @@ pub fn parse(src: &str) -> Option<(Cookie<'_>, usize)> {
|
|||
if bytes[num1] == b'%' && *bytes.get(num1 + 1)? == b']' {
|
||||
Some((Cookie::Percent(&src[1..num1]), num1 + 2))
|
||||
} else {
|
||||
let num2 =
|
||||
memchr(b']', bytes).filter(|&i| bytes[num1 + 1..i].iter().all(u8::is_ascii_digit))?;
|
||||
let num2 = memchr(b']', bytes)
|
||||
.filter(|&i| bytes[num1 + 1..i].iter().all(u8::is_ascii_digit))?;
|
||||
|
||||
Some((Cookie::Slash(&src[1..num1], &src[num1 + 1..num2]), num2 + 1))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn parse() {
|
||||
use super::parse;
|
||||
use super::Cookie::*;
|
||||
|
||||
assert_eq!(parse("[1/10]"), Some((Slash("1", "10"), "[1/10]".len())));
|
||||
assert_eq!(
|
||||
parse("[1/1000]"),
|
||||
Some((Slash("1", "1000"), "[1/1000]".len()))
|
||||
);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse() {
|
||||
assert_eq!(
|
||||
Cookie::parse("[1/10]"),
|
||||
Some((Cookie::Slash("1", "10"), "[1/10]".len()))
|
||||
);
|
||||
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!(Cookie::parse("[10% ]"), None);
|
||||
assert_eq!(Cookie::parse("[1//100]"), None);
|
||||
assert_eq!(Cookie::parse("[1\\100]"), None);
|
||||
assert_eq!(Cookie::parse("[10%%]"), None);
|
||||
}
|
||||
|
|
|
@ -1,20 +1,36 @@
|
|||
use memchr::{memchr2, memchr2_iter};
|
||||
|
||||
/// returns (footnote reference label, footnote reference definition, offset)
|
||||
#[inline]
|
||||
pub fn parse(text: &str) -> Option<(Option<&str>, Option<&str>, usize)> {
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
#[derive(Debug)]
|
||||
pub struct FnRef<'a> {
|
||||
pub label: Option<&'a str>,
|
||||
pub definition: Option<&'a str>,
|
||||
}
|
||||
|
||||
impl<'a> FnRef<'a> {
|
||||
#[inline]
|
||||
pub fn parse(text: &str) -> Option<(FnRef<'_>, usize)> {
|
||||
debug_assert!(text.starts_with("[fn:"));
|
||||
|
||||
let bytes = text.as_bytes();
|
||||
let (label, off) = memchr2(b']', b':', &bytes[4..])
|
||||
let (label, off) = memchr2(b']', b':', &bytes["[fn:".len()..])
|
||||
.filter(|&i| {
|
||||
bytes[4..i + 4]
|
||||
bytes["[fn:".len().."[fn:".len() + i]
|
||||
.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))?;
|
||||
.map(|i| {
|
||||
(
|
||||
if i == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(&text["[fn:".len().."[fn:".len() + i])
|
||||
},
|
||||
"[fn:".len() + i,
|
||||
)
|
||||
})?;
|
||||
|
||||
let (def, off) = if bytes[off] == b':' {
|
||||
let (definition, off) = if bytes[off] == b':' {
|
||||
let mut pairs = 1;
|
||||
memchr2_iter(b'[', b']', &bytes[off..])
|
||||
.find(|&i| {
|
||||
|
@ -30,25 +46,51 @@ pub fn parse(text: &str) -> Option<(Option<&str>, Option<&str>, usize)> {
|
|||
(None, off + 1)
|
||||
};
|
||||
|
||||
Some((label, def, off))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn parse() {
|
||||
use super::parse;
|
||||
|
||||
assert_eq!(parse("[fn:1]"), Some((Some("1"), None, "[fn:1]".len())));
|
||||
assert_eq!(
|
||||
parse("[fn:1:2]"),
|
||||
Some((Some("1"), Some("2"), "[fn:1:2]".len()))
|
||||
);
|
||||
assert_eq!(parse("[fn::2]"), Some((None, Some("2"), "[fn::2]".len())));
|
||||
assert_eq!(
|
||||
parse("[fn::[]]"),
|
||||
Some((None, Some("[]"), "[fn::[]]".len()))
|
||||
);
|
||||
assert_eq!(parse("[fn::[]"), None);
|
||||
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,20 +1,30 @@
|
|||
use memchr::{memchr, memchr2};
|
||||
|
||||
// returns (name, args, inside_header, end_header)
|
||||
#[inline]
|
||||
pub fn parse(text: &str) -> Option<(&str, &str, Option<&str>, Option<&str>, usize)> {
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
#[derive(Debug)]
|
||||
pub struct InlineCall<'a> {
|
||||
pub name: &'a str,
|
||||
pub inside_header: Option<&'a str>,
|
||||
pub args: &'a str,
|
||||
pub end_header: Option<&'a str>,
|
||||
}
|
||||
|
||||
impl<'a> InlineCall<'a> {
|
||||
#[inline]
|
||||
pub(crate) fn parse(text: &str) -> Option<(InlineCall<'_>, usize)> {
|
||||
debug_assert!(text.starts_with("call_"));
|
||||
|
||||
let bytes = text.as_bytes();
|
||||
|
||||
let (name, off) = memchr2(b'[', b'(', bytes)
|
||||
.map(|i| (&text[5..i], i))
|
||||
.map(|i| (&text["call_".len()..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')
|
||||
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 {
|
||||
|
@ -33,48 +43,66 @@ pub fn parse(text: &str) -> Option<(&str, &str, Option<&str>, Option<&str>, usiz
|
|||
(None, off)
|
||||
};
|
||||
|
||||
Some((name, args, inside_header, end_header, off))
|
||||
Some((
|
||||
InlineCall {
|
||||
name,
|
||||
args,
|
||||
inside_header,
|
||||
end_header,
|
||||
},
|
||||
off,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn parse() {
|
||||
use super::parse;
|
||||
|
||||
#[test]
|
||||
fn parse() {
|
||||
assert_eq!(
|
||||
parse("call_square(4)"),
|
||||
Some(("square", "4", None, None, "call_square(4)".len()))
|
||||
InlineCall::parse("call_square(4)"),
|
||||
Some((
|
||||
InlineCall {
|
||||
name: "square",
|
||||
args: "4",
|
||||
inside_header: None,
|
||||
end_header: None,
|
||||
},
|
||||
"call_square(4)".len()
|
||||
))
|
||||
);
|
||||
assert_eq!(
|
||||
parse("call_square[:results output](4)"),
|
||||
InlineCall::parse("call_square[:results output](4)"),
|
||||
Some((
|
||||
"square",
|
||||
"4",
|
||||
Some(":results output"),
|
||||
None,
|
||||
InlineCall {
|
||||
name: "square",
|
||||
args: "4",
|
||||
inside_header: Some(":results output"),
|
||||
end_header: None,
|
||||
},
|
||||
"call_square[:results output](4)".len()
|
||||
))
|
||||
);
|
||||
assert_eq!(
|
||||
parse("call_square(4)[:results html]"),
|
||||
InlineCall::parse("call_square(4)[:results html]"),
|
||||
Some((
|
||||
"square",
|
||||
"4",
|
||||
None,
|
||||
Some(":results html"),
|
||||
InlineCall {
|
||||
name: "square",
|
||||
args: "4",
|
||||
inside_header: None,
|
||||
end_header: Some(":results html"),
|
||||
},
|
||||
"call_square(4)[:results html]".len()
|
||||
))
|
||||
);
|
||||
assert_eq!(
|
||||
parse("call_square[:results output](4)[:results html]"),
|
||||
InlineCall::parse("call_square[:results output](4)[:results html]"),
|
||||
Some((
|
||||
"square",
|
||||
"4",
|
||||
Some(":results output"),
|
||||
Some(":results html"),
|
||||
InlineCall {
|
||||
name: "square",
|
||||
args: "4",
|
||||
inside_header: Some(":results output"),
|
||||
end_header: Some(":results html"),
|
||||
},
|
||||
"call_square[:results output](4)[:results html]".len()
|
||||
))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,20 @@
|
|||
use memchr::{memchr, memchr2};
|
||||
|
||||
/// returns (language, option, body, offset)
|
||||
#[inline]
|
||||
pub fn parse(text: &str) -> Option<(&str, Option<&str>, &str, usize)> {
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
#[derive(Debug)]
|
||||
pub struct InlineSrc<'a> {
|
||||
pub lang: &'a str,
|
||||
pub option: Option<&'a str>,
|
||||
pub body: &'a str,
|
||||
}
|
||||
|
||||
impl<'a> InlineSrc<'a> {
|
||||
#[inline]
|
||||
pub(crate) fn parse(text: &str) -> Option<(InlineSrc<'_>, usize)> {
|
||||
debug_assert!(text.starts_with("src_"));
|
||||
|
||||
let (lang, off) = memchr2(b'[', b'{', text.as_bytes())
|
||||
.map(|i| (&text[4..i], i))
|
||||
.map(|i| (&text["src_".len()..i], i))
|
||||
.filter(|(lang, off)| {
|
||||
*off != 4 && lang.as_bytes().iter().all(|c| !c.is_ascii_whitespace())
|
||||
})?;
|
||||
|
@ -23,30 +31,41 @@ pub fn parse(text: &str) -> Option<(&str, Option<&str>, &str, usize)> {
|
|||
.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))
|
||||
Some((InlineSrc { lang, option, body }, off))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn parse() {
|
||||
use super::parse;
|
||||
|
||||
#[test]
|
||||
fn parse() {
|
||||
assert_eq!(
|
||||
parse("src_C{int a = 0;}"),
|
||||
Some(("C", None, "int a = 0;", "src_C{int a = 0;}".len()))
|
||||
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!(
|
||||
parse("src_xml[:exports code]{<tag>text</tag>}"),
|
||||
InlineSrc::parse("src_xml[:exports code]{<tag>text</tag>}"),
|
||||
Some((
|
||||
"xml",
|
||||
Some(":exports code"),
|
||||
"<tag>text</tag>",
|
||||
InlineSrc {
|
||||
lang: "xml",
|
||||
option: Some(":exports code"),
|
||||
body: "<tag>text</tag>",
|
||||
},
|
||||
"src_xml[:exports code]{<tag>text</tag>}".len()
|
||||
))
|
||||
);
|
||||
assert_eq!(parse("src_xml[:exports code]{<tag>text</tag>"), None);
|
||||
assert_eq!(parse("src_[:exports code]{<tag>text</tag>}"), None);
|
||||
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,13 +1,20 @@
|
|||
use jetscii::Substring;
|
||||
use memchr::memchr;
|
||||
|
||||
/// returns (link path, link description, offset)
|
||||
#[inline]
|
||||
pub fn parse(text: &str) -> Option<(&str, Option<&str>, usize)> {
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
#[derive(Debug)]
|
||||
pub struct Link<'a> {
|
||||
pub path: &'a str,
|
||||
pub desc: Option<&'a str>,
|
||||
}
|
||||
|
||||
impl<'a> Link<'a> {
|
||||
#[inline]
|
||||
pub(crate) fn parse(text: &str) -> Option<(Link<'_>, usize)> {
|
||||
debug_assert!(text.starts_with("[["));
|
||||
|
||||
let (path, off) = memchr(b']', text.as_bytes())
|
||||
.map(|i| (&text[2..i], i))
|
||||
.map(|i| (&text["[[".len()..i], i))
|
||||
.filter(|(path, _)| {
|
||||
path.as_bytes()
|
||||
.iter()
|
||||
|
@ -15,29 +22,46 @@ pub fn parse(text: &str) -> Option<(&str, Option<&str>, usize)> {
|
|||
})?;
|
||||
|
||||
if *text.as_bytes().get(off + 1)? == b']' {
|
||||
Some((path, None, off + 2))
|
||||
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 + i + 1], off + i + 3))
|
||||
.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((path, Some(desc), off))
|
||||
Some((
|
||||
Link {
|
||||
path,
|
||||
desc: Some(desc),
|
||||
},
|
||||
off,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn parse() {
|
||||
use super::parse;
|
||||
|
||||
assert_eq!(parse("[[#id]]"), Some(("#id", None, "[[#id]]".len())));
|
||||
assert_eq!(
|
||||
parse("[[#id][desc]]"),
|
||||
Some(("#id", Some("desc"), "[[#id][desc]]".len()))
|
||||
);
|
||||
assert_eq!(parse("[[#id][desc]"), None);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse() {
|
||||
assert_eq!(
|
||||
Link::parse("[[#id]]"),
|
||||
Some((
|
||||
Link {
|
||||
path: "#id",
|
||||
desc: None
|
||||
},
|
||||
"[[#id]]".len()
|
||||
))
|
||||
);
|
||||
assert_eq!(
|
||||
Link::parse("[[#id][desc]]"),
|
||||
Some((
|
||||
Link {
|
||||
path: "#id",
|
||||
desc: Some("desc")
|
||||
},
|
||||
"[[#id][desc]]".len()
|
||||
))
|
||||
);
|
||||
assert_eq!(Link::parse("[[#id][desc]"), None);
|
||||
}
|
||||
|
|
|
@ -1,9 +1,16 @@
|
|||
use jetscii::Substring;
|
||||
use memchr::memchr2;
|
||||
|
||||
/// returns (macros name, macros arguments, offset)
|
||||
#[inline]
|
||||
pub fn parse(text: &str) -> Option<(&str, Option<&str>, usize)> {
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
#[derive(Debug)]
|
||||
pub struct Macros<'a> {
|
||||
pub name: &'a str,
|
||||
pub arguments: Option<&'a str>,
|
||||
}
|
||||
|
||||
impl<'a> Macros<'a> {
|
||||
#[inline]
|
||||
pub fn parse(text: &str) -> Option<(Macros<'_>, usize)> {
|
||||
debug_assert!(text.starts_with("{{{"));
|
||||
|
||||
let bytes = text.as_bytes();
|
||||
|
@ -19,42 +26,55 @@ pub fn parse(text: &str) -> Option<(&str, Option<&str>, usize)> {
|
|||
})
|
||||
.map(|i| (&text[3..i], i))?;
|
||||
|
||||
let (args, off) = if bytes[off] == b'}' {
|
||||
let (arguments, off) = if bytes[off] == b'}' {
|
||||
if text.len() <= off + 2 || bytes[off + 1] != b'}' || bytes[off + 2] != b'}' {
|
||||
return None;
|
||||
}
|
||||
(None, off + 3 /* }}} */)
|
||||
(None, off + "}}}".len())
|
||||
} else {
|
||||
Substring::new(")}}}")
|
||||
.find(&text[off..])
|
||||
.map(|i| (Some(&text[off + 1..off + i]), off + i + 4 /* )}}} */))?
|
||||
.map(|i| (Some(&text[off + 1..off + i]), off + i + ")}}}".len()))?
|
||||
};
|
||||
|
||||
Some((name, args, off))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn parse() {
|
||||
use super::parse;
|
||||
|
||||
assert_eq!(
|
||||
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);
|
||||
Some((Macros { name, arguments }, off))
|
||||
}
|
||||
}
|
||||
|
||||
#[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 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::*;
|
||||
|
|
|
@ -15,7 +15,7 @@ pub fn parse(src: &str) -> Option<(&str, usize)> {
|
|||
.iter()
|
||||
.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))
|
||||
}
|
||||
|
|
|
@ -1,9 +1,16 @@
|
|||
use jetscii::Substring;
|
||||
use memchr::memchr;
|
||||
|
||||
/// returns (snippet name, snippet value, offset)
|
||||
#[inline]
|
||||
pub fn parse(text: &str) -> Option<(&str, &str, usize)> {
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
#[derive(Debug)]
|
||||
pub struct Snippet<'a> {
|
||||
pub name: &'a str,
|
||||
pub value: &'a str,
|
||||
}
|
||||
|
||||
impl<'a> Snippet<'a> {
|
||||
#[inline]
|
||||
pub(crate) fn parse(text: &str) -> Option<(Snippet<'_>, usize)> {
|
||||
debug_assert!(text.starts_with("@@"));
|
||||
|
||||
let (name, off) = memchr(b':', text.as_bytes())
|
||||
|
@ -17,32 +24,45 @@ pub fn parse(text: &str) -> Option<(&str, &str, usize)> {
|
|||
|
||||
let (value, off) = Substring::new("@@")
|
||||
.find(&text[off..])
|
||||
.map(|i| (&text[off..off + i], off + i + 2 /* @@ */))?;
|
||||
.map(|i| (&text[off..off + i], off + i + "@@".len()))?;
|
||||
|
||||
Some((name, value, off))
|
||||
Some((Snippet { name, value }, off))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn parse() {
|
||||
use super::parse;
|
||||
|
||||
#[test]
|
||||
fn parse() {
|
||||
assert_eq!(
|
||||
parse("@@html:<b>@@"),
|
||||
Some(("html", "<b>", "@@html:<b>@@".len()))
|
||||
Snippet::parse("@@html:<b>@@"),
|
||||
Some((
|
||||
Snippet {
|
||||
name: "html",
|
||||
value: "<b>"
|
||||
},
|
||||
"@@html:<b>@@".len()
|
||||
))
|
||||
);
|
||||
assert_eq!(
|
||||
parse("@@latex:any arbitrary LaTeX code@@"),
|
||||
Snippet::parse("@@latex:any arbitrary LaTeX code@@"),
|
||||
Some((
|
||||
"latex",
|
||||
"any arbitrary LaTeX code",
|
||||
Snippet {
|
||||
name: "latex",
|
||||
value: "any arbitrary LaTeX code",
|
||||
},
|
||||
"@@latex:any arbitrary LaTeX code@@".len()
|
||||
))
|
||||
);
|
||||
assert_eq!(parse("@@html:@@"), Some(("html", "", "@@html:@@".len())));
|
||||
assert_eq!(parse("@@html:<b>@"), None);
|
||||
assert_eq!(parse("@@html<b>@@"), None);
|
||||
assert_eq!(parse("@@:<b>@@"), None);
|
||||
}
|
||||
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()
|
||||
.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)]
|
||||
|
|
|
@ -1,11 +1,50 @@
|
|||
use memchr::memchr;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Datetime<'a> {
|
||||
pub date: (u16, u8, u8),
|
||||
pub time: Option<(u8, u8)>,
|
||||
pub dayname: &'a str,
|
||||
pub(crate) date: &'a str,
|
||||
pub(crate) time: Option<&'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")]
|
||||
|
@ -15,16 +54,11 @@ mod chrono {
|
|||
|
||||
impl<'a> Datetime<'a> {
|
||||
pub fn naive_date(&self) -> NaiveDate {
|
||||
let (y, m, d) = self.date;
|
||||
NaiveDate::from_ymd(y.into(), m.into(), d.into())
|
||||
NaiveDate::from_ymd(self.year() as i32, self.month(), self.day())
|
||||
}
|
||||
|
||||
pub fn naive_time(&self) -> NaiveTime {
|
||||
if let Some((h, m)) = self.time {
|
||||
NaiveTime::from_hms(h.into(), m.into(), 0)
|
||||
} else {
|
||||
NaiveTime::from_hms(0, 0, 0)
|
||||
}
|
||||
NaiveTime::from_hms(self.hour().unwrap_or(0), self.minute().unwrap_or(0), 0)
|
||||
}
|
||||
|
||||
pub fn naive_date_time(&self) -> NaiveDateTime {
|
||||
|
@ -88,25 +122,25 @@ pub struct Delay {
|
|||
pub enum Timestamp<'a> {
|
||||
Active {
|
||||
start: Datetime<'a>,
|
||||
repeater: Option<Repeater>,
|
||||
delay: Option<Delay>,
|
||||
repeater: Option<&'a str>,
|
||||
delay: Option<&'a str>,
|
||||
},
|
||||
Inactive {
|
||||
start: Datetime<'a>,
|
||||
repeater: Option<Repeater>,
|
||||
delay: Option<Delay>,
|
||||
repeater: Option<&'a str>,
|
||||
delay: Option<&'a str>,
|
||||
},
|
||||
ActiveRange {
|
||||
start: Datetime<'a>,
|
||||
end: Datetime<'a>,
|
||||
repeater: Option<Repeater>,
|
||||
delay: Option<Delay>,
|
||||
repeater: Option<&'a str>,
|
||||
delay: Option<&'a str>,
|
||||
},
|
||||
InactiveRange {
|
||||
start: Datetime<'a>,
|
||||
end: Datetime<'a>,
|
||||
repeater: Option<Repeater>,
|
||||
delay: Option<Delay>,
|
||||
repeater: Option<&'a str>,
|
||||
delay: Option<&'a str>,
|
||||
},
|
||||
Diary(&'a str),
|
||||
}
|
||||
|
@ -129,7 +163,9 @@ impl<'a> Timestamp<'a> {
|
|||
let mut off = memchr(b'>', bytes)?;
|
||||
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((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 mut off = memchr(b']', bytes)?;
|
||||
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((start, _)) = Self::parse_datetime(&text[off + 4..off + 1 + new_off]) {
|
||||
|
@ -203,9 +241,7 @@ impl<'a> Timestamp<'a> {
|
|||
|
||||
let mut words = text.split_ascii_whitespace();
|
||||
|
||||
let date = words
|
||||
.next()
|
||||
.filter(|word| {
|
||||
let date = words.next().filter(|word| {
|
||||
let word = word.as_bytes();
|
||||
// YYYY-MM-DD
|
||||
word.len() == 10
|
||||
|
@ -214,17 +250,6 @@ impl<'a> Timestamp<'a> {
|
|||
&& word[5..7].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| {
|
||||
|
@ -239,86 +264,109 @@ impl<'a> Timestamp<'a> {
|
|||
})?;
|
||||
|
||||
let (start, end) = if let Some(word) = words.next() {
|
||||
let word = word.as_bytes();
|
||||
let time = word.as_bytes();
|
||||
|
||||
macro_rules! datetime {
|
||||
($a:expr, $b:expr, $c:expr) => {
|
||||
if (time.len() == "H:MM".len()
|
||||
&& 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 {
|
||||
date,
|
||||
dayname,
|
||||
time: Some((word[$a] - b'0', (word[$b] - b'0') * 10 + (word[$c] - b'0'))),
|
||||
}
|
||||
};
|
||||
($a:expr, $b:expr, $c:expr, $d:expr) => {
|
||||
time: Some(word),
|
||||
},
|
||||
None,
|
||||
)
|
||||
} 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 {
|
||||
date,
|
||||
dayname,
|
||||
time: Some((
|
||||
(word[$a] - b'0') * 10 + (word[$b] - b'0'),
|
||||
(word[$c] - b'0') * 10 + (word[$d] - b'0'),
|
||||
)),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if word.len() == 4
|
||||
&& word[0].is_ascii_digit()
|
||||
&& word[1] == b':'
|
||||
&& word[2..4].iter().all(u8::is_ascii_digit)
|
||||
time: Some(&word[0..4]),
|
||||
},
|
||||
Some(Datetime {
|
||||
date,
|
||||
dayname,
|
||||
time: Some(&word[5..9]),
|
||||
}),
|
||||
)
|
||||
} else if time.len() == "H:MM-HH:MM".len()
|
||||
&& time[0].is_ascii_digit()
|
||||
&& time[1] == b':'
|
||||
&& 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)
|
||||
} else if word.len() == 5
|
||||
&& word[0..2].iter().all(u8::is_ascii_digit)
|
||||
&& word[2] == b':'
|
||||
&& word[3..5].iter().all(u8::is_ascii_digit)
|
||||
(
|
||||
Datetime {
|
||||
date,
|
||||
dayname,
|
||||
time: Some(&word[0..4]),
|
||||
},
|
||||
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)
|
||||
} else if word.len() == 9
|
||||
&& word[0].is_ascii_digit()
|
||||
&& word[1] == b':'
|
||||
&& word[2..4].iter().all(u8::is_ascii_digit)
|
||||
&& word[4] == b'-'
|
||||
&& word[5].is_ascii_digit()
|
||||
&& word[6] == b':'
|
||||
&& word[7..9].iter().all(u8::is_ascii_digit)
|
||||
(
|
||||
Datetime {
|
||||
date,
|
||||
dayname,
|
||||
time: Some(&word[0..5]),
|
||||
},
|
||||
Some(Datetime {
|
||||
date,
|
||||
dayname,
|
||||
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)))
|
||||
} else if word.len() == 10
|
||||
&& word[0].is_ascii_digit()
|
||||
&& word[1] == b':'
|
||||
&& word[2..4].iter().all(u8::is_ascii_digit)
|
||||
&& word[4] == b'-'
|
||||
&& word[5..7].iter().all(u8::is_ascii_digit)
|
||||
&& word[7] == b':'
|
||||
&& word[8..10].iter().all(u8::is_ascii_digit)
|
||||
{
|
||||
// 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)))
|
||||
(
|
||||
Datetime {
|
||||
date,
|
||||
dayname,
|
||||
time: Some(&word[0..5]),
|
||||
},
|
||||
Some(Datetime {
|
||||
date,
|
||||
dayname,
|
||||
time: Some(&word[6..11]),
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
|
@ -344,22 +392,22 @@ impl<'a> Timestamp<'a> {
|
|||
pub(crate) fn parse_diary(text: &str) -> Option<(Timestamp<'_>, usize)> {
|
||||
debug_assert!(text.starts_with('<'));
|
||||
|
||||
if text.len() <= 6 /* <%%()> */ || &text[1..4] != "%%(" {
|
||||
if text.len() <= "<%%()>".len() || &text[1..4] != "%%(" {
|
||||
return None;
|
||||
}
|
||||
|
||||
let bytes = text.as_bytes();
|
||||
|
||||
memchr(b'>', bytes)
|
||||
.filter(|i| bytes[i - 1] == b')' && bytes[4..i - 1].iter().all(|&c| c != b'\n'))
|
||||
.map(|i| (Timestamp::Diary(&text[4..i - 1]), i))
|
||||
.filter(|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)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn parse_range() {
|
||||
#[test]
|
||||
fn parse_range() {
|
||||
use super::*;
|
||||
|
||||
assert_eq!(
|
||||
|
@ -367,7 +415,7 @@ mod tests {
|
|||
Some((
|
||||
Timestamp::Inactive {
|
||||
start: Datetime {
|
||||
date: (2003, 9, 16),
|
||||
date: "2003-09-16",
|
||||
time: None,
|
||||
dayname: "Tue"
|
||||
},
|
||||
|
@ -382,13 +430,13 @@ mod tests {
|
|||
Some((
|
||||
Timestamp::InactiveRange {
|
||||
start: Datetime {
|
||||
date: (2003, 9, 16),
|
||||
time: Some((9, 39)),
|
||||
date: "2003-09-16",
|
||||
time: Some("09:39"),
|
||||
dayname: "Tue"
|
||||
},
|
||||
end: Datetime {
|
||||
date: (2003, 9, 16),
|
||||
time: Some((10, 39)),
|
||||
date: "2003-09-16",
|
||||
time: Some("10:39"),
|
||||
dayname: "Tue"
|
||||
},
|
||||
repeater: None,
|
||||
|
@ -402,13 +450,13 @@ mod tests {
|
|||
Some((
|
||||
Timestamp::ActiveRange {
|
||||
start: Datetime {
|
||||
date: (2003, 9, 16),
|
||||
time: Some((9, 39)),
|
||||
date: "2003-09-16",
|
||||
time: Some("09:39"),
|
||||
dayname: "Tue"
|
||||
},
|
||||
end: Datetime {
|
||||
date: (2003, 9, 16),
|
||||
time: Some((10, 39)),
|
||||
date: "2003-09-16",
|
||||
time: Some("10:39"),
|
||||
dayname: "Tue"
|
||||
},
|
||||
repeater: None,
|
||||
|
@ -417,17 +465,17 @@ mod tests {
|
|||
"<2003-09-16 Tue 09:39-10:39>".len()
|
||||
))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_datetime() {
|
||||
#[test]
|
||||
fn parse_datetime() {
|
||||
use super::*;
|
||||
|
||||
assert_eq!(
|
||||
Timestamp::parse_datetime("2003-09-16 Tue"),
|
||||
Some((
|
||||
Datetime {
|
||||
date: (2003, 9, 16),
|
||||
date: "2003-09-16",
|
||||
time: None,
|
||||
dayname: "Tue"
|
||||
},
|
||||
|
@ -438,8 +486,8 @@ mod tests {
|
|||
Timestamp::parse_datetime("2003-09-16 Tue 9:39"),
|
||||
Some((
|
||||
Datetime {
|
||||
date: (2003, 9, 16),
|
||||
time: Some((9, 39)),
|
||||
date: "2003-09-16",
|
||||
time: Some("9:39"),
|
||||
dayname: "Tue"
|
||||
},
|
||||
None
|
||||
|
@ -449,8 +497,8 @@ mod tests {
|
|||
Timestamp::parse_datetime("2003-09-16 Tue 09:39"),
|
||||
Some((
|
||||
Datetime {
|
||||
date: (2003, 9, 16),
|
||||
time: Some((9, 39)),
|
||||
date: "2003-09-16",
|
||||
time: Some("09:39"),
|
||||
dayname: "Tue"
|
||||
},
|
||||
None
|
||||
|
@ -460,13 +508,13 @@ mod tests {
|
|||
Timestamp::parse_datetime("2003-09-16 Tue 9:39-10:39"),
|
||||
Some((
|
||||
Datetime {
|
||||
date: (2003, 9, 16),
|
||||
time: Some((9, 39)),
|
||||
date: "2003-09-16",
|
||||
time: Some("9:39"),
|
||||
dayname: "Tue"
|
||||
},
|
||||
Some(Datetime {
|
||||
date: (2003, 9, 16),
|
||||
time: Some((10, 39)),
|
||||
date: "2003-09-16",
|
||||
time: Some("10:39"),
|
||||
dayname: "Tue"
|
||||
}),
|
||||
))
|
||||
|
@ -476,5 +524,4 @@ mod tests {
|
|||
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,
|
||||
cont: &'a str,
|
||||
},
|
||||
Keyword {
|
||||
key: Key<'a>,
|
||||
value: &'a str,
|
||||
},
|
||||
Keyword(Keyword<'a>),
|
||||
Rule,
|
||||
|
||||
Timestamp(Timestamp<'a>),
|
||||
Cookie(Cookie<'a>),
|
||||
FnRef {
|
||||
label: Option<&'a str>,
|
||||
def: Option<&'a str>,
|
||||
},
|
||||
InlineCall {
|
||||
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>,
|
||||
},
|
||||
FnRef(FnRef<'a>),
|
||||
InlineCall(InlineCall<'a>),
|
||||
InlineSrc(InlineSrc<'a>),
|
||||
Link(Link<'a>),
|
||||
Macros(Macros<'a>),
|
||||
RadioTarget {
|
||||
target: &'a str,
|
||||
},
|
||||
Snippet {
|
||||
name: &'a str,
|
||||
value: &'a str,
|
||||
},
|
||||
Snippet(Snippet<'a>),
|
||||
Target {
|
||||
target: &'a str,
|
||||
},
|
||||
|
@ -430,12 +406,17 @@ impl<'a> Parser<'a> {
|
|||
})
|
||||
})
|
||||
.or_else(|| {
|
||||
keyword::parse(tail).map(|(key, value, off)| {
|
||||
if let Key::Call = key {
|
||||
(Event::Call { value }, off + line_begin, 0, 0)
|
||||
Keyword::parse(tail).map(|(key, option, value, off)| {
|
||||
(
|
||||
if key.eq_ignore_ascii_case("CALL") {
|
||||
Event::Call { value }
|
||||
} else {
|
||||
(Event::Keyword { key, value }, off + line_begin, 0, 0)
|
||||
}
|
||||
Event::Keyword(Keyword::new(key, option, value))
|
||||
},
|
||||
off + line_begin,
|
||||
0,
|
||||
0,
|
||||
)
|
||||
})
|
||||
})
|
||||
} else {
|
||||
|
@ -494,10 +475,12 @@ impl<'a> Parser<'a> {
|
|||
|
||||
let bytes = text.as_bytes();
|
||||
match bytes[0] {
|
||||
b'@' if bytes[1] == b'@' => snippet::parse(text)
|
||||
.map(|(name, value, off)| (Event::Snippet { name, value }, 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'@' => {
|
||||
Snippet::parse(text).map(|(snippet, off)| (Event::Snippet(snippet), 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'<' => {
|
||||
if bytes[2] == b'<' {
|
||||
radio_target::parse(text)
|
||||
|
@ -514,13 +497,11 @@ impl<'a> Parser<'a> {
|
|||
}),
|
||||
b'[' => {
|
||||
if text[1..].starts_with("fn:") {
|
||||
fn_ref::parse(text)
|
||||
.map(|(label, def, off)| (Event::FnRef { label, def }, off, 0, 0))
|
||||
FnRef::parse(text).map(|(fn_ref, off)| (Event::FnRef(fn_ref), off, 0, 0))
|
||||
} else if bytes[1] == b'[' {
|
||||
link::parse(text)
|
||||
.map(|(path, desc, off)| (Event::Link { path, desc }, off, 0, 0))
|
||||
Link::parse(text).map(|(link, off)| (Event::Link(link), off, 0, 0))
|
||||
} else {
|
||||
cookie::parse(text)
|
||||
Cookie::parse(text)
|
||||
.map(|(cookie, off)| (Event::Cookie(cookie), off, 0, 0))
|
||||
.or_else(|| {
|
||||
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))
|
||||
}
|
||||
b's' if text.starts_with("src_") => {
|
||||
inline_src::parse(text).map(|(lang, option, body, off)| {
|
||||
(Event::InlineSrc { lang, option, body }, off, 0, 0)
|
||||
})
|
||||
InlineSrc::parse(text).map(|(src, off)| (Event::InlineSrc(src), off, 0, 0))
|
||||
}
|
||||
b'c' if text.starts_with("call_") => {
|
||||
inline_call::parse(text).map(|(name, args, inside_header, end_header, off)| {
|
||||
(
|
||||
Event::InlineCall {
|
||||
name,
|
||||
args,
|
||||
inside_header,
|
||||
end_header,
|
||||
},
|
||||
off,
|
||||
0,
|
||||
0,
|
||||
)
|
||||
})
|
||||
InlineCall::parse(text).map(|(call, off)| (Event::InlineCall(call), off, 0, 0))
|
||||
}
|
||||
_ => 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 memchr::memchr;
|
||||
|
||||
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>;
|
||||
|
||||
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)
|
||||
}
|
||||
} 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))
|
||||
}
|
||||
} else if line.starts_with("[fn:") {
|
||||
|
|
Loading…
Reference in a new issue