From 56e289fb488c0023f621cbb50b617201d414f7e2 Mon Sep 17 00:00:00 2001 From: PoiScript Date: Wed, 24 Apr 2019 17:42:21 +0800 Subject: [PATCH] refactor(parser): wrap some objects and elements with struct --- src/elements/clock.rs | 169 ++++++------ src/elements/dyn_block.rs | 14 +- src/elements/fn_def.rs | 4 +- src/elements/keyword.rs | 206 ++++++++------- src/elements/mod.rs | 2 +- src/elements/planning.rs | 50 ++-- src/elements/rule.rs | 14 +- src/export/html.rs | 37 ++- src/export/mod.rs | 19 +- src/objects/cookie.rs | 82 +++--- src/objects/fn_ref.rs | 136 ++++++---- src/objects/inline_call.rs | 174 +++++++------ src/objects/inline_src.rs | 109 ++++---- src/objects/link.rs | 92 ++++--- src/objects/macros.rs | 116 +++++---- src/objects/mod.rs | 6 + src/objects/radio_target.rs | 2 +- src/objects/snippet.rs | 100 +++++--- src/objects/target.rs | 2 +- src/objects/timestamp.rs | 499 ++++++++++++++++++++---------------- src/parser.rs | 91 +++---- src/tools.rs | 6 +- 22 files changed, 1055 insertions(+), 875 deletions(-) diff --git a/src/elements/clock.rs b/src/elements/clock.rs index ca55234..73a9f10 100644 --- a/src/elements/clock.rs +++ b/src/elements/clock.rs @@ -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, - delay: Option, + repeater: Option<&'a str>, + delay: Option<&'a str>, duration: &'a str, }, /// running Clock Running { start: Datetime<'a>, - repeater: Option, - delay: Option, + repeater: Option<&'a str>, + delay: Option<&'a str>, }, } @@ -37,60 +37,57 @@ impl<'a> Clock<'a> { return None; } - match Timestamp::parse_inactive(tail).map(|(t, off)| (t, tail[off..].trim_start())) { - Some(( - Timestamp::InactiveRange { - start, - end, - repeater, - delay, - }, - tail, - )) => { - if tail.starts_with("=>") { - let duration = &tail[3..].trim(); - let colon = memchr(b':', duration.as_bytes())?; - if duration.as_bytes()[0..colon].iter().all(u8::is_ascii_digit) - && colon == duration.len() - 3 - && duration.as_bytes()[colon + 1].is_ascii_digit() - && duration.as_bytes()[colon + 2].is_ascii_digit() - { - return Some(( - Clock::Closed { - start, - end, - repeater, - delay, - duration, - }, - off, - )); - } + let (timestamp, tail) = + Timestamp::parse_inactive(tail).map(|(t, off)| (t, tail[off..].trim_start()))?; + + match timestamp { + Timestamp::InactiveRange { + start, + end, + repeater, + delay, + } if tail.starts_with("=>") => { + let duration = &tail[3..].trim(); + let colon = memchr(b':', duration.as_bytes())?; + if duration.as_bytes()[0..colon].iter().all(u8::is_ascii_digit) + && colon == duration.len() - 3 + && duration.as_bytes()[colon + 1].is_ascii_digit() + && duration.as_bytes()[colon + 2].is_ascii_digit() + { + Some(( + Clock::Closed { + start, + end, + repeater, + delay, + duration, + }, + off, + )) + } else { + None } } - Some(( - Timestamp::Inactive { - start, - repeater, - delay, - }, - tail, - )) => { + Timestamp::Inactive { + start, + repeater, + delay, + } => { if tail.as_bytes().iter().all(u8::is_ascii_whitespace) { - return Some(( + Some(( Clock::Running { start, repeater, delay, }, off, - )); + )) + } else { + None } } - _ => (), + _ => None, } - - None } /// returns `true` if the clock is running @@ -146,48 +143,42 @@ impl<'a> Clock<'a> { } } -#[cfg(test)] -mod tests { - use super::Clock; - use crate::objects::timestamp::Datetime; - - #[test] - fn parse() { - assert_eq!( - Clock::parse("CLOCK: [2003-09-16 Tue 09:39]"), - Some(( - Clock::Running { - start: Datetime { - date: (2003, 9, 16), - time: Some((9, 39)), - dayname: "Tue" - }, - repeater: None, - delay: None, +#[test] +fn parse() { + assert_eq!( + Clock::parse("CLOCK: [2003-09-16 Tue 09:39]"), + Some(( + Clock::Running { + start: Datetime { + date: "2003-09-16", + time: Some("09:39"), + dayname: "Tue" }, - "CLOCK: [2003-09-16 Tue 09:39]".len() - )) - ); - assert_eq!( - Clock::parse("CLOCK: [2003-09-16 Tue 09:39]--[2003-09-16 Tue 10:39] => 1:00"), - Some(( - Clock::Closed { - start: Datetime { - date: (2003, 9, 16), - time: Some((9, 39)), - dayname: "Tue" - }, - end: Datetime { - date: (2003, 9, 16), - time: Some((10, 39)), - dayname: "Tue" - }, - repeater: None, - delay: None, - duration: "1:00", + repeater: None, + delay: None, + }, + "CLOCK: [2003-09-16 Tue 09:39]".len() + )) + ); + assert_eq!( + Clock::parse("CLOCK: [2003-09-16 Tue 09:39]--[2003-09-16 Tue 10:39] => 1:00"), + Some(( + Clock::Closed { + start: Datetime { + date: "2003-09-16", + time: Some("09:39"), + dayname: "Tue" }, - "CLOCK: [2003-09-16 Tue 09:39]--[2003-09-16 Tue 10:39] => 1:00".len() - )) - ); - } + end: Datetime { + date: "2003-09-16", + time: Some("10:39"), + dayname: "Tue" + }, + repeater: None, + delay: None, + duration: "1:00", + }, + "CLOCK: [2003-09-16 Tue 09:39]--[2003-09-16 Tue 10:39] => 1:00".len() + )) + ); } diff --git a/src/elements/dyn_block.rs b/src/elements/dyn_block.rs index cb165f0..839b35f 100644 --- a/src/elements/dyn_block.rs +++ b/src/elements/dyn_block.rs @@ -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()))?; diff --git a/src/elements/fn_def.rs b/src/elements/fn_def.rs index fa059d8..3c16183 100644 --- a/src/elements/fn_def.rs +++ b/src/elements/fn_def.rs @@ -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)) diff --git a/src/elements/keyword.rs b/src/elements/keyword.rs index 472456e..953032b 100644 --- a/src/elements/keyword.rs +++ b/src/elements/keyword.rs @@ -17,112 +17,124 @@ pub enum Key<'a> { Date, Title, Custom(&'a str), - - // Babel Call - Call, } -pub fn parse(text: &str) -> Option<(Key<'_>, &str, usize)> { - debug_assert!(text.starts_with("#+")); +#[cfg_attr(test, derive(PartialEq))] +#[derive(Debug)] +pub struct Keyword<'a> { + pub key: Key<'a>, + pub value: &'a str, +} - let bytes = text.as_bytes(); - - let (key, off) = memchr2(b':', b'[', bytes) - .filter(|&i| { - bytes[2..i] - .iter() - .all(|&c| c.is_ascii_alphabetic() || c == b'_') - }) - .map(|i| (&text[2..i], i + 1))?; - - let (option, off) = if bytes[off - 1] == b'[' { - memchr(b']', bytes) - .filter(|&i| { - bytes[off..i].iter().all(|&c| c != b'\n') && i < text.len() && bytes[i + 1] == b':' - }) - .map(|i| (Some(&text[off..i]), i + 2 /* ]: */))? - } else { - (None, off) - }; - - let (value, off) = memchr(b'\n', bytes) - .map(|i| (&text[off..i], i + 1)) - .unwrap_or_else(|| (&text[off..], text.len())); - - Some(( - match &*key.to_uppercase() { - "AUTHOR" => Key::Author, - "CALL" => Key::Call, - "DATE" => Key::Date, - "HEADER" => Key::Header, - "NAME" => Key::Name, - "PLOT" => Key::Plot, - "TITLE" => Key::Title, - "RESULTS" => Key::Results { option }, - "CAPTION" => Key::Caption { option }, - k if k.starts_with("ATTR_") => Key::Attr { - backend: &key["ATTR_".len()..], +impl<'a> Keyword<'a> { + #[inline] + pub(crate) fn new(key: &'a str, option: Option<&'a str>, value: &'a str) -> Keyword<'a> { + Keyword { + key: match &*key.to_uppercase() { + "AUTHOR" => Key::Author, + "DATE" => Key::Date, + "HEADER" => Key::Header, + "NAME" => Key::Name, + "PLOT" => Key::Plot, + "TITLE" => Key::Title, + "RESULTS" => Key::Results { option }, + "CAPTION" => Key::Caption { option }, + k => { + if k.starts_with("ATTR_") { + Key::Attr { + backend: &key["ATTR_".len()..], + } + } else { + Key::Custom(key) + } + } }, - _ => Key::Custom(key), - }, - value.trim(), - off, - )) -} + value, + } + } -#[cfg(test)] -mod tests { - #[test] - fn parse() { - use super::*; + #[inline] + pub(crate) fn parse(text: &str) -> Option<(&str, Option<&str>, &str, usize)> { + debug_assert!(text.starts_with("#+")); - assert_eq!( - parse("#+KEY:"), - Some((Key::Custom("KEY"), "", "#+KEY:".len())) - ); - assert_eq!( - parse("#+KEY: VALUE"), - Some((Key::Custom("KEY"), "VALUE", "#+KEY: VALUE".len())) - ); - assert_eq!( - parse("#+K_E_Y: VALUE"), - Some((Key::Custom("K_E_Y"), "VALUE", "#+K_E_Y: VALUE".len())) - ); - assert_eq!( - parse("#+KEY:VALUE\n"), - Some((Key::Custom("KEY"), "VALUE", "#+KEY:VALUE\n".len())) - ); - assert_eq!(parse("#+KE Y: VALUE"), None); - assert_eq!(parse("#+ KEY: VALUE"), None); + let bytes = text.as_bytes(); - assert_eq!( - parse("#+RESULTS:"), - Some((Key::Results { option: None }, "", "#+RESULTS:".len())) - ); + let (key, off) = memchr2(b':', b'[', bytes) + .filter(|&i| { + bytes[2..i] + .iter() + .all(|&c| c.is_ascii_alphabetic() || c == b'_') + }) + .map(|i| (&text[2..i], i + 1))?; - assert_eq!( - parse("#+ATTR_LATEX: :width 5cm"), - Some(( - Key::Attr { backend: "LATEX" }, - ":width 5cm", - "#+ATTR_LATEX: :width 5cm".len() - )) - ); + let (option, off) = if bytes[off - 1] == b'[' { + memchr(b']', bytes) + .filter(|&i| { + bytes[off..i].iter().all(|&c| c != b'\n') + && i < text.len() + && bytes[i + 1] == b':' + }) + .map(|i| (Some(&text[off..i]), i + "]:".len()))? + } else { + (None, off) + }; - assert_eq!( - parse("#+CALL: double(n=4)"), - Some((Key::Call, "double(n=4)", "#+CALL: double(n=4)".len())) - ); + let (value, off) = memchr(b'\n', bytes) + .map(|i| (&text[off..i], i + 1)) + .unwrap_or_else(|| (&text[off..], text.len())); - assert_eq!( - parse("#+CAPTION[Short caption]: Longer caption."), - Some(( - Key::Caption { - option: Some("Short caption") - }, - "Longer caption.", - "#+CAPTION[Short caption]: Longer caption.".len() - )) - ); + Some((key, option, value.trim(), off)) } } + +#[test] +fn parse() { + assert_eq!( + Keyword::parse("#+KEY:"), + Some(("KEY", None, "", "#+KEY:".len())) + ); + assert_eq!( + Keyword::parse("#+KEY: VALUE"), + Some(("KEY", None, "VALUE", "#+KEY: VALUE".len())) + ); + assert_eq!( + Keyword::parse("#+K_E_Y: VALUE"), + Some(("K_E_Y", None, "VALUE", "#+K_E_Y: VALUE".len())) + ); + assert_eq!( + Keyword::parse("#+KEY:VALUE\n"), + Some(("KEY", None, "VALUE", "#+KEY:VALUE\n".len())) + ); + assert_eq!(Keyword::parse("#+KE Y: VALUE"), None); + assert_eq!(Keyword::parse("#+ KEY: VALUE"), None); + + assert_eq!( + Keyword::parse("#+RESULTS:"), + Some(("RESULTS", None, "", "#+RESULTS:".len())) + ); + + assert_eq!( + Keyword::parse("#+ATTR_LATEX: :width 5cm"), + Some(( + "ATTR_LATEX", + None, + ":width 5cm", + "#+ATTR_LATEX: :width 5cm".len() + )) + ); + + assert_eq!( + Keyword::parse("#+CALL: double(n=4)"), + Some(("CALL", None, "double(n=4)", "#+CALL: double(n=4)".len())) + ); + + assert_eq!( + Keyword::parse("#+CAPTION[Short caption]: Longer caption."), + Some(( + "CAPTION", + Some("Short caption"), + "Longer caption.", + "#+CAPTION[Short caption]: Longer caption.".len() + )) + ); +} diff --git a/src/elements/mod.rs b/src/elements/mod.rs index 61261eb..d91a158 100644 --- a/src/elements/mod.rs +++ b/src/elements/mod.rs @@ -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; diff --git a/src/elements/planning.rs b/src/elements/planning.rs index 5b42485..81e7d96 100644 --- a/src/elements/planning.rs +++ b/src/elements/planning.rs @@ -1,4 +1,4 @@ -use crate::objects::timestamp::Timestamp; +use crate::objects::Timestamp; use memchr::memchr; /// palnning elements @@ -58,31 +58,27 @@ impl<'a> Planning<'a> { } } -#[cfg(test)] -mod tests { - #[test] - fn prase() { - use super::Planning; - use crate::objects::timestamp::{Datetime, Timestamp}; +#[test] +fn prase() { + use crate::objects::Datetime; - assert_eq!( - Planning::parse("SCHEDULED: <2019-04-08 Mon>\n"), - Some(( - Planning { - scheduled: Some(Timestamp::Active { - start: Datetime { - date: (2019, 4, 8), - time: None, - dayname: "Mon" - }, - repeater: None, - delay: None - }), - closed: None, - deadline: None, - }, - "SCHEDULED: <2019-04-08 Mon>\n".len() - )) - ) - } + assert_eq!( + Planning::parse("SCHEDULED: <2019-04-08 Mon>\n"), + Some(( + Planning { + scheduled: Some(Timestamp::Active { + start: Datetime { + date: "2019-04-08", + time: None, + dayname: "Mon" + }, + repeater: None, + delay: None + }), + closed: None, + deadline: None, + }, + "SCHEDULED: <2019-04-08 Mon>\n".len() + )) + ) } diff --git a/src/elements/rule.rs b/src/elements/rule.rs index 13d93f2..89e877d 100644 --- a/src/elements/rule.rs +++ b/src/elements/rule.rs @@ -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 } diff --git a/src/export/html.rs b/src/export/html.rs index 54a6c5f..6b7d1a2 100644 --- a/src/export/html.rs +++ b/src/export/html.rs @@ -32,7 +32,7 @@ pub trait HtmlHandler> { fn headline_beg(&mut self, w: &mut W, hdl: Headline) -> Result<(), E> { let level = if hdl.level <= 6 { hdl.level } else { 6 }; write!(w, "", level)?; - self.escape(w, hdl.title)?; + self.text(w, hdl.title)?; write!(w, "", level)?; Ok(()) } @@ -155,7 +155,7 @@ pub trait HtmlHandler> { 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> { 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, "")?; - self.escape(w, body)?; + self.text(w, src.body)?; write!(w, "")?; 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#""#)?; - self.escape(w, desc.unwrap_or(path))?; + self.text(w, link.desc.unwrap_or(link.path))?; write!(w, "")?; 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> { } fn verbatim(&mut self, w: &mut W, cont: &str) -> Result<(), E> { write!(w, "")?; - self.escape(w, cont)?; + self.text(w, cont)?; write!(w, "")?; Ok(()) } fn code(&mut self, w: &mut W, cont: &str) -> Result<(), E> { write!(w, "")?; - self.escape(w, cont)?; + self.text(w, cont)?; write!(w, "")?; Ok(()) } diff --git a/src/export/mod.rs b/src/export/mod.rs index 4bd9809..44266bb 100644 --- a/src/export/mod.rs +++ b/src/export/mod.rs @@ -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)?, diff --git a/src/objects/cookie.rs b/src/objects/cookie.rs index fd5510c..4c2fafb 100644 --- a/src/objects/cookie.rs +++ b/src/objects/cookie.rs @@ -7,45 +7,59 @@ pub enum Cookie<'a> { Slash(&'a str, &'a str), } -#[inline] -pub fn parse(src: &str) -> Option<(Cookie<'_>, usize)> { - debug_assert!(src.starts_with('[')); +impl<'a> Cookie<'a> { + #[inline] + pub(crate) fn parse(src: &str) -> Option<(Cookie<'_>, usize)> { + debug_assert!(src.starts_with('[')); - let bytes = src.as_bytes(); - let num1 = - memchr2(b'%', b'/', bytes).filter(|&i| bytes[1..i].iter().all(u8::is_ascii_digit))?; + let bytes = src.as_bytes(); + let num1 = + memchr2(b'%', b'/', bytes).filter(|&i| bytes[1..i].iter().all(u8::is_ascii_digit))?; - 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))?; + 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))?; - 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)] -mod tests { - #[test] - fn parse() { - use super::parse; - use super::Cookie::*; +#[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!(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); - } + 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); } diff --git a/src/objects/fn_ref.rs b/src/objects/fn_ref.rs index 1c76e26..0ef302f 100644 --- a/src/objects/fn_ref.rs +++ b/src/objects/fn_ref.rs @@ -1,54 +1,96 @@ 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)> { - debug_assert!(text.starts_with("[fn:")); - - 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_attr(test, derive(PartialEq))] +#[derive(Debug)] +pub struct FnRef<'a> { + pub label: Option<&'a str>, + pub definition: Option<&'a str>, } -#[cfg(test)] -mod tests { - #[test] - fn parse() { - use super::parse; +impl<'a> FnRef<'a> { + #[inline] + pub fn parse(text: &str) -> Option<(FnRef<'_>, usize)> { + debug_assert!(text.starts_with("[fn:")); - 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); + let bytes = text.as_bytes(); + let (label, off) = memchr2(b']', b':', &bytes["[fn:".len()..]) + .filter(|&i| { + 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["[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); +} diff --git a/src/objects/inline_call.rs b/src/objects/inline_call.rs index 7b386c3..2d1d85d 100644 --- a/src/objects/inline_call.rs +++ b/src/objects/inline_call.rs @@ -1,80 +1,108 @@ 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)> { - debug_assert!(text.starts_with("call_")); - - let bytes = text.as_bytes(); - - 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_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>, } -#[cfg(test)] -mod tests { - #[test] - fn parse() { - use super::parse; +impl<'a> InlineCall<'a> { + #[inline] + pub(crate) fn parse(text: &str) -> Option<(InlineCall<'_>, usize)> { + debug_assert!(text.starts_with("call_")); - assert_eq!( - parse("call_square(4)"), - Some(("square", "4", None, None, "call_square(4)".len())) - ); - assert_eq!( - parse("call_square[:results output](4)"), - Some(( - "square", - "4", - Some(":results output"), - None, - "call_square[:results output](4)".len() - )) - ); - assert_eq!( - parse("call_square(4)[:results html]"), - Some(( - "square", - "4", - None, - Some(":results html"), - "call_square(4)[:results html]".len() - )) - ); - assert_eq!( - parse("call_square[:results output](4)[:results html]"), - Some(( - "square", - "4", - Some(":results output"), - Some(":results html"), - "call_square[:results output](4)[:results html]".len() - )) - ); + let bytes = text.as_bytes(); + + let (name, off) = memchr2(b'[', b'(', bytes) + .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') + }) + .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(( + InlineCall { + 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() + )) + ); +} diff --git a/src/objects/inline_src.rs b/src/objects/inline_src.rs index 3f20bad..99cde82 100644 --- a/src/objects/inline_src.rs +++ b/src/objects/inline_src.rs @@ -1,52 +1,71 @@ use memchr::{memchr, memchr2}; -/// returns (language, option, body, offset) -#[inline] -pub fn parse(text: &str) -> Option<(&str, Option<&str>, &str, usize)> { - debug_assert!(text.starts_with("src_")); - - let (lang, off) = memchr2(b'[', b'{', text.as_bytes()) - .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_attr(test, derive(PartialEq))] +#[derive(Debug)] +pub struct InlineSrc<'a> { + pub lang: &'a str, + pub option: Option<&'a str>, + pub body: &'a str, } -#[cfg(test)] -mod tests { - #[test] - fn parse() { - use super::parse; +impl<'a> InlineSrc<'a> { + #[inline] + pub(crate) fn parse(text: &str) -> Option<(InlineSrc<'_>, usize)> { + debug_assert!(text.starts_with("src_")); - assert_eq!( - parse("src_C{int a = 0;}"), - Some(("C", None, "int a = 0;", "src_C{int a = 0;}".len())) - ); - assert_eq!( - parse("src_xml[:exports code]{text}"), - Some(( - "xml", - Some(":exports code"), - "text", - "src_xml[:exports code]{text}".len() - )) - ); - assert_eq!(parse("src_xml[:exports code]{text"), None); - assert_eq!(parse("src_[:exports code]{text}"), None); - // assert_eq!(parse("src_xml[:exports code]"), None); + let (lang, off) = memchr2(b'[', b'{', text.as_bytes()) + .map(|i| (&text["src_".len()..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((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]{text}"), + Some(( + InlineSrc { + lang: "xml", + option: Some(":exports code"), + body: "text", + }, + "src_xml[:exports code]{text}".len() + )) + ); + assert_eq!( + InlineSrc::parse("src_xml[:exports code]{text"), + None + ); + assert_eq!( + InlineSrc::parse("src_[:exports code]{text}"), + None + ); + // assert_eq!(parse("src_xml[:exports code]"), None); +} diff --git a/src/objects/link.rs b/src/objects/link.rs index 1df7f13..06045e6 100644 --- a/src/objects/link.rs +++ b/src/objects/link.rs @@ -1,43 +1,67 @@ use jetscii::Substring; use memchr::memchr; -/// returns (link path, link description, offset) -#[inline] -pub fn parse(text: &str) -> Option<(&str, Option<&str>, usize)> { - debug_assert!(text.starts_with("[[")); +#[cfg_attr(test, derive(PartialEq))] +#[derive(Debug)] +pub struct Link<'a> { + pub path: &'a str, + pub desc: Option<&'a str>, +} - let (path, off) = memchr(b']', text.as_bytes()) - .map(|i| (&text[2..i], i)) - .filter(|(path, _)| { - path.as_bytes() - .iter() - .all(|&c| c != b'<' && c != b'>' && c != b'\n') - })?; +impl<'a> Link<'a> { + #[inline] + pub(crate) fn parse(text: &str) -> Option<(Link<'_>, usize)> { + debug_assert!(text.starts_with("[[")); - if *text.as_bytes().get(off + 1)? == b']' { - Some((path, 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)) - .filter(|(desc, _)| desc.as_bytes().iter().all(|&c| c != b'[' && c != b']'))?; - Some((path, Some(desc), off)) - } else { - None + let (path, off) = memchr(b']', text.as_bytes()) + .map(|i| (&text["[[".len()..i], i)) + .filter(|(path, _)| { + path.as_bytes() + .iter() + .all(|&c| c != b'<' && c != b'>' && c != b'\n') + })?; + + if *text.as_bytes().get(off + 1)? == b']' { + 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)] -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); } diff --git a/src/objects/macros.rs b/src/objects/macros.rs index 5ae4e19..4f779d6 100644 --- a/src/objects/macros.rs +++ b/src/objects/macros.rs @@ -1,60 +1,80 @@ use jetscii::Substring; use memchr::memchr2; -/// returns (macros name, macros arguments, offset) -#[inline] -pub fn parse(text: &str) -> Option<(&str, Option<&str>, usize)> { - debug_assert!(text.starts_with("{{{")); +#[cfg_attr(test, derive(PartialEq))] +#[derive(Debug)] +pub struct Macros<'a> { + pub name: &'a str, + pub arguments: Option<&'a str>, +} - let bytes = text.as_bytes(); - if text.len() <= 3 || !bytes[3].is_ascii_alphabetic() { - return None; - } +impl<'a> Macros<'a> { + #[inline] + pub fn parse(text: &str) -> Option<(Macros<'_>, usize)> { + debug_assert!(text.starts_with("{{{")); - 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))?; - - let (args, off) = if bytes[off] == b'}' { - if text.len() <= off + 2 || bytes[off + 1] != b'}' || bytes[off + 2] != b'}' { + let bytes = text.as_bytes(); + if text.len() <= 3 || !bytes[3].is_ascii_alphabetic() { 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)] -mod tests { - #[test] - fn parse() { - use super::parse; + let (arguments, off) = if bytes[off] == b'}' { + if text.len() <= off + 2 || bytes[off + 1] != b'}' || bytes[off + 2] != b'}' { + return None; + } + (None, off + "}}}".len()) + } else { + Substring::new(")}}}") + .find(&text[off..]) + .map(|i| (Some(&text[off + 1..off + i]), off + i + ")}}}".len()))? + }; - 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); +} diff --git a/src/objects/mod.rs b/src/objects/mod.rs index 81be4a8..8cae4ab 100644 --- a/src/objects/mod.rs +++ b/src/objects/mod.rs @@ -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::*; diff --git a/src/objects/radio_target.rs b/src/objects/radio_target.rs index f1f075e..43740ec 100644 --- a/src/objects/radio_target.rs +++ b/src/objects/radio_target.rs @@ -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)) } diff --git a/src/objects/snippet.rs b/src/objects/snippet.rs index a19ad8a..5727d26 100644 --- a/src/objects/snippet.rs +++ b/src/objects/snippet.rs @@ -1,48 +1,68 @@ use jetscii::Substring; use memchr::memchr; -/// returns (snippet name, snippet value, offset) -#[inline] -pub fn parse(text: &str) -> Option<(&str, &str, usize)> { - debug_assert!(text.starts_with("@@")); - - 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_attr(test, derive(PartialEq))] +#[derive(Debug)] +pub struct Snippet<'a> { + pub name: &'a str, + pub value: &'a str, } -#[cfg(test)] -mod tests { - #[test] - fn parse() { - use super::parse; +impl<'a> Snippet<'a> { + #[inline] + pub(crate) fn parse(text: &str) -> Option<(Snippet<'_>, usize)> { + debug_assert!(text.starts_with("@@")); - assert_eq!( - parse("@@html:@@"), - Some(("html", "", "@@html:@@".len())) - ); - assert_eq!( - parse("@@latex:any arbitrary LaTeX code@@"), - Some(( - "latex", - "any arbitrary LaTeX code", - "@@latex:any arbitrary LaTeX code@@".len() - )) - ); - assert_eq!(parse("@@html:@@"), Some(("html", "", "@@html:@@".len()))); - assert_eq!(parse("@@html:@"), None); - assert_eq!(parse("@@html@@"), None); - assert_eq!(parse("@@:@@"), None); + 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 + "@@".len()))?; + + Some((Snippet { name, value }, off)) } } + +#[test] +fn parse() { + assert_eq!( + Snippet::parse("@@html:@@"), + Some(( + Snippet { + name: "html", + value: "" + }, + "@@html:@@".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:@"), None); + assert_eq!(Snippet::parse("@@html@@"), None); + assert_eq!(Snippet::parse("@@:@@"), None); +} diff --git a/src/objects/target.rs b/src/objects/target.rs index d1d8286..2f37470 100644 --- a/src/objects/target.rs +++ b/src/objects/target.rs @@ -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)] diff --git a/src/objects/timestamp.rs b/src/objects/timestamp.rs index 6493f23..fc35291 100644 --- a/src/objects/timestamp.rs +++ b/src/objects/timestamp.rs @@ -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 { + 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 { + 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, - delay: Option, + repeater: Option<&'a str>, + delay: Option<&'a str>, }, Inactive { start: Datetime<'a>, - repeater: Option, - delay: Option, + repeater: Option<&'a str>, + delay: Option<&'a str>, }, ActiveRange { start: Datetime<'a>, end: Datetime<'a>, - repeater: Option, - delay: Option, + repeater: Option<&'a str>, + delay: Option<&'a str>, }, InactiveRange { start: Datetime<'a>, end: Datetime<'a>, - repeater: Option, - delay: Option, + 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 /* -- */ && text[off + 1..].starts_with("--<") + if end.is_none() + && off + "--".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,29 +241,16 @@ impl<'a> Timestamp<'a> { let mut words = text.split_ascii_whitespace(); - let date = words - .next() - .filter(|word| { - let word = word.as_bytes(); - // YYYY-MM-DD - word.len() == 10 - && word[0..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) - }) - .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 date = words.next().filter(|word| { + let word = word.as_bytes(); + // YYYY-MM-DD + word.len() == 10 + && word[0..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) + })?; let dayname = words.next().filter(|word| { word.as_bytes().iter().all(|&c| { @@ -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,137 +392,136 @@ 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() { - use super::*; +#[test] +fn parse_range() { + use super::*; - assert_eq!( - Timestamp::parse_inactive("[2003-09-16 Tue]"), - Some(( - Timestamp::Inactive { - start: Datetime { - date: (2003, 9, 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), + assert_eq!( + Timestamp::parse_inactive("[2003-09-16 Tue]"), + Some(( + Timestamp::Inactive { + start: 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, 9, 16), - time: Some((9, 39)), + 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-09-16", + time: Some("09:39"), dayname: "Tue" }, - None - )) - ); - assert_eq!( - Timestamp::parse_datetime("2003-09-16 Tue 09:39"), - Some(( - Datetime { - date: (2003, 9, 16), - time: Some((9, 39)), + end: Datetime { + date: "2003-09-16", + time: Some("10:39"), dayname: "Tue" }, - None - )) - ); - assert_eq!( - Timestamp::parse_datetime("2003-09-16 Tue 9:39-10:39"), - Some(( - Datetime { - date: (2003, 9, 16), - time: Some((9, 39)), + 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-09-16", + time: Some("09:39"), dayname: "Tue" }, - Some(Datetime { - date: (2003, 9, 16), - time: Some((10, 39)), + end: 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); - } + }, + 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-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); } diff --git a/src/parser.rs b/src/parser.rs index d4172ef..a1ec5de 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -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) - } else { - (Event::Keyword { key, 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(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, } diff --git a/src/tools.rs b/src/tools.rs index fcd8254..4657b8a 100644 --- a/src/tools.rs +++ b/src/tools.rs @@ -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>; -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:") {