diff --git a/orgize/src/elements/block.rs b/orgize/src/elements/block.rs index a7a3eb8..fc0a011 100644 --- a/orgize/src/elements/block.rs +++ b/orgize/src/elements/block.rs @@ -1,6 +1,9 @@ use std::borrow::Cow; -use nom::{bytes::complete::tag_no_case, character::complete::alpha1, sequence::preceded, IResult}; +use nom::{ + bytes::complete::tag_no_case, character::complete::alpha1, error::ParseError, + sequence::preceded, IResult, +}; use crate::parsers::{line, take_lines_while}; @@ -160,12 +163,20 @@ impl SourceBlock<'_> { // TODO: fn retain_labels() -> bool { } } -pub(crate) fn parse_block_element(input: &str) -> IResult<&str, (&str, Option<&str>, &str)> { +#[inline] +pub fn parse_block_element(input: &str) -> Option<(&str, (&str, Option<&str>, &str))> { + parse_block_element_internal::<()>(input).ok() +} + +#[inline] +fn parse_block_element_internal<'a, E: ParseError<&'a str>>( + input: &'a str, +) -> IResult<&'a str, (&'a str, Option<&'a str>, &'a str), E> { let (input, name) = preceded(tag_no_case("#+BEGIN_"), alpha1)(input)?; let (input, args) = line(input)?; - let end_line = format!(r"#+END_{}", name); + let end_line = format!("#+END_{}", name); let (input, contents) = - take_lines_while(|line| !line.trim().eq_ignore_ascii_case(&end_line))(input)?; + take_lines_while(|line| !line.trim().eq_ignore_ascii_case(&end_line))(input); let (input, _) = line(input)?; Ok(( @@ -184,22 +195,24 @@ pub(crate) fn parse_block_element(input: &str) -> IResult<&str, (&str, Option<&s #[test] fn parse() { + use nom::error::VerboseError; + assert_eq!( - parse_block_element( + parse_block_element_internal::>( r#"#+BEGIN_SRC #+END_SRC"# ), Ok(("", ("SRC".into(), None, ""))) ); assert_eq!( - parse_block_element( + parse_block_element_internal::>( r#"#+begin_src #+end_src"# ), Ok(("", ("src".into(), None, ""))) ); assert_eq!( - parse_block_element( + parse_block_element_internal::>( r#"#+BEGIN_SRC javascript console.log('Hello World!'); #+END_SRC diff --git a/orgize/src/elements/clock.rs b/orgize/src/elements/clock.rs index cb0f865..054bfee 100644 --- a/orgize/src/elements/clock.rs +++ b/orgize/src/elements/clock.rs @@ -4,11 +4,13 @@ use nom::{ bytes::complete::tag, character::complete::{char, digit1, space0}, combinator::recognize, + error::ParseError, sequence::separated_pair, IResult, }; -use crate::elements::{Datetime, Timestamp}; +use crate::elements::timestamp::{parse_inactive, Datetime, Timestamp}; + use crate::parsers::eol; /// Clock Element @@ -42,54 +44,8 @@ pub enum Clock<'a> { } impl Clock<'_> { - pub(crate) fn parse(input: &str) -> IResult<&str, Clock<'_>> { - let (input, _) = tag("CLOCK:")(input)?; - let (input, _) = space0(input)?; - let (input, timestamp) = Timestamp::parse_inactive(input)?; - - match timestamp { - Timestamp::InactiveRange { - start, - end, - repeater, - delay, - } => { - let (input, _) = space0(input)?; - let (input, _) = tag("=>")(input)?; - let (input, _) = space0(input)?; - let (input, duration) = - recognize(separated_pair(digit1, char(':'), digit1))(input)?; - let (input, _) = eol(input)?; - Ok(( - input, - Clock::Closed { - start, - end, - repeater, - delay, - duration: duration.into(), - }, - )) - } - Timestamp::Inactive { - start, - repeater, - delay, - } => { - let (input, _) = eol(input)?; - Ok(( - input, - Clock::Running { - start, - repeater, - delay, - }, - )) - } - _ => unreachable!( - "`Timestamp::parse_inactive` only returns `Timestamp::InactiveRange` or `Timestamp::Inactive`." - ), - } + pub(crate) fn parse(input: &str) -> Option<(&str, Clock<'_>)> { + parse_clock::<()>(input).ok() } pub fn into_onwed(self) -> Clock<'static> { @@ -171,10 +127,61 @@ impl Clock<'_> { } } +fn parse_clock<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, Clock<'_>, E> { + let (input, _) = tag("CLOCK:")(input)?; + let (input, _) = space0(input)?; + let (input, timestamp) = parse_inactive(input)?; + + match timestamp { + Timestamp::InactiveRange { + start, + end, + repeater, + delay, + } => { + let (input, _) = space0(input)?; + let (input, _) = tag("=>")(input)?; + let (input, _) = space0(input)?; + let (input, duration) = recognize(separated_pair(digit1, char(':'), digit1))(input)?; + let (input, _) = eol(input)?; + Ok(( + input, + Clock::Closed { + start, + end, + repeater, + delay, + duration: duration.into(), + }, + )) + } + Timestamp::Inactive { + start, + repeater, + delay, + } => { + let (input, _) = eol(input)?; + Ok(( + input, + Clock::Running { + start, + repeater, + delay, + }, + )) + } + _ => unreachable!( + "`parse_inactive` only returns `Timestamp::InactiveRange` or `Timestamp::Inactive`." + ), + } +} + #[test] fn parse() { + use nom::error::VerboseError; + assert_eq!( - Clock::parse("CLOCK: [2003-09-16 Tue 09:39]"), + parse_clock::>("CLOCK: [2003-09-16 Tue 09:39]"), Ok(( "", Clock::Running { @@ -192,7 +199,9 @@ fn parse() { )) ); assert_eq!( - Clock::parse("CLOCK: [2003-09-16 Tue 09:39]--[2003-09-16 Tue 10:39] => 1:00"), + parse_clock::>( + "CLOCK: [2003-09-16 Tue 09:39]--[2003-09-16 Tue 10:39] => 1:00" + ), Ok(( "", Clock::Closed { diff --git a/orgize/src/elements/cookie.rs b/orgize/src/elements/cookie.rs index 90a7d7b..6cb1ce0 100644 --- a/orgize/src/elements/cookie.rs +++ b/orgize/src/elements/cookie.rs @@ -5,6 +5,7 @@ use nom::{ bytes::complete::tag, character::complete::digit0, combinator::recognize, + error::ParseError, sequence::{delimited, pair, separated_pair}, IResult, }; @@ -19,23 +20,8 @@ pub struct Cookie<'a> { } impl Cookie<'_> { - #[inline] - pub(crate) fn parse(input: &str) -> IResult<&str, Cookie<'_>> { - let (input, value) = recognize(delimited( - tag("["), - alt(( - separated_pair(digit0, tag("/"), digit0), - pair(digit0, tag("%")), - )), - tag("]"), - ))(input)?; - - Ok(( - input, - Cookie { - value: value.into(), - }, - )) + pub(crate) fn parse(input: &str) -> Option<(&str, Cookie<'_>)> { + parse_cookie::<()>(input).ok() } pub fn into_owned(self) -> Cookie<'static> { @@ -45,10 +31,31 @@ impl Cookie<'_> { } } +#[inline] +fn parse_cookie<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, Cookie<'a>, E> { + let (input, value) = recognize(delimited( + tag("["), + alt(( + separated_pair(digit0, tag("/"), digit0), + pair(digit0, tag("%")), + )), + tag("]"), + ))(input)?; + + Ok(( + input, + Cookie { + value: value.into(), + }, + )) +} + #[test] fn parse() { + use nom::error::VerboseError; + assert_eq!( - Cookie::parse("[1/10]"), + parse_cookie::>("[1/10]"), Ok(( "", Cookie { @@ -57,7 +64,7 @@ fn parse() { )) ); assert_eq!( - Cookie::parse("[1/1000]"), + parse_cookie::>("[1/1000]"), Ok(( "", Cookie { @@ -66,7 +73,7 @@ fn parse() { )) ); assert_eq!( - Cookie::parse("[10%]"), + parse_cookie::>("[10%]"), Ok(( "", Cookie { @@ -75,7 +82,7 @@ fn parse() { )) ); assert_eq!( - Cookie::parse("[%]"), + parse_cookie::>("[%]"), Ok(( "", Cookie { @@ -84,7 +91,7 @@ fn parse() { )) ); assert_eq!( - Cookie::parse("[/]"), + parse_cookie::>("[/]"), Ok(( "", Cookie { @@ -93,7 +100,7 @@ fn parse() { )) ); assert_eq!( - Cookie::parse("[100/]"), + parse_cookie::>("[100/]"), Ok(( "", Cookie { @@ -102,7 +109,7 @@ fn parse() { )) ); assert_eq!( - Cookie::parse("[/100]"), + parse_cookie::>("[/100]"), Ok(( "", Cookie { @@ -111,8 +118,8 @@ fn parse() { )) ); - assert!(Cookie::parse("[10% ]").is_err()); - assert!(Cookie::parse("[1//100]").is_err()); - assert!(Cookie::parse("[1\\100]").is_err()); - assert!(Cookie::parse("[10%%]").is_err()); + assert!(parse_cookie::>("[10% ]").is_err()); + assert!(parse_cookie::>("[1//100]").is_err()); + assert!(parse_cookie::>("[1\\100]").is_err()); + assert!(parse_cookie::>("[10%%]").is_err()); } diff --git a/orgize/src/elements/drawer.rs b/orgize/src/elements/drawer.rs index 5cb94a2..0a7b325 100644 --- a/orgize/src/elements/drawer.rs +++ b/orgize/src/elements/drawer.rs @@ -2,6 +2,7 @@ use std::borrow::Cow; use nom::{ bytes::complete::{tag, take_while1}, + error::ParseError, sequence::delimited, IResult, }; @@ -18,19 +19,8 @@ pub struct Drawer<'a> { } impl Drawer<'_> { - #[inline] - pub(crate) fn parse(input: &str) -> IResult<&str, (Drawer<'_>, &str)> { - let (input, name) = delimited( - tag(":"), - take_while1(|c: char| c.is_ascii_alphabetic() || c == '-' || c == '_'), - tag(":"), - )(input)?; - let (input, _) = eol(input)?; - let (input, contents) = - take_lines_while(|line| !line.trim().eq_ignore_ascii_case(":END:"))(input)?; - let (input, _) = line(input)?; - - Ok((input, (Drawer { name: name.into() }, contents))) + pub(crate) fn parse(input: &str) -> Option<(&str, (Drawer<'_>, &str))> { + parse_drawer::<()>(input).ok() } pub fn into_owned(self) -> Drawer<'static> { @@ -40,10 +30,29 @@ impl Drawer<'_> { } } +#[inline] +pub fn parse_drawer<'a, E: ParseError<&'a str>>( + input: &'a str, +) -> IResult<&'a str, (Drawer<'a>, &'a str), E> { + let (input, name) = delimited( + tag(":"), + take_while1(|c: char| c.is_ascii_alphabetic() || c == '-' || c == '_'), + tag(":"), + )(input)?; + let (input, _) = eol(input)?; + let (input, contents) = + take_lines_while(|line| !line.trim().eq_ignore_ascii_case(":END:"))(input); + let (input, _) = line(input)?; + + Ok((input, (Drawer { name: name.into() }, contents))) +} + #[test] fn parse() { + use nom::error::VerboseError; + assert_eq!( - Drawer::parse(":PROPERTIES:\n :CUSTOM_ID: id\n :END:"), + parse_drawer::>(":PROPERTIES:\n :CUSTOM_ID: id\n :END:"), Ok(( "", ( diff --git a/orgize/src/elements/dyn_block.rs b/orgize/src/elements/dyn_block.rs index 6a1050a..4afe1c3 100644 --- a/orgize/src/elements/dyn_block.rs +++ b/orgize/src/elements/dyn_block.rs @@ -3,6 +3,7 @@ use std::borrow::Cow; use nom::{ bytes::complete::tag_no_case, character::complete::{alpha1, space1}, + error::ParseError, IResult, }; @@ -21,30 +22,8 @@ pub struct DynBlock<'a> { } impl DynBlock<'_> { - #[inline] - pub(crate) fn parse(input: &str) -> IResult<&str, (DynBlock<'_>, &str)> { - let (input, _) = tag_no_case("#+BEGIN:")(input)?; - let (input, _) = space1(input)?; - let (input, name) = alpha1(input)?; - let (input, args) = line(input)?; - let (input, contents) = - take_lines_while(|line| !line.trim().eq_ignore_ascii_case("#+END:"))(input)?; - let (input, _) = line(input)?; - - Ok(( - input, - ( - DynBlock { - block_name: name.into(), - arguments: if args.trim().is_empty() { - None - } else { - Some(args.trim().into()) - }, - }, - contents, - ), - )) + pub(crate) fn parse(input: &str) -> Option<(&str, (DynBlock<'_>, &str))> { + parse_dyn_block::<()>(input).ok() } pub fn into_owned(self) -> DynBlock<'static> { @@ -55,11 +34,41 @@ impl DynBlock<'_> { } } +#[inline] +fn parse_dyn_block<'a, E: ParseError<&'a str>>( + input: &'a str, +) -> IResult<&'a str, (DynBlock<'a>, &'a str), E> { + let (input, _) = tag_no_case("#+BEGIN:")(input)?; + let (input, _) = space1(input)?; + let (input, name) = alpha1(input)?; + let (input, args) = line(input)?; + let (input, contents) = + take_lines_while(|line| !line.trim().eq_ignore_ascii_case("#+END:"))(input); + let (input, _) = line(input)?; + + Ok(( + input, + ( + DynBlock { + block_name: name.into(), + arguments: if args.trim().is_empty() { + None + } else { + Some(args.trim().into()) + }, + }, + contents, + ), + )) +} + #[test] fn parse() { + use nom::error::VerboseError; + // TODO: testing assert_eq!( - DynBlock::parse( + parse_dyn_block::>( r#"#+BEGIN: clocktable :scope file CONTENTS #+END: diff --git a/orgize/src/elements/fn_def.rs b/orgize/src/elements/fn_def.rs index b15fd2b..7a1934c 100644 --- a/orgize/src/elements/fn_def.rs +++ b/orgize/src/elements/fn_def.rs @@ -2,6 +2,7 @@ use std::borrow::Cow; use nom::{ bytes::complete::{tag, take_while1}, + error::ParseError, sequence::delimited, IResult, }; @@ -18,24 +19,8 @@ pub struct FnDef<'a> { } impl FnDef<'_> { - #[inline] - pub(crate) fn parse(input: &str) -> IResult<&str, (FnDef<'_>, &str)> { - let (input, label) = delimited( - tag("[fn:"), - take_while1(|c: char| c.is_ascii_alphanumeric() || c == '-' || c == '_'), - tag("]"), - )(input)?; - let (input, content) = line(input)?; - - Ok(( - input, - ( - FnDef { - label: label.into(), - }, - content, - ), - )) + pub(crate) fn parse(input: &str) -> Option<(&str, (FnDef<'_>, &str))> { + parse_fn_def::<()>(input).ok() } pub fn into_owned(self) -> FnDef<'static> { @@ -45,14 +30,38 @@ impl FnDef<'_> { } } +#[inline] +fn parse_fn_def<'a, E: ParseError<&'a str>>( + input: &'a str, +) -> IResult<&'a str, (FnDef<'a>, &'a str), E> { + let (input, label) = delimited( + tag("[fn:"), + take_while1(|c: char| c.is_ascii_alphanumeric() || c == '-' || c == '_'), + tag("]"), + )(input)?; + let (input, content) = line(input)?; + + Ok(( + input, + ( + FnDef { + label: label.into(), + }, + content, + ), + )) +} + #[test] fn parse() { + use nom::error::VerboseError; + assert_eq!( - FnDef::parse("[fn:1] https://orgmode.org"), + parse_fn_def::>("[fn:1] https://orgmode.org"), Ok(("", (FnDef { label: "1".into() }, " https://orgmode.org"))) ); assert_eq!( - FnDef::parse("[fn:word_1] https://orgmode.org"), + parse_fn_def::>("[fn:word_1] https://orgmode.org"), Ok(( "", ( @@ -64,7 +73,7 @@ fn parse() { )) ); assert_eq!( - FnDef::parse("[fn:WORD-1] https://orgmode.org"), + parse_fn_def::>("[fn:WORD-1] https://orgmode.org"), Ok(( "", ( @@ -76,7 +85,7 @@ fn parse() { )) ); assert_eq!( - FnDef::parse("[fn:WORD]"), + parse_fn_def::>("[fn:WORD]"), Ok(( "", ( @@ -88,7 +97,7 @@ fn parse() { )) ); - assert!(FnDef::parse("[fn:] https://orgmode.org").is_err()); - assert!(FnDef::parse("[fn:wor d] https://orgmode.org").is_err()); - assert!(FnDef::parse("[fn:WORD https://orgmode.org").is_err()); + assert!(parse_fn_def::>("[fn:] https://orgmode.org").is_err()); + assert!(parse_fn_def::>("[fn:wor d] https://orgmode.org").is_err()); + assert!(parse_fn_def::>("[fn:WORD https://orgmode.org").is_err()); } diff --git a/orgize/src/elements/fn_ref.rs b/orgize/src/elements/fn_ref.rs index 770f403..87c5943 100644 --- a/orgize/src/elements/fn_ref.rs +++ b/orgize/src/elements/fn_ref.rs @@ -4,8 +4,7 @@ use memchr::memchr2_iter; use nom::{ bytes::complete::{tag, take_while}, combinator::opt, - error::ErrorKind, - error_position, + error::{ErrorKind, ParseError}, sequence::preceded, Err, IResult, }; @@ -21,36 +20,9 @@ pub struct FnRef<'a> { pub definition: Option>, } -fn balanced_brackets(input: &str) -> IResult<&str, &str> { - let mut pairs = 1; - for i in memchr2_iter(b'[', b']', input.as_bytes()) { - if input.as_bytes()[i] == b'[' { - pairs += 1; - } else if pairs != 1 { - pairs -= 1; - } else { - return Ok((&input[i..], &input[0..i])); - } - } - Err(Err::Error(error_position!(input, ErrorKind::Tag))) -} - impl FnRef<'_> { - #[inline] - pub(crate) fn parse(input: &str) -> IResult<&str, FnRef<'_>> { - let (input, _) = tag("[fn:")(input)?; - let (input, label) = - take_while(|c: char| c.is_ascii_alphanumeric() || c == '-' || c == '_')(input)?; - let (input, definition) = opt(preceded(tag(":"), balanced_brackets))(input)?; - let (input, _) = tag("]")(input)?; - - Ok(( - input, - FnRef { - label: label.into(), - definition: definition.map(Into::into), - }, - )) + pub(crate) fn parse(input: &str) -> Option<(&str, FnRef<'_>)> { + parse_fn_ref::<()>(input).ok() } pub fn into_owned(self) -> FnRef<'static> { @@ -61,10 +33,43 @@ impl FnRef<'_> { } } +#[inline] +fn parse_fn_ref<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, FnRef<'a>, E> { + let (input, _) = tag("[fn:")(input)?; + let (input, label) = + take_while(|c: char| c.is_ascii_alphanumeric() || c == '-' || c == '_')(input)?; + let (input, definition) = opt(preceded(tag(":"), balanced_brackets))(input)?; + let (input, _) = tag("]")(input)?; + + Ok(( + input, + FnRef { + label: label.into(), + definition: definition.map(Into::into), + }, + )) +} + +fn balanced_brackets<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, &'a str, E> { + let mut pairs = 1; + for i in memchr2_iter(b'[', b']', input.as_bytes()) { + if input.as_bytes()[i] == b'[' { + pairs += 1; + } else if pairs != 1 { + pairs -= 1; + } else { + return Ok((&input[i..], &input[0..i])); + } + } + Err(Err::Error(E::from_error_kind(input, ErrorKind::Tag))) +} + #[test] fn parse() { + use nom::error::VerboseError; + assert_eq!( - FnRef::parse("[fn:1]"), + parse_fn_ref::>("[fn:1]"), Ok(( "", FnRef { @@ -74,7 +79,7 @@ fn parse() { )) ); assert_eq!( - FnRef::parse("[fn:1:2]"), + parse_fn_ref::>("[fn:1:2]"), Ok(( "", FnRef { @@ -84,7 +89,7 @@ fn parse() { )) ); assert_eq!( - FnRef::parse("[fn::2]"), + parse_fn_ref::>("[fn::2]"), Ok(( "", FnRef { @@ -94,7 +99,7 @@ fn parse() { )) ); assert_eq!( - FnRef::parse("[fn::[]]"), + parse_fn_ref::>("[fn::[]]"), Ok(( "", FnRef { @@ -104,5 +109,5 @@ fn parse() { )) ); - assert!(FnRef::parse("[fn::[]").is_err()); + assert!(parse_fn_ref::>("[fn::[]").is_err()); } diff --git a/orgize/src/elements/inline_call.rs b/orgize/src/elements/inline_call.rs index 22b67f5..a5f0ecc 100644 --- a/orgize/src/elements/inline_call.rs +++ b/orgize/src/elements/inline_call.rs @@ -3,6 +3,7 @@ use std::borrow::Cow; use nom::{ bytes::complete::{tag, take_till}, combinator::opt, + error::ParseError, sequence::{delimited, preceded}, IResult, }; @@ -25,34 +26,8 @@ pub struct InlineCall<'a> { } impl InlineCall<'_> { - #[inline] - pub(crate) fn parse(input: &str) -> IResult<&str, InlineCall<'_>> { - let (input, name) = preceded( - tag("call_"), - take_till(|c| c == '[' || c == '\n' || c == '(' || c == ')'), - )(input)?; - let (input, inside_header) = opt(delimited( - tag("["), - take_till(|c| c == ']' || c == '\n'), - tag("]"), - ))(input)?; - let (input, arguments) = - delimited(tag("("), take_till(|c| c == ')' || c == '\n'), tag(")"))(input)?; - let (input, end_header) = opt(delimited( - tag("["), - take_till(|c| c == ']' || c == '\n'), - tag("]"), - ))(input)?; - - Ok(( - input, - InlineCall { - name: name.into(), - arguments: arguments.into(), - inside_header: inside_header.map(Into::into), - end_header: end_header.map(Into::into), - }, - )) + pub(crate) fn parse(input: &str) -> Option<(&str, InlineCall<'_>)> { + parse_inline_call::<()>(input).ok() } pub fn into_owned(self) -> InlineCall<'static> { @@ -65,10 +40,44 @@ impl InlineCall<'_> { } } +#[inline] +fn parse_inline_call<'a, E: ParseError<&'a str>>( + input: &'a str, +) -> IResult<&'a str, InlineCall<'a>, E> { + let (input, name) = preceded( + tag("call_"), + take_till(|c| c == '[' || c == '\n' || c == '(' || c == ')'), + )(input)?; + let (input, inside_header) = opt(delimited( + tag("["), + take_till(|c| c == ']' || c == '\n'), + tag("]"), + ))(input)?; + let (input, arguments) = + delimited(tag("("), take_till(|c| c == ')' || c == '\n'), tag(")"))(input)?; + let (input, end_header) = opt(delimited( + tag("["), + take_till(|c| c == ']' || c == '\n'), + tag("]"), + ))(input)?; + + Ok(( + input, + InlineCall { + name: name.into(), + arguments: arguments.into(), + inside_header: inside_header.map(Into::into), + end_header: end_header.map(Into::into), + }, + )) +} + #[test] fn parse() { + use nom::error::VerboseError; + assert_eq!( - InlineCall::parse("call_square(4)"), + parse_inline_call::>("call_square(4)"), Ok(( "", InlineCall { @@ -80,7 +89,7 @@ fn parse() { )) ); assert_eq!( - InlineCall::parse("call_square[:results output](4)"), + parse_inline_call::>("call_square[:results output](4)"), Ok(( "", InlineCall { @@ -92,7 +101,7 @@ fn parse() { )) ); assert_eq!( - InlineCall::parse("call_square(4)[:results html]"), + parse_inline_call::>("call_square(4)[:results html]"), Ok(( "", InlineCall { @@ -104,7 +113,7 @@ fn parse() { )) ); assert_eq!( - InlineCall::parse("call_square[:results output](4)[:results html]"), + parse_inline_call::>("call_square[:results output](4)[:results html]"), Ok(( "", InlineCall { diff --git a/orgize/src/elements/inline_src.rs b/orgize/src/elements/inline_src.rs index cb571a5..d03faa8 100644 --- a/orgize/src/elements/inline_src.rs +++ b/orgize/src/elements/inline_src.rs @@ -3,6 +3,7 @@ use std::borrow::Cow; use nom::{ bytes::complete::{tag, take_till, take_while1}, combinator::opt, + error::ParseError, sequence::delimited, IResult, }; @@ -22,27 +23,8 @@ pub struct InlineSrc<'a> { } impl InlineSrc<'_> { - #[inline] - pub(crate) fn parse(input: &str) -> IResult<&str, InlineSrc<'_>> { - let (input, _) = tag("src_")(input)?; - let (input, lang) = - take_while1(|c: char| !c.is_ascii_whitespace() && c != '[' && c != '{')(input)?; - let (input, options) = opt(delimited( - tag("["), - take_till(|c| c == '\n' || c == ']'), - tag("]"), - ))(input)?; - let (input, body) = - delimited(tag("{"), take_till(|c| c == '\n' || c == '}'), tag("}"))(input)?; - - Ok(( - input, - InlineSrc { - lang: lang.into(), - options: options.map(Into::into), - body: body.into(), - }, - )) + pub(crate) fn parse(input: &str) -> Option<(&str, InlineSrc<'_>)> { + parse_inline_src::<()>(input).ok() } pub fn into_owned(self) -> InlineSrc<'static> { @@ -54,10 +36,36 @@ impl InlineSrc<'_> { } } +#[inline] +fn parse_inline_src<'a, E: ParseError<&'a str>>( + input: &'a str, +) -> IResult<&'a str, InlineSrc<'a>, E> { + let (input, _) = tag("src_")(input)?; + let (input, lang) = + take_while1(|c: char| !c.is_ascii_whitespace() && c != '[' && c != '{')(input)?; + let (input, options) = opt(delimited( + tag("["), + take_till(|c| c == '\n' || c == ']'), + tag("]"), + ))(input)?; + let (input, body) = delimited(tag("{"), take_till(|c| c == '\n' || c == '}'), tag("}"))(input)?; + + Ok(( + input, + InlineSrc { + lang: lang.into(), + options: options.map(Into::into), + body: body.into(), + }, + )) +} + #[test] fn parse() { + use nom::error::VerboseError; + assert_eq!( - InlineSrc::parse("src_C{int a = 0;}"), + parse_inline_src::>("src_C{int a = 0;}"), Ok(( "", InlineSrc { @@ -68,7 +76,7 @@ fn parse() { )) ); assert_eq!( - InlineSrc::parse("src_xml[:exports code]{text}"), + parse_inline_src::>("src_xml[:exports code]{text}"), Ok(( "", InlineSrc { @@ -79,7 +87,11 @@ fn parse() { )) ); - assert!(InlineSrc::parse("src_xml[:exports code]{text").is_err()); - assert!(InlineSrc::parse("src_[:exports code]{text}").is_err()); - assert!(InlineSrc::parse("src_xml[:exports code]").is_err()); + assert!( + parse_inline_src::>("src_xml[:exports code]{text").is_err() + ); + assert!( + parse_inline_src::>("src_[:exports code]{text}").is_err() + ); + assert!(parse_inline_src::>("src_xml[:exports code]").is_err()); } diff --git a/orgize/src/elements/keyword.rs b/orgize/src/elements/keyword.rs index 0f367d1..0625284 100644 --- a/orgize/src/elements/keyword.rs +++ b/orgize/src/elements/keyword.rs @@ -3,6 +3,7 @@ use std::borrow::Cow; use nom::{ bytes::complete::{tag, take_till}, combinator::opt, + error::ParseError, sequence::delimited, IResult, }; @@ -48,7 +49,15 @@ impl BabelCall<'_> { } } -pub(crate) fn parse_keyword(input: &str) -> IResult<&str, (&str, Option<&str>, &str)> { +#[inline] +pub fn parse_keyword(input: &str) -> Option<(&str, (&str, Option<&str>, &str))> { + parse_keyword_internal::<()>(input).ok() +} + +#[inline] +fn parse_keyword_internal<'a, E: ParseError<&'a str>>( + input: &'a str, +) -> IResult<&'a str, (&'a str, Option<&'a str>, &'a str), E> { let (input, _) = tag("#+")(input)?; let (input, key) = take_till(|c: char| c.is_ascii_whitespace() || c == ':' || c == '[')(input)?; let (input, optional) = opt(delimited( @@ -64,36 +73,44 @@ pub(crate) fn parse_keyword(input: &str) -> IResult<&str, (&str, Option<&str>, & #[test] fn parse() { - assert_eq!(parse_keyword("#+KEY:"), Ok(("", ("KEY", None, "")))); + use nom::error::VerboseError; + assert_eq!( - parse_keyword("#+KEY: VALUE"), + parse_keyword_internal::>("#+KEY:"), + Ok(("", ("KEY", None, ""))) + ); + assert_eq!( + parse_keyword_internal::>("#+KEY: VALUE"), Ok(("", ("KEY", None, "VALUE"))) ); assert_eq!( - parse_keyword("#+K_E_Y: VALUE"), + parse_keyword_internal::>("#+K_E_Y: VALUE"), Ok(("", ("K_E_Y", None, "VALUE"))) ); assert_eq!( - parse_keyword("#+KEY:VALUE\n"), + parse_keyword_internal::>("#+KEY:VALUE\n"), Ok(("", ("KEY", None, "VALUE"))) ); - assert!(parse_keyword("#+KE Y: VALUE").is_err()); - assert!(parse_keyword("#+ KEY: VALUE").is_err()); - - assert_eq!(parse_keyword("#+RESULTS:"), Ok(("", ("RESULTS", None, "")))); + assert!(parse_keyword_internal::>("#+KE Y: VALUE").is_err()); + assert!(parse_keyword_internal::>("#+ KEY: VALUE").is_err()); assert_eq!( - parse_keyword("#+ATTR_LATEX: :width 5cm\n"), + parse_keyword_internal::>("#+RESULTS:"), + Ok(("", ("RESULTS", None, ""))) + ); + + assert_eq!( + parse_keyword_internal::>("#+ATTR_LATEX: :width 5cm\n"), Ok(("", ("ATTR_LATEX", None, ":width 5cm"))) ); assert_eq!( - parse_keyword("#+CALL: double(n=4)"), + parse_keyword_internal::>("#+CALL: double(n=4)"), Ok(("", ("CALL", None, "double(n=4)"))) ); assert_eq!( - parse_keyword("#+CAPTION[Short caption]: Longer caption."), + parse_keyword_internal::>("#+CAPTION[Short caption]: Longer caption."), Ok(("", ("CAPTION", Some("Short caption"), "Longer caption.",))) ); } diff --git a/orgize/src/elements/link.rs b/orgize/src/elements/link.rs index 15292ef..34b3b5b 100644 --- a/orgize/src/elements/link.rs +++ b/orgize/src/elements/link.rs @@ -3,6 +3,7 @@ use std::borrow::Cow; use nom::{ bytes::complete::{tag, take_while}, combinator::opt, + error::ParseError, sequence::delimited, IResult, }; @@ -20,25 +21,8 @@ pub struct Link<'a> { impl Link<'_> { #[inline] - pub(crate) fn parse(input: &str) -> IResult<&str, Link<'_>> { - let (input, path) = delimited( - tag("[["), - take_while(|c: char| c != '<' && c != '>' && c != '\n' && c != ']'), - tag("]"), - )(input)?; - let (input, desc) = opt(delimited( - tag("["), - take_while(|c: char| c != '[' && c != ']'), - tag("]"), - ))(input)?; - let (input, _) = tag("]")(input)?; - Ok(( - input, - Link { - path: path.into(), - desc: desc.map(Into::into), - }, - )) + pub(crate) fn parse(input: &str) -> Option<(&str, Link<'_>)> { + parse_link::<()>(input).ok() } pub fn into_owned(self) -> Link<'static> { @@ -49,10 +33,34 @@ impl Link<'_> { } } +#[inline] +fn parse_link<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, Link<'a>, E> { + let (input, path) = delimited( + tag("[["), + take_while(|c: char| c != '<' && c != '>' && c != '\n' && c != ']'), + tag("]"), + )(input)?; + let (input, desc) = opt(delimited( + tag("["), + take_while(|c: char| c != '[' && c != ']'), + tag("]"), + ))(input)?; + let (input, _) = tag("]")(input)?; + Ok(( + input, + Link { + path: path.into(), + desc: desc.map(Into::into), + }, + )) +} + #[test] fn parse() { + use nom::error::VerboseError; + assert_eq!( - Link::parse("[[#id]]"), + parse_link::>("[[#id]]"), Ok(( "", Link { @@ -62,7 +70,7 @@ fn parse() { )) ); assert_eq!( - Link::parse("[[#id][desc]]"), + parse_link::>("[[#id][desc]]"), Ok(( "", Link { @@ -71,5 +79,5 @@ fn parse() { } )) ); - assert!(Link::parse("[[#id][desc]").is_err()); + assert!(parse_link::>("[[#id][desc]").is_err()); } diff --git a/orgize/src/elements/macros.rs b/orgize/src/elements/macros.rs index 77b304e..a5985e7 100644 --- a/orgize/src/elements/macros.rs +++ b/orgize/src/elements/macros.rs @@ -3,6 +3,7 @@ use std::borrow::Cow; use nom::{ bytes::complete::{tag, take, take_until, take_while1}, combinator::{opt, verify}, + error::ParseError, sequence::delimited, IResult, }; @@ -20,23 +21,8 @@ pub struct Macros<'a> { } impl Macros<'_> { - #[inline] - pub(crate) fn parse(input: &str) -> IResult<&str, Macros<'_>> { - let (input, _) = tag("{{{")(input)?; - let (input, name) = verify( - take_while1(|c: char| c.is_ascii_alphanumeric() || c == '-' || c == '_'), - |s: &str| s.starts_with(|c: char| c.is_ascii_alphabetic()), - )(input)?; - let (input, arguments) = opt(delimited(tag("("), take_until(")}}}"), take(1usize)))(input)?; - let (input, _) = tag("}}}")(input)?; - - Ok(( - input, - Macros { - name: name.into(), - arguments: arguments.map(Into::into), - }, - )) + pub(crate) fn parse(input: &str) -> Option<(&str, Macros<'_>)> { + parse_macros::<()>(input).ok() } pub fn into_owned(self) -> Macros<'static> { @@ -47,10 +33,31 @@ impl Macros<'_> { } } +#[inline] +fn parse_macros<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, Macros<'a>, E> { + let (input, _) = tag("{{{")(input)?; + let (input, name) = verify( + take_while1(|c: char| c.is_ascii_alphanumeric() || c == '-' || c == '_'), + |s: &str| s.starts_with(|c: char| c.is_ascii_alphabetic()), + )(input)?; + let (input, arguments) = opt(delimited(tag("("), take_until(")}}}"), take(1usize)))(input)?; + let (input, _) = tag("}}}")(input)?; + + Ok(( + input, + Macros { + name: name.into(), + arguments: arguments.map(Into::into), + }, + )) +} + #[test] fn parse() { + use nom::error::VerboseError; + assert_eq!( - Macros::parse("{{{poem(red,blue)}}}"), + parse_macros::>("{{{poem(red,blue)}}}"), Ok(( "", Macros { @@ -60,7 +67,7 @@ fn parse() { )) ); assert_eq!( - Macros::parse("{{{poem())}}}"), + parse_macros::>("{{{poem())}}}"), Ok(( "", Macros { @@ -70,7 +77,7 @@ fn parse() { )) ); assert_eq!( - Macros::parse("{{{author}}}"), + parse_macros::>("{{{author}}}"), Ok(( "", Macros { @@ -79,8 +86,8 @@ fn parse() { } )) ); - assert!(Macros::parse("{{{0uthor}}}").is_err()); - assert!(Macros::parse("{{{author}}").is_err()); - assert!(Macros::parse("{{{poem(}}}").is_err()); - assert!(Macros::parse("{{{poem)}}}").is_err()); + assert!(parse_macros::>("{{{0uthor}}}").is_err()); + assert!(parse_macros::>("{{{author}}").is_err()); + assert!(parse_macros::>("{{{poem(}}}").is_err()); + assert!(parse_macros::>("{{{poem)}}}").is_err()); } diff --git a/orgize/src/elements/mod.rs b/orgize/src/elements/mod.rs index 8a48a31..56e1510 100644 --- a/orgize/src/elements/mod.rs +++ b/orgize/src/elements/mod.rs @@ -1,32 +1,27 @@ //! Org-mode elements -mod block; -mod clock; -mod cookie; -mod drawer; -mod dyn_block; -mod emphasis; -mod fn_def; -mod fn_ref; -mod inline_call; -mod inline_src; -mod keyword; -mod link; -mod list; -mod macros; -mod planning; -mod radio_target; -mod rule; -mod snippet; -mod table; -mod target; -mod timestamp; -mod title; - -pub(crate) use self::{ - block::parse_block_element, emphasis::parse_emphasis, keyword::parse_keyword, - radio_target::parse_radio_target, rule::parse_rule, table::parse_table_el, -}; +pub(crate) mod block; +pub(crate) mod clock; +pub(crate) mod cookie; +pub(crate) mod drawer; +pub(crate) mod dyn_block; +pub(crate) mod emphasis; +pub(crate) mod fn_def; +pub(crate) mod fn_ref; +pub(crate) mod inline_call; +pub(crate) mod inline_src; +pub(crate) mod keyword; +pub(crate) mod link; +pub(crate) mod list; +pub(crate) mod macros; +pub(crate) mod planning; +pub(crate) mod radio_target; +pub(crate) mod rule; +pub(crate) mod snippet; +pub(crate) mod table; +pub(crate) mod target; +pub(crate) mod timestamp; +pub(crate) mod title; pub use self::{ block::{ diff --git a/orgize/src/elements/planning.rs b/orgize/src/elements/planning.rs index c37e988..ddfc61c 100644 --- a/orgize/src/elements/planning.rs +++ b/orgize/src/elements/planning.rs @@ -30,23 +30,18 @@ impl Planning<'_> { let next = &tail[i + 1..].trim_start(); macro_rules! set_timestamp { - ($timestamp:expr) => { - if $timestamp.is_none() { - let (new_tail, timestamp) = Timestamp::parse_active(next) - .or_else(|_| Timestamp::parse_inactive(next)) - .ok()?; - $timestamp = Some(timestamp); - tail = new_tail.trim_start(); - } else { - return None; - } - }; + ($timestamp:expr) => {{ + let (new_tail, timestamp) = + Timestamp::parse_active(next).or(Timestamp::parse_inactive(next))?; + $timestamp = Some(timestamp); + tail = new_tail.trim_start(); + }}; } match &tail[..i] { - "DEADLINE:" => set_timestamp!(deadline), - "SCHEDULED:" => set_timestamp!(scheduled), - "CLOSED:" => set_timestamp!(closed), + "DEADLINE:" if deadline.is_none() => set_timestamp!(deadline), + "SCHEDULED:" if scheduled.is_none() => set_timestamp!(scheduled), + "CLOSED:" if closed.is_none() => set_timestamp!(closed), _ => return None, } } diff --git a/orgize/src/elements/radio_target.rs b/orgize/src/elements/radio_target.rs index 84c14c5..c5da94c 100644 --- a/orgize/src/elements/radio_target.rs +++ b/orgize/src/elements/radio_target.rs @@ -1,6 +1,7 @@ use nom::{ bytes::complete::{tag, take_while}, combinator::verify, + error::ParseError, sequence::delimited, IResult, }; @@ -8,7 +9,14 @@ use nom::{ // TODO: text-markup, entities, latex-fragments, subscript and superscript #[inline] -pub(crate) fn parse_radio_target(input: &str) -> IResult<&str, &str> { +pub fn parse_radio_target(input: &str) -> Option<(&str, &str)> { + parse_radio_target_internal::<()>(input).ok() +} + +#[inline] +fn parse_radio_target_internal<'a, E: ParseError<&'a str>>( + input: &'a str, +) -> IResult<&'a str, &'a str, E> { let (input, contents) = delimited( tag("<<<"), verify( @@ -23,12 +31,20 @@ pub(crate) fn parse_radio_target(input: &str) -> IResult<&str, &str> { #[test] fn parse() { - assert_eq!(parse_radio_target("<<>>"), Ok(("", "target"))); - assert_eq!(parse_radio_target("<<>>"), Ok(("", "tar get"))); - assert!(parse_radio_target("<<>>").is_err()); - assert!(parse_radio_target("<<< target>>>").is_err()); - assert!(parse_radio_target("<<>>").is_err()); - assert!(parse_radio_target("<<get>>>").is_err()); - assert!(parse_radio_target("<<>>").is_err()); - assert!(parse_radio_target("<<>").is_err()); + use nom::error::VerboseError; + + assert_eq!( + parse_radio_target_internal::>("<<>>"), + Ok(("", "target")) + ); + assert_eq!( + parse_radio_target_internal::>("<<>>"), + Ok(("", "tar get")) + ); + assert!(parse_radio_target_internal::>("<<>>").is_err()); + assert!(parse_radio_target_internal::>("<<< target>>>").is_err()); + assert!(parse_radio_target_internal::>("<<>>").is_err()); + assert!(parse_radio_target_internal::>("<<get>>>").is_err()); + assert!(parse_radio_target_internal::>("<<>>").is_err()); + assert!(parse_radio_target_internal::>("<<>").is_err()); } diff --git a/orgize/src/elements/rule.rs b/orgize/src/elements/rule.rs index c0b5e13..26692f2 100644 --- a/orgize/src/elements/rule.rs +++ b/orgize/src/elements/rule.rs @@ -1,10 +1,16 @@ use std::usize; -use nom::{bytes::complete::take_while_m_n, IResult}; +use nom::{bytes::complete::take_while_m_n, error::ParseError, IResult}; use crate::parsers::eol; -pub(crate) fn parse_rule(input: &str) -> IResult<&str, ()> { +pub fn parse_rule(input: &str) -> Option<&str> { + parse_rule_internal::<()>(input) + .ok() + .map(|(input, _)| input) +} + +fn parse_rule_internal<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, (), E> { let (input, _) = take_while_m_n(5, usize::MAX, |c| c == '-')(input)?; let (input, _) = eol(input)?; Ok((input, ())) @@ -12,18 +18,32 @@ pub(crate) fn parse_rule(input: &str) -> IResult<&str, ()> { #[test] fn parse() { - assert_eq!(parse_rule("-----"), Ok(("", ()))); - assert_eq!(parse_rule("--------"), Ok(("", ()))); - assert_eq!(parse_rule("-----\n"), Ok(("", ()))); - assert_eq!(parse_rule("----- \n"), Ok(("", ()))); - assert!(parse_rule("").is_err()); - assert!(parse_rule("----").is_err()); - assert!(parse_rule("----").is_err()); - assert!(parse_rule("None----").is_err()); - assert!(parse_rule("None ----").is_err()); - assert!(parse_rule("None------").is_err()); - assert!(parse_rule("----None----").is_err()); - assert!(parse_rule("\t\t----").is_err()); - assert!(parse_rule("------None").is_err()); - assert!(parse_rule("----- None").is_err()); + use nom::error::VerboseError; + + assert_eq!( + parse_rule_internal::>("-----"), + Ok(("", ())) + ); + assert_eq!( + parse_rule_internal::>("--------"), + Ok(("", ())) + ); + assert_eq!( + parse_rule_internal::>("-----\n"), + Ok(("", ())) + ); + assert_eq!( + parse_rule_internal::>("----- \n"), + Ok(("", ())) + ); + assert!(parse_rule_internal::>("").is_err()); + assert!(parse_rule_internal::>("----").is_err()); + assert!(parse_rule_internal::>("----").is_err()); + assert!(parse_rule_internal::>("None----").is_err()); + assert!(parse_rule_internal::>("None ----").is_err()); + assert!(parse_rule_internal::>("None------").is_err()); + assert!(parse_rule_internal::>("----None----").is_err()); + assert!(parse_rule_internal::>("\t\t----").is_err()); + assert!(parse_rule_internal::>("------None").is_err()); + assert!(parse_rule_internal::>("----- None").is_err()); } diff --git a/orgize/src/elements/snippet.rs b/orgize/src/elements/snippet.rs index 865f6f0..8280c04 100644 --- a/orgize/src/elements/snippet.rs +++ b/orgize/src/elements/snippet.rs @@ -2,6 +2,7 @@ use std::borrow::Cow; use nom::{ bytes::complete::{tag, take, take_until, take_while1}, + error::ParseError, sequence::{delimited, separated_pair}, IResult, }; @@ -18,25 +19,8 @@ pub struct Snippet<'a> { } impl Snippet<'_> { - #[inline] - pub(crate) fn parse(input: &str) -> IResult<&str, Snippet<'_>> { - let (input, (name, value)) = delimited( - tag("@@"), - separated_pair( - take_while1(|c: char| c.is_ascii_alphanumeric() || c == '-'), - tag(":"), - take_until("@@"), - ), - take(2usize), - )(input)?; - - Ok(( - input, - Snippet { - name: name.into(), - value: value.into(), - }, - )) + pub(crate) fn parse(input: &str) -> Option<(&str, Snippet<'_>)> { + parse_snippet::<()>(input).ok() } pub fn into_owned(self) -> Snippet<'static> { @@ -47,10 +31,33 @@ impl Snippet<'_> { } } +#[inline] +fn parse_snippet<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, Snippet<'a>, E> { + let (input, (name, value)) = delimited( + tag("@@"), + separated_pair( + take_while1(|c: char| c.is_ascii_alphanumeric() || c == '-'), + tag(":"), + take_until("@@"), + ), + take(2usize), + )(input)?; + + Ok(( + input, + Snippet { + name: name.into(), + value: value.into(), + }, + )) +} + #[test] fn parse() { + use nom::error::VerboseError; + assert_eq!( - Snippet::parse("@@html:@@"), + parse_snippet::>("@@html:@@"), Ok(( "", Snippet { @@ -60,7 +67,7 @@ fn parse() { )) ); assert_eq!( - Snippet::parse("@@latex:any arbitrary LaTeX code@@"), + parse_snippet::>("@@latex:any arbitrary LaTeX code@@"), Ok(( "", Snippet { @@ -70,7 +77,7 @@ fn parse() { )) ); assert_eq!( - Snippet::parse("@@html:@@"), + parse_snippet::>("@@html:@@"), Ok(( "", Snippet { @@ -80,7 +87,7 @@ fn parse() { )) ); assert_eq!( - Snippet::parse("@@html:

@

@@"), + parse_snippet::>("@@html:

@

@@"), Ok(( "", Snippet { @@ -89,7 +96,7 @@ fn parse() { } )) ); - assert!(Snippet::parse("@@html:@").is_err()); - assert!(Snippet::parse("@@html@@").is_err()); - assert!(Snippet::parse("@@:@@").is_err()); + assert!(parse_snippet::>("@@html:@").is_err()); + assert!(parse_snippet::>("@@html@@").is_err()); + assert!(parse_snippet::>("@@:@@").is_err()); } diff --git a/orgize/src/elements/table.rs b/orgize/src/elements/table.rs index 41407e8..522050d 100644 --- a/orgize/src/elements/table.rs +++ b/orgize/src/elements/table.rs @@ -2,6 +2,7 @@ use std::borrow::Cow; use nom::{ combinator::{peek, verify}, + error::ParseError, IResult, }; @@ -57,19 +58,30 @@ impl TableRow { } } -pub(crate) fn parse_table_el(input: &str) -> IResult<&str, &str> { +pub(crate) fn parse_table_el(input: &str) -> Option<(&str, &str)> { + parse_table_el_internal::<()>(input).ok() +} + +fn parse_table_el_internal<'a, E: ParseError<&'a str>>( + input: &'a str, +) -> IResult<&'a str, &'a str, E> { let (input, _) = peek(verify(line, |s: &str| { let s = s.trim(); s.starts_with("+-") && s.as_bytes().iter().all(|&c| c == b'+' || c == b'-') }))(input)?; - take_lines_while(|line| line.starts_with('|') || line.starts_with('+'))(input) + let (input, content) = + take_lines_while(|line| line.starts_with('|') || line.starts_with('+'))(input); + + Ok((input, content)) } #[test] fn parse_table_el_() { + use nom::error::VerboseError; + assert_eq!( - parse_table_el( + parse_table_el_internal::>( r#"+---+ | | +---+ @@ -85,6 +97,6 @@ fn parse_table_el_() { "# )) ); - assert!(parse_table_el("").is_err()); - assert!(parse_table_el("+----|---").is_err()); + assert!(parse_table_el_internal::>("").is_err()); + assert!(parse_table_el_internal::>("+----|---").is_err()); } diff --git a/orgize/src/elements/target.rs b/orgize/src/elements/target.rs index 44a1c2d..ccf235f 100644 --- a/orgize/src/elements/target.rs +++ b/orgize/src/elements/target.rs @@ -3,6 +3,7 @@ use std::borrow::Cow; use nom::{ bytes::complete::{tag, take_while}, combinator::verify, + error::ParseError, sequence::delimited, IResult, }; @@ -18,22 +19,8 @@ pub struct Target<'a> { impl Target<'_> { #[inline] - pub(crate) fn parse(input: &str) -> IResult<&str, Target<'_>> { - let (input, target) = delimited( - tag("<<"), - verify( - take_while(|c: char| c != '<' && c != '\n' && c != '>'), - |s: &str| s.starts_with(|c| c != ' ') && s.ends_with(|c| c != ' '), - ), - tag(">>"), - )(input)?; - - Ok(( - input, - Target { - target: target.into(), - }, - )) + pub(crate) fn parse(input: &str) -> Option<(&str, Target<'_>)> { + parse_target::<()>(input).ok() } pub fn into_owned(self) -> Target<'static> { @@ -43,10 +30,31 @@ impl Target<'_> { } } +#[inline] +fn parse_target<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, Target<'a>, E> { + let (input, target) = delimited( + tag("<<"), + verify( + take_while(|c: char| c != '<' && c != '\n' && c != '>'), + |s: &str| s.starts_with(|c| c != ' ') && s.ends_with(|c| c != ' '), + ), + tag(">>"), + )(input)?; + + Ok(( + input, + Target { + target: target.into(), + }, + )) +} + #[test] fn parse() { + use nom::error::VerboseError; + assert_eq!( - Target::parse("<>"), + parse_target::>("<>"), Ok(( "", Target { @@ -55,7 +63,7 @@ fn parse() { )) ); assert_eq!( - Target::parse("<>"), + parse_target::>("<>"), Ok(( "", Target { @@ -63,10 +71,10 @@ fn parse() { } )) ); - assert!(Target::parse("<>").is_err()); - assert!(Target::parse("<< target>>").is_err()); - assert!(Target::parse("<>").is_err()); - assert!(Target::parse("<get>>").is_err()); - assert!(Target::parse("<>").is_err()); - assert!(Target::parse("<").is_err()); + assert!(parse_target::>("<>").is_err()); + assert!(parse_target::>("<< target>>").is_err()); + assert!(parse_target::>("<>").is_err()); + assert!(parse_target::>("<get>>").is_err()); + assert!(parse_target::>("<>").is_err()); + assert!(parse_target::>("<").is_err()); } diff --git a/orgize/src/elements/timestamp.rs b/orgize/src/elements/timestamp.rs index 3d5afbc..a5527dc 100644 --- a/orgize/src/elements/timestamp.rs +++ b/orgize/src/elements/timestamp.rs @@ -4,6 +4,7 @@ use nom::{ bytes::complete::{tag, take, take_till, take_while, take_while_m_n}, character::complete::{space0, space1}, combinator::{map, map_res, opt}, + error::ParseError, sequence::preceded, IResult, }; @@ -137,123 +138,16 @@ pub enum Timestamp<'a> { } impl Timestamp<'_> { - pub(crate) fn parse_active(input: &str) -> IResult<&str, Timestamp<'_>> { - let (input, _) = tag("<")(input)?; - let (input, start) = parse_datetime(input)?; - - if input.starts_with('-') { - let (input, (hour, minute)) = parse_time(&input[1..])?; - let (input, _) = space0(input)?; - // TODO: delay-or-repeater - let (input, _) = tag(">")(input)?; - let mut end = start.clone(); - end.hour = Some(hour); - end.minute = Some(minute); - return Ok(( - input, - Timestamp::ActiveRange { - start, - end, - repeater: None, - delay: None, - }, - )); - } - - let (input, _) = space0(input)?; - // TODO: delay-or-repeater - let (input, _) = tag(">")(input)?; - - if input.starts_with("--<") { - let (input, end) = parse_datetime(&input["--<".len()..])?; - let (input, _) = space0(input)?; - // TODO: delay-or-repeater - let (input, _) = tag(">")(input)?; - Ok(( - input, - Timestamp::ActiveRange { - start, - end, - repeater: None, - delay: None, - }, - )) - } else { - Ok(( - input, - Timestamp::Active { - start, - repeater: None, - delay: None, - }, - )) - } + pub(crate) fn parse_active(input: &str) -> Option<(&str, Timestamp<'_>)> { + parse_active::<()>(input).ok() } - pub(crate) fn parse_inactive(input: &str) -> IResult<&str, Timestamp<'_>> { - let (input, _) = tag("[")(input)?; - let (input, start) = parse_datetime(input)?; - - if input.starts_with('-') { - let (input, (hour, minute)) = parse_time(&input[1..])?; - let (input, _) = space0(input)?; - // TODO: delay-or-repeater - let (input, _) = tag("]")(input)?; - let mut end = start.clone(); - end.hour = Some(hour); - end.minute = Some(minute); - return Ok(( - input, - Timestamp::InactiveRange { - start, - end, - repeater: None, - delay: None, - }, - )); - } - - let (input, _) = space0(input)?; - // TODO: delay-or-repeater - let (input, _) = tag("]")(input)?; - - if input.starts_with("--[") { - let (input, end) = parse_datetime(&input["--[".len()..])?; - let (input, _) = space0(input)?; - // TODO: delay-or-repeater - let (input, _) = tag("]")(input)?; - Ok(( - input, - Timestamp::InactiveRange { - start, - end, - repeater: None, - delay: None, - }, - )) - } else { - Ok(( - input, - Timestamp::Inactive { - start, - repeater: None, - delay: None, - }, - )) - } + pub(crate) fn parse_inactive(input: &str) -> Option<(&str, Timestamp<'_>)> { + parse_inactive::<()>(input).ok() } - pub(crate) fn parse_diary(input: &str) -> IResult<&str, Timestamp<'_>> { - let (input, _) = tag("<%%(")(input)?; - let (input, value) = take_till(|c| c == ')' || c == '>' || c == '\n')(input)?; - let (input, _) = tag(")>")(input)?; - - Ok(( - input, - Timestamp::Diary { - value: value.into(), - }, - )) + pub(crate) fn parse_diary(input: &str) -> Option<(&str, Timestamp<'_>)> { + parse_diary::<()>(input).ok() } pub fn into_owned(self) -> Timestamp<'static> { @@ -305,7 +199,132 @@ impl Timestamp<'_> { } } -fn parse_time(input: &str) -> IResult<&str, (u8, u8)> { +pub fn parse_active<'a, E: ParseError<&'a str>>( + input: &'a str, +) -> IResult<&'a str, Timestamp<'a>, E> { + let (input, _) = tag("<")(input)?; + let (input, start) = parse_datetime(input)?; + + if input.starts_with('-') { + let (input, (hour, minute)) = parse_time(&input[1..])?; + let (input, _) = space0(input)?; + // TODO: delay-or-repeater + let (input, _) = tag(">")(input)?; + let mut end = start.clone(); + end.hour = Some(hour); + end.minute = Some(minute); + return Ok(( + input, + Timestamp::ActiveRange { + start, + end, + repeater: None, + delay: None, + }, + )); + } + + let (input, _) = space0(input)?; + // TODO: delay-or-repeater + let (input, _) = tag(">")(input)?; + + if input.starts_with("--<") { + let (input, end) = parse_datetime(&input["--<".len()..])?; + let (input, _) = space0(input)?; + // TODO: delay-or-repeater + let (input, _) = tag(">")(input)?; + Ok(( + input, + Timestamp::ActiveRange { + start, + end, + repeater: None, + delay: None, + }, + )) + } else { + Ok(( + input, + Timestamp::Active { + start, + repeater: None, + delay: None, + }, + )) + } +} + +pub fn parse_inactive<'a, E: ParseError<&'a str>>( + input: &'a str, +) -> IResult<&'a str, Timestamp<'a>, E> { + let (input, _) = tag("[")(input)?; + let (input, start) = parse_datetime(input)?; + + if input.starts_with('-') { + let (input, (hour, minute)) = parse_time(&input[1..])?; + let (input, _) = space0(input)?; + // TODO: delay-or-repeater + let (input, _) = tag("]")(input)?; + let mut end = start.clone(); + end.hour = Some(hour); + end.minute = Some(minute); + return Ok(( + input, + Timestamp::InactiveRange { + start, + end, + repeater: None, + delay: None, + }, + )); + } + + let (input, _) = space0(input)?; + // TODO: delay-or-repeater + let (input, _) = tag("]")(input)?; + + if input.starts_with("--[") { + let (input, end) = parse_datetime(&input["--[".len()..])?; + let (input, _) = space0(input)?; + // TODO: delay-or-repeater + let (input, _) = tag("]")(input)?; + Ok(( + input, + Timestamp::InactiveRange { + start, + end, + repeater: None, + delay: None, + }, + )) + } else { + Ok(( + input, + Timestamp::Inactive { + start, + repeater: None, + delay: None, + }, + )) + } +} + +pub fn parse_diary<'a, E: ParseError<&'a str>>( + input: &'a str, +) -> IResult<&'a str, Timestamp<'a>, E> { + let (input, _) = tag("<%%(")(input)?; + let (input, value) = take_till(|c| c == ')' || c == '>' || c == '\n')(input)?; + let (input, _) = tag(")>")(input)?; + + Ok(( + input, + Timestamp::Diary { + value: value.into(), + }, + )) +} + +fn parse_time<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, (u8, u8), E> { let (input, hour) = map_res(take_while_m_n(1, 2, |c: char| c.is_ascii_digit()), |num| { u8::from_str_radix(num, 10) })(input)?; @@ -314,7 +333,7 @@ fn parse_time(input: &str) -> IResult<&str, (u8, u8)> { Ok((input, (hour, minute))) } -fn parse_datetime(input: &str) -> IResult<&str, Datetime<'_>> { +fn parse_datetime<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, Datetime<'a>, E> { let parse_u8 = |num| u8::from_str_radix(num, 10); let (input, year) = map_res(take(4usize), |num| u16::from_str_radix(num, 10))(input)?; @@ -397,8 +416,10 @@ fn parse_datetime(input: &str) -> IResult<&str, Datetime<'_>> { #[test] fn parse() { + use nom::error::VerboseError; + assert_eq!( - Timestamp::parse_inactive("[2003-09-16 Tue]"), + parse_inactive::>("[2003-09-16 Tue]"), Ok(( "", Timestamp::Inactive { @@ -416,7 +437,7 @@ fn parse() { )) ); assert_eq!( - Timestamp::parse_inactive("[2003-09-16 Tue 09:39]--[2003-09-16 Tue 10:39]"), + parse_inactive::>("[2003-09-16 Tue 09:39]--[2003-09-16 Tue 10:39]"), Ok(( "", Timestamp::InactiveRange { @@ -442,7 +463,7 @@ fn parse() { )) ); assert_eq!( - Timestamp::parse_active("<2003-09-16 Tue 09:39-10:39>"), + parse_active::>("<2003-09-16 Tue 09:39-10:39>"), Ok(( "", Timestamp::ActiveRange { diff --git a/orgize/src/elements/title.rs b/orgize/src/elements/title.rs index f231f23..405199e 100644 --- a/orgize/src/elements/title.rs +++ b/orgize/src/elements/title.rs @@ -8,15 +8,14 @@ use nom::{ bytes::complete::{tag, take_until, take_while}, character::complete::{anychar, space1}, combinator::{map, map_parser, opt, verify}, - error::ErrorKind, - error_position, + error::{ErrorKind, ParseError}, multi::fold_many0, sequence::{delimited, preceded}, Err, IResult, }; use crate::config::ParseConfig; -use crate::elements::{Drawer, Planning, Timestamp}; +use crate::elements::{drawer::parse_drawer, Planning, Timestamp}; use crate::parsers::{line, skip_empty_lines, take_one_word}; /// Title Elemenet @@ -46,68 +45,11 @@ pub struct Title<'a> { } impl Title<'_> { - #[inline] pub(crate) fn parse<'a>( input: &'a str, config: &ParseConfig, - ) -> IResult<&'a str, (Title<'a>, &'a str)> { - let (input, level) = map(take_while(|c: char| c == '*'), |s: &str| s.len())(input)?; - - debug_assert!(level > 0); - - let (input, keyword) = opt(preceded( - space1, - verify(take_one_word, |s: &str| { - config.todo_keywords.iter().any(|x| x == s) - || config.done_keywords.iter().any(|x| x == s) - }), - ))(input)?; - - let (input, priority) = opt(preceded( - space1, - map_parser( - take_one_word, - delimited( - tag("[#"), - verify(anychar, |c: &char| c.is_ascii_uppercase()), - tag("]"), - ), - ), - ))(input)?; - let (input, tail) = line(input)?; - let tail = tail.trim(); - let (raw, tags) = memrchr(b' ', tail.as_bytes()) - .map(|i| (tail[0..i].trim(), &tail[i + 1..])) - .filter(|(_, x)| x.len() > 2 && x.starts_with(':') && x.ends_with(':')) - .unwrap_or((tail, "")); - - let tags = tags - .split(':') - .filter(|s| !s.is_empty()) - .map(Into::into) - .collect(); - - let (input, planning) = Planning::parse(input) - .map(|(input, planning)| (input, Some(Box::new(planning)))) - .unwrap_or((input, None)); - - let (input, properties) = opt(parse_properties_drawer)(input)?; - - Ok(( - input, - ( - Title { - properties: properties.unwrap_or_default(), - level, - keyword: keyword.map(Into::into), - priority, - tags, - raw: raw.into(), - planning, - }, - raw, - ), - )) + ) -> Option<(&'a str, (Title<'a>, &'a str))> { + parse_title::<()>(input, config).ok() } // TODO: fn is_archived(&self) -> bool { } @@ -136,6 +78,11 @@ impl Title<'_> { .and_then(|planning| planning.deadline.as_ref()) } + /// checks if this headline is "archived" + pub fn is_archived(&self) -> bool { + self.tags.iter().any(|tag| tag == "ARCHIVE") + } + pub fn into_owned(self) -> Title<'static> { Title { level: self.level, @@ -171,10 +118,77 @@ impl Default for Title<'_> { } } -fn parse_properties_drawer(input: &str) -> IResult<&str, HashMap, Cow<'_, str>>> { - let (input, (drawer, content)) = Drawer::parse(input.trim_start())?; +#[inline] +fn parse_title<'a, E: ParseError<&'a str>>( + input: &'a str, + config: &ParseConfig, +) -> IResult<&'a str, (Title<'a>, &'a str), E> { + let (input, level) = map(take_while(|c: char| c == '*'), |s: &str| s.len())(input)?; + + debug_assert!(level > 0); + + let (input, keyword) = opt(preceded( + space1, + verify(take_one_word, |s: &str| { + config.todo_keywords.iter().any(|x| x == s) + || config.done_keywords.iter().any(|x| x == s) + }), + ))(input)?; + + let (input, priority) = opt(preceded( + space1, + map_parser( + take_one_word, + delimited( + tag("[#"), + verify(anychar, |c: &char| c.is_ascii_uppercase()), + tag("]"), + ), + ), + ))(input)?; + let (input, tail) = line(input)?; + let tail = tail.trim(); + let (raw, tags) = memrchr(b' ', tail.as_bytes()) + .map(|i| (tail[0..i].trim(), &tail[i + 1..])) + .filter(|(_, x)| x.len() > 2 && x.starts_with(':') && x.ends_with(':')) + .unwrap_or((tail, "")); + + let tags = tags + .split(':') + .filter(|s| !s.is_empty()) + .map(Into::into) + .collect(); + + let (input, planning) = Planning::parse(input) + .map(|(input, planning)| (input, Some(Box::new(planning)))) + .unwrap_or((input, None)); + + let (input, properties) = opt(parse_properties_drawer)(input)?; + + Ok(( + input, + ( + Title { + properties: properties.unwrap_or_default(), + level, + keyword: keyword.map(Into::into), + priority, + tags, + raw: raw.into(), + planning, + }, + raw, + ), + )) +} + +#[inline] +fn parse_properties_drawer<'a, E: ParseError<&'a str>>( + input: &'a str, +) -> IResult<&'a str, HashMap, Cow<'a, str>>, E> { + let (input, (drawer, content)) = parse_drawer(input.trim_start())?; if drawer.name != "PROPERTIES" { - return Err(Err::Error(error_position!(input, ErrorKind::Tag))); + return Err(Err::Error(E::from_error_kind(input, ErrorKind::Tag))); } let (_, map) = fold_many0( parse_node_property, @@ -187,7 +201,10 @@ fn parse_properties_drawer(input: &str) -> IResult<&str, HashMap, C Ok((input, map)) } -fn parse_node_property(input: &str) -> IResult<&str, (&str, &str)> { +#[inline] +fn parse_node_property<'a, E: ParseError<&'a str>>( + input: &'a str, +) -> IResult<&'a str, (&'a str, &'a str), E> { let input = skip_empty_lines(input).trim_start(); let (input, name) = map(delimited(tag(":"), take_until(":"), tag(":")), |s: &str| { s.trim_end_matches('+') @@ -196,19 +213,17 @@ fn parse_node_property(input: &str) -> IResult<&str, (&str, &str)> { Ok((input, (name, value.trim()))) } -impl Title<'_> { - /// checks if this headline is "archived" - pub fn is_archived(&self) -> bool { - self.tags.iter().any(|tag| tag == "ARCHIVE") - } -} - #[test] -fn parse_title() { +fn parse_title_() { + use nom::error::VerboseError; + use crate::config::DEFAULT_CONFIG; assert_eq!( - Title::parse("**** DONE [#A] COMMENT Title :tag:a2%:", &DEFAULT_CONFIG), + parse_title::>( + "**** DONE [#A] COMMENT Title :tag:a2%:", + &DEFAULT_CONFIG + ), Ok(( "", ( @@ -226,7 +241,7 @@ fn parse_title() { )) ); assert_eq!( - Title::parse("**** ToDO [#A] COMMENT Title", &DEFAULT_CONFIG), + parse_title::>("**** ToDO [#A] COMMENT Title", &DEFAULT_CONFIG), Ok(( "", ( @@ -244,7 +259,7 @@ fn parse_title() { )) ); assert_eq!( - Title::parse("**** T0DO [#A] COMMENT Title", &DEFAULT_CONFIG), + parse_title::>("**** T0DO [#A] COMMENT Title", &DEFAULT_CONFIG), Ok(( "", ( @@ -262,7 +277,7 @@ fn parse_title() { )) ); assert_eq!( - Title::parse("**** DONE [#1] COMMENT Title", &DEFAULT_CONFIG), + parse_title::>("**** DONE [#1] COMMENT Title", &DEFAULT_CONFIG), Ok(( "", ( @@ -280,7 +295,7 @@ fn parse_title() { )) ); assert_eq!( - Title::parse("**** DONE [#a] COMMENT Title", &DEFAULT_CONFIG), + parse_title::>("**** DONE [#a] COMMENT Title", &DEFAULT_CONFIG), Ok(( "", ( @@ -298,7 +313,7 @@ fn parse_title() { )) ); assert_eq!( - Title::parse("**** Title :tag:a2%", &DEFAULT_CONFIG), + parse_title::>("**** Title :tag:a2%", &DEFAULT_CONFIG), Ok(( "", ( @@ -316,7 +331,7 @@ fn parse_title() { )) ); assert_eq!( - Title::parse("**** Title tag:a2%:", &DEFAULT_CONFIG), + parse_title::>("**** Title tag:a2%:", &DEFAULT_CONFIG), Ok(( "", ( @@ -335,7 +350,7 @@ fn parse_title() { ); assert_eq!( - Title::parse( + parse_title::>( "**** DONE Title", &ParseConfig { done_keywords: vec![], @@ -359,7 +374,7 @@ fn parse_title() { )) ); assert_eq!( - Title::parse( + parse_title::>( "**** TASK [#A] Title", &ParseConfig { todo_keywords: vec!["TASK".to_string()], @@ -386,8 +401,12 @@ fn parse_title() { #[test] fn parse_properties_drawer_() { + use nom::error::VerboseError; + assert_eq!( - parse_properties_drawer(" :PROPERTIES:\n :CUSTOM_ID: id\n :END:"), + parse_properties_drawer::>( + " :PROPERTIES:\n :CUSTOM_ID: id\n :END:" + ), Ok(( "", vec![("CUSTOM_ID".into(), "id".into())] @@ -399,14 +418,14 @@ fn parse_properties_drawer_() { // #[test] // fn is_commented() { -// assert!(Title::parse("* COMMENT Title", &DEFAULT_CONFIG) +// assert!(parse_title::>("* COMMENT Title", &DEFAULT_CONFIG) // .1 // .is_commented()); -// assert!(!Title::parse("* Title", &DEFAULT_CONFIG).1.is_commented()); -// assert!(!Title::parse("* C0MMENT Title", &DEFAULT_CONFIG) +// assert!(!parse_title::>("* Title", &DEFAULT_CONFIG).1.is_commented()); +// assert!(!parse_title::>("* C0MMENT Title", &DEFAULT_CONFIG) // .1 // .is_commented()); -// assert!(!Title::parse("* comment Title", &DEFAULT_CONFIG) +// assert!(!parse_title::>("* comment Title", &DEFAULT_CONFIG) // .1 // .is_commented()); // } diff --git a/orgize/src/parsers.rs b/orgize/src/parsers.rs index 8297ce5..472b501 100644 --- a/orgize/src/parsers.rs +++ b/orgize/src/parsers.rs @@ -7,13 +7,16 @@ use std::marker::PhantomData; use indextree::{Arena, NodeId}; use jetscii::{bytes, BytesConst}; use memchr::{memchr, memchr_iter}; -use nom::{ - bytes::complete::take_while1, combinator::verify, error::ErrorKind, error_position, Err, - IResult, -}; +use nom::{bytes::complete::take_while1, combinator::verify, error::ParseError, IResult}; use crate::config::ParseConfig; -use crate::elements::*; +use crate::elements::{ + block::parse_block_element, emphasis::parse_emphasis, keyword::parse_keyword, + radio_target::parse_radio_target, rule::parse_rule, table::parse_table_el, BabelCall, + CenterBlock, Clock, CommentBlock, Cookie, Drawer, DynBlock, Element, ExampleBlock, ExportBlock, + FnDef, FnRef, InlineCall, InlineSrc, Keyword, Link, List, ListItem, Macros, QuoteBlock, + Snippet, SourceBlock, SpecialBlock, Table, TableRow, Target, Timestamp, Title, VerseBlock, +}; pub trait ElementArena<'a> { fn append_element>>(&mut self, element: T, parent: NodeId) -> NodeId; @@ -72,13 +75,8 @@ impl<'a> ElementArena<'a> for OwnedArena<'a, '_, '_> { element: T, parent: NodeId, ) -> NodeId { - if let Some(child) = self.arena[parent].last_child() { - let node = self.arena.new_node(element.into().into_owned()); - child.insert_before(node, self.arena); - node - } else { - self.append_element(element, parent) - } + self.arena + .insert_before_last_child(element.into().into_owned(), parent) } } @@ -170,7 +168,7 @@ pub fn parse_section_and_headlines<'a, T: ElementArena<'a>>( let mut last_end = 0; for i in memchr_iter(b'\n', content.as_bytes()).chain(once(content.len())) { - if let Ok((mut tail, (headline_content, level))) = parse_headline(&content[last_end..]) { + if let Some((mut tail, (headline_content, level))) = parse_headline(&content[last_end..]) { if last_end != 0 { let node = arena.append_element(Element::Section, parent); let content = &content[0..last_end]; @@ -183,7 +181,7 @@ pub fn parse_section_and_headlines<'a, T: ElementArena<'a>>( node, }); - while let Ok((new_tail, (content, level))) = parse_headline(tail) { + while let Some((new_tail, (content, level))) = parse_headline(tail) { debug_assert_ne!(tail, new_tail); let node = arena.append_element(Element::Headline { level }, parent); containers.push(Container::Headline { content, node }); @@ -266,7 +264,7 @@ pub fn parse_block<'a, T: ElementArena<'a>>( parent: NodeId, containers: &mut Vec>, ) -> Option<&'a str> { - if let Ok((tail, (fn_def, content))) = FnDef::parse(contents) { + if let Some((tail, (fn_def, content))) = FnDef::parse(contents) { let node = arena.append_element(fn_def, parent); containers.push(Container::Block { content, node }); return Some(tail); @@ -285,42 +283,37 @@ pub fn parse_block<'a, T: ElementArena<'a>>( match contents.as_bytes().get(0)? { b'C' => { - if let Ok((tail, clock)) = Clock::parse(contents) { - arena.append_element(clock, parent); - return Some(tail); - } + let (tail, clock) = Clock::parse(contents)?; + arena.append_element(clock, parent); + Some(tail) } b'\'' => { // TODO: LaTeX environment + None } b'-' => { - if let Ok((tail, _)) = parse_rule(contents) { - arena.append_element(Element::Rule, parent); - return Some(tail); - } + let tail = parse_rule(contents)?; + arena.append_element(Element::Rule, parent); + Some(tail) } b':' => { - if let Ok((tail, (drawer, content))) = Drawer::parse(contents) { + if let Some((tail, (drawer, content))) = Drawer::parse(contents) { let node = arena.append_element(drawer, parent); containers.push(Container::Block { content, node }); - return Some(tail); - } else if let Ok((tail, value)) = parse_fixed_width(contents) { - arena.append_element( - Element::FixedWidth { - value: value.into(), - }, - parent, - ); - return Some(tail); + Some(tail) + } else { + let (tail, value) = parse_fixed_width(contents)?; + let value = value.into(); + arena.append_element(Element::FixedWidth { value }, parent); + Some(tail) } } b'|' => { - if let Some(tail) = parse_table(arena, contents, containers, parent) { - return Some(tail); - } + let tail = parse_table(arena, contents, containers, parent)?; + Some(tail) } b'#' => { - if let Ok((tail, (name, args, content))) = parse_block_element(contents) { + if let Some((tail, (name, args, content))) = parse_block_element(contents) { match_block( arena, parent, @@ -329,12 +322,12 @@ pub fn parse_block<'a, T: ElementArena<'a>>( args.map(Into::into), content, ); - return Some(tail); - } else if let Ok((tail, (dyn_block, content))) = DynBlock::parse(contents) { + Some(tail) + } else if let Some((tail, (dyn_block, content))) = DynBlock::parse(contents) { let node = arena.append_element(dyn_block, parent); containers.push(Container::Block { content, node }); - return Some(tail); - } else if let Ok((tail, (key, optional, value))) = parse_keyword(contents) { + Some(tail) + } else if let Some((tail, (key, optional, value))) = parse_keyword(contents) { if (&*key).eq_ignore_ascii_case("CALL") { arena.append_element( BabelCall { @@ -352,21 +345,16 @@ pub fn parse_block<'a, T: ElementArena<'a>>( parent, ); } - return Some(tail); - } else if let Ok((tail, value)) = parse_comment(contents) { - arena.append_element( - Element::Comment { - value: value.into(), - }, - parent, - ); - return Some(tail); + Some(tail) + } else { + let (tail, value) = parse_comment(contents)?; + let value = value.into(); + arena.append_element(Element::Comment { value }, parent); + Some(tail) } } - _ => (), + _ => None, } - - None } pub fn match_block<'a, T: ElementArena<'a>>( @@ -542,116 +530,97 @@ pub fn parse_inline<'a, T: ElementArena<'a>>( return None; } - let bytes = contents.as_bytes(); - match bytes[0] { + match contents.as_bytes()[0] { b'@' => { - if let Ok((tail, snippet)) = Snippet::parse(contents) { - arena.append_element(snippet, parent); - return Some(tail); - } + let (tail, snippet) = Snippet::parse(contents)?; + arena.append_element(snippet, parent); + Some(tail) } b'{' => { - if let Ok((tail, macros)) = Macros::parse(contents) { - arena.append_element(macros, parent); - return Some(tail); - } + let (tail, macros) = Macros::parse(contents)?; + arena.append_element(macros, parent); + Some(tail) } b'<' => { - if let Ok((tail, _content)) = parse_radio_target(contents) { + if let Some((tail, _content)) = parse_radio_target(contents) { arena.append_element(Element::RadioTarget, parent); - return Some(tail); - } else if let Ok((tail, target)) = Target::parse(contents) { + Some(tail) + } else if let Some((tail, target)) = Target::parse(contents) { arena.append_element(target, parent); - return Some(tail); - } else if let Ok((tail, timestamp)) = Timestamp::parse_active(contents) { + Some(tail) + } else if let Some((tail, timestamp)) = Timestamp::parse_active(contents) { arena.append_element(timestamp, parent); - return Some(tail); - } else if let Ok((tail, timestamp)) = Timestamp::parse_diary(contents) { + Some(tail) + } else { + let (tail, timestamp) = Timestamp::parse_diary(contents)?; arena.append_element(timestamp, parent); - return Some(tail); + Some(tail) } } b'[' => { - if let Ok((tail, fn_ref)) = FnRef::parse(contents) { + if let Some((tail, fn_ref)) = FnRef::parse(contents) { arena.append_element(fn_ref, parent); - return Some(tail); - } else if let Ok((tail, link)) = Link::parse(contents) { + Some(tail) + } else if let Some((tail, link)) = Link::parse(contents) { arena.append_element(link, parent); - return Some(tail); - } else if let Ok((tail, cookie)) = Cookie::parse(contents) { + Some(tail) + } else if let Some((tail, cookie)) = Cookie::parse(contents) { arena.append_element(cookie, parent); - return Some(tail); - } else if let Ok((tail, timestamp)) = Timestamp::parse_inactive(contents) { + Some(tail) + } else { + let (tail, timestamp) = Timestamp::parse_inactive(contents)?; arena.append_element(timestamp, parent); - return Some(tail); + Some(tail) } } b'*' => { - if let Some((tail, content)) = parse_emphasis(contents, b'*') { - let node = arena.append_element(Element::Bold, parent); - containers.push(Container::Inline { content, node }); - return Some(tail); - } + let (tail, content) = parse_emphasis(contents, b'*')?; + let node = arena.append_element(Element::Bold, parent); + containers.push(Container::Inline { content, node }); + Some(tail) } b'+' => { - if let Some((tail, content)) = parse_emphasis(contents, b'+') { - let node = arena.append_element(Element::Strike, parent); - containers.push(Container::Inline { content, node }); - return Some(tail); - } + let (tail, content) = parse_emphasis(contents, b'+')?; + let node = arena.append_element(Element::Strike, parent); + containers.push(Container::Inline { content, node }); + Some(tail) } b'/' => { - if let Some((tail, content)) = parse_emphasis(contents, b'/') { - let node = arena.append_element(Element::Italic, parent); - containers.push(Container::Inline { content, node }); - return Some(tail); - } + let (tail, content) = parse_emphasis(contents, b'/')?; + let node = arena.append_element(Element::Italic, parent); + containers.push(Container::Inline { content, node }); + Some(tail) } b'_' => { - if let Some((tail, content)) = parse_emphasis(contents, b'_') { - let node = arena.append_element(Element::Underline, parent); - containers.push(Container::Inline { content, node }); - return Some(tail); - } + let (tail, content) = parse_emphasis(contents, b'_')?; + let node = arena.append_element(Element::Underline, parent); + containers.push(Container::Inline { content, node }); + Some(tail) } b'=' => { - if let Some((tail, value)) = parse_emphasis(contents, b'=') { - arena.append_element( - Element::Verbatim { - value: value.into(), - }, - parent, - ); - return Some(tail); - } + let (tail, value) = parse_emphasis(contents, b'=')?; + let value = value.into(); + arena.append_element(Element::Verbatim { value }, parent); + Some(tail) } b'~' => { - if let Some((tail, value)) = parse_emphasis(contents, b'~') { - arena.append_element( - Element::Code { - value: value.into(), - }, - parent, - ); - return Some(tail); - } + let (tail, value) = parse_emphasis(contents, b'~')?; + let value = value.into(); + arena.append_element(Element::Code { value }, parent); + Some(tail) } b's' => { - if let Ok((tail, inline_src)) = InlineSrc::parse(contents) { - arena.append_element(inline_src, parent); - return Some(tail); - } + let (tail, inline_src) = InlineSrc::parse(contents)?; + arena.append_element(inline_src, parent); + Some(tail) } b'c' => { - if let Ok((tail, inline_call)) = InlineCall::parse(contents) { - arena.append_element(inline_call, parent); - return Some(tail); - } + let (tail, inline_call) = InlineCall::parse(contents)?; + arena.append_element(inline_call, parent); + Some(tail) } - _ => (), + _ => None, } - - None } pub fn parse_list_items<'a, T: ElementArena<'a>>( @@ -701,20 +670,16 @@ pub fn parse_table<'a, T: ElementArena<'a>>( } Some("") - } else if let Ok((tail, value)) = parse_table_el(contents) { - arena.append_element( - Table::TableEl { - value: value.into(), - }, - parent, - ); - Some(tail) } else { - None + let (tail, value) = parse_table_el(contents)?; + let value = value.into(); + arena.append_element(Table::TableEl { value }, parent); + + Some(tail) } } -pub fn line(input: &str) -> IResult<&str, &str> { +pub fn line<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, &'a str, E> { if let Some(i) = memchr(b'\n', input.as_bytes()) { if i > 0 && input.as_bytes()[i - 1] == b'\r' { Ok((&input[i + 1..], &input[0..i - 1])) @@ -726,73 +691,76 @@ pub fn line(input: &str) -> IResult<&str, &str> { } } -pub fn eol(input: &str) -> IResult<&str, &str> { +pub fn eol<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, &'a str, E> { verify(line, |s: &str| s.trim().is_empty())(input) } -pub fn take_lines_while(predicate: impl Fn(&str) -> bool) -> impl Fn(&str) -> IResult<&str, &str> { +pub fn take_lines_while(predicate: impl Fn(&str) -> bool) -> impl Fn(&str) -> (&str, &str) { move |input| { let mut last_end = 0; for i in memchr_iter(b'\n', input.as_bytes()) { if i > 0 && input.as_bytes()[i - 1] == b'\r' { if !predicate(&input[last_end..i - 1]) { - return Ok((&input[last_end..], &input[0..last_end])); + return (&input[last_end..], &input[0..last_end]); } } else if !predicate(&input[last_end..i]) { - return Ok((&input[last_end..], &input[0..last_end])); + return (&input[last_end..], &input[0..last_end]); } last_end = i + 1; } if !predicate(&input[last_end..]) { - Ok((&input[last_end..], &input[0..last_end])) + (&input[last_end..], &input[0..last_end]) } else { - Ok(("", input)) + ("", input) } } } pub fn skip_empty_lines(input: &str) -> &str { - take_lines_while(|line| line.trim().is_empty())(input) - .map(|(tail, _)| tail) - .unwrap_or(input) + take_lines_while(|line| line.trim().is_empty())(input).0 } -pub fn parse_headline(input: &str) -> IResult<&str, (&str, usize)> { +pub fn parse_headline(input: &str) -> Option<(&str, (&str, usize))> { let (input_, level) = parse_headline_level(input)?; let (input_, content) = take_lines_while(move |line| { - if let Ok((_, l)) = parse_headline_level(line) { - l > level - } else { - true - } - })(input_)?; - Ok((input_, (&input[0..level + content.len()], level))) + parse_headline_level(line) + .map(|(_, l)| l > level) + .unwrap_or(true) + })(input_); + Some((input_, (&input[0..level + content.len()], level))) } -pub fn parse_headline_level(input: &str) -> IResult<&str, usize> { - let (input, stars) = take_while1(|c: char| c == '*')(input)?; +pub fn parse_headline_level(input: &str) -> Option<(&str, usize)> { + let (input, stars) = take_while1::<_, _, ()>(|c: char| c == '*')(input).ok()?; + if input.starts_with(' ') || input.starts_with('\n') || input.is_empty() { - Ok((input, stars.len())) + Some((input, stars.len())) } else { - Err(Err::Error(error_position!(input, ErrorKind::Tag))) + None } } -pub fn parse_fixed_width(input: &str) -> IResult<&str, &str> { - verify( - take_lines_while(|line| line == ":" || line.starts_with(": ")), - |s: &str| !s.is_empty(), - )(input) +pub fn parse_fixed_width(input: &str) -> Option<(&str, &str)> { + let (input, content) = take_lines_while(|line| line == ":" || line.starts_with(": "))(input); + + if !content.is_empty() { + Some((input, content)) + } else { + None + } } -pub fn parse_comment(input: &str) -> IResult<&str, &str> { - verify( - take_lines_while(|line| line == "#" || line.starts_with("# ")), - |s: &str| !s.is_empty(), - )(input) +pub fn parse_comment(input: &str) -> Option<(&str, &str)> { + let (input, content) = take_lines_while(|line| line == "#" || line.starts_with("# "))(input); + + if !content.is_empty() { + Some((input, content)) + } else { + None + } } -pub fn take_one_word(input: &str) -> IResult<&str, &str> { +pub fn take_one_word<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, &'a str, E> { take_while1(|c: char| !c.is_ascii_whitespace())(input) }