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

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

View file

@ -1,4 +1,4 @@
use crate::objects::timestamp::{Datetime, Delay, Repeater, Timestamp}; use crate::objects::timestamp::{Datetime, Timestamp};
use memchr::memchr; 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()
))
);
} }

View file

@ -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()))?;

View file

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

View file

@ -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()
))
);
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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()
))
);
}

View file

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

View file

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

View file

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

View file

@ -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::*;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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