feat(parser): generic error type

This commit is contained in:
PoiScript 2019-10-02 16:50:36 +08:00
parent f4f0fa9828
commit 231d80b464
22 changed files with 974 additions and 799 deletions

View file

@ -1,6 +1,9 @@
use std::borrow::Cow; 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}; use crate::parsers::{line, take_lines_while};
@ -160,12 +163,20 @@ impl SourceBlock<'_> {
// TODO: fn retain_labels() -> bool { } // 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, name) = preceded(tag_no_case("#+BEGIN_"), alpha1)(input)?;
let (input, args) = line(input)?; let (input, args) = line(input)?;
let end_line = format!(r"#+END_{}", name); let end_line = format!("#+END_{}", name);
let (input, contents) = 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)?; let (input, _) = line(input)?;
Ok(( Ok((
@ -184,22 +195,24 @@ pub(crate) fn parse_block_element(input: &str) -> IResult<&str, (&str, Option<&s
#[test] #[test]
fn parse() { fn parse() {
use nom::error::VerboseError;
assert_eq!( assert_eq!(
parse_block_element( parse_block_element_internal::<VerboseError<&str>>(
r#"#+BEGIN_SRC r#"#+BEGIN_SRC
#+END_SRC"# #+END_SRC"#
), ),
Ok(("", ("SRC".into(), None, ""))) Ok(("", ("SRC".into(), None, "")))
); );
assert_eq!( assert_eq!(
parse_block_element( parse_block_element_internal::<VerboseError<&str>>(
r#"#+begin_src r#"#+begin_src
#+end_src"# #+end_src"#
), ),
Ok(("", ("src".into(), None, ""))) Ok(("", ("src".into(), None, "")))
); );
assert_eq!( assert_eq!(
parse_block_element( parse_block_element_internal::<VerboseError<&str>>(
r#"#+BEGIN_SRC javascript r#"#+BEGIN_SRC javascript
console.log('Hello World!'); console.log('Hello World!');
#+END_SRC #+END_SRC

View file

@ -4,11 +4,13 @@ use nom::{
bytes::complete::tag, bytes::complete::tag,
character::complete::{char, digit1, space0}, character::complete::{char, digit1, space0},
combinator::recognize, combinator::recognize,
error::ParseError,
sequence::separated_pair, sequence::separated_pair,
IResult, IResult,
}; };
use crate::elements::{Datetime, Timestamp}; use crate::elements::timestamp::{parse_inactive, Datetime, Timestamp};
use crate::parsers::eol; use crate::parsers::eol;
/// Clock Element /// Clock Element
@ -42,54 +44,8 @@ pub enum Clock<'a> {
} }
impl Clock<'_> { impl Clock<'_> {
pub(crate) fn parse(input: &str) -> IResult<&str, Clock<'_>> { pub(crate) fn parse(input: &str) -> Option<(&str, Clock<'_>)> {
let (input, _) = tag("CLOCK:")(input)?; parse_clock::<()>(input).ok()
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 fn into_onwed(self) -> Clock<'static> { 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] #[test]
fn parse() { fn parse() {
use nom::error::VerboseError;
assert_eq!( assert_eq!(
Clock::parse("CLOCK: [2003-09-16 Tue 09:39]"), parse_clock::<VerboseError<&str>>("CLOCK: [2003-09-16 Tue 09:39]"),
Ok(( Ok((
"", "",
Clock::Running { Clock::Running {
@ -192,7 +199,9 @@ fn parse() {
)) ))
); );
assert_eq!( assert_eq!(
Clock::parse("CLOCK: [2003-09-16 Tue 09:39]--[2003-09-16 Tue 10:39] => 1:00"), parse_clock::<VerboseError<&str>>(
"CLOCK: [2003-09-16 Tue 09:39]--[2003-09-16 Tue 10:39] => 1:00"
),
Ok(( Ok((
"", "",
Clock::Closed { Clock::Closed {

View file

@ -5,6 +5,7 @@ use nom::{
bytes::complete::tag, bytes::complete::tag,
character::complete::digit0, character::complete::digit0,
combinator::recognize, combinator::recognize,
error::ParseError,
sequence::{delimited, pair, separated_pair}, sequence::{delimited, pair, separated_pair},
IResult, IResult,
}; };
@ -19,23 +20,8 @@ pub struct Cookie<'a> {
} }
impl Cookie<'_> { impl Cookie<'_> {
#[inline] pub(crate) fn parse(input: &str) -> Option<(&str, Cookie<'_>)> {
pub(crate) fn parse(input: &str) -> IResult<&str, Cookie<'_>> { parse_cookie::<()>(input).ok()
let (input, value) = recognize(delimited(
tag("["),
alt((
separated_pair(digit0, tag("/"), digit0),
pair(digit0, tag("%")),
)),
tag("]"),
))(input)?;
Ok((
input,
Cookie {
value: value.into(),
},
))
} }
pub fn into_owned(self) -> Cookie<'static> { 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] #[test]
fn parse() { fn parse() {
use nom::error::VerboseError;
assert_eq!( assert_eq!(
Cookie::parse("[1/10]"), parse_cookie::<VerboseError<&str>>("[1/10]"),
Ok(( Ok((
"", "",
Cookie { Cookie {
@ -57,7 +64,7 @@ fn parse() {
)) ))
); );
assert_eq!( assert_eq!(
Cookie::parse("[1/1000]"), parse_cookie::<VerboseError<&str>>("[1/1000]"),
Ok(( Ok((
"", "",
Cookie { Cookie {
@ -66,7 +73,7 @@ fn parse() {
)) ))
); );
assert_eq!( assert_eq!(
Cookie::parse("[10%]"), parse_cookie::<VerboseError<&str>>("[10%]"),
Ok(( Ok((
"", "",
Cookie { Cookie {
@ -75,7 +82,7 @@ fn parse() {
)) ))
); );
assert_eq!( assert_eq!(
Cookie::parse("[%]"), parse_cookie::<VerboseError<&str>>("[%]"),
Ok(( Ok((
"", "",
Cookie { Cookie {
@ -84,7 +91,7 @@ fn parse() {
)) ))
); );
assert_eq!( assert_eq!(
Cookie::parse("[/]"), parse_cookie::<VerboseError<&str>>("[/]"),
Ok(( Ok((
"", "",
Cookie { Cookie {
@ -93,7 +100,7 @@ fn parse() {
)) ))
); );
assert_eq!( assert_eq!(
Cookie::parse("[100/]"), parse_cookie::<VerboseError<&str>>("[100/]"),
Ok(( Ok((
"", "",
Cookie { Cookie {
@ -102,7 +109,7 @@ fn parse() {
)) ))
); );
assert_eq!( assert_eq!(
Cookie::parse("[/100]"), parse_cookie::<VerboseError<&str>>("[/100]"),
Ok(( Ok((
"", "",
Cookie { Cookie {
@ -111,8 +118,8 @@ fn parse() {
)) ))
); );
assert!(Cookie::parse("[10% ]").is_err()); assert!(parse_cookie::<VerboseError<&str>>("[10% ]").is_err());
assert!(Cookie::parse("[1//100]").is_err()); assert!(parse_cookie::<VerboseError<&str>>("[1//100]").is_err());
assert!(Cookie::parse("[1\\100]").is_err()); assert!(parse_cookie::<VerboseError<&str>>("[1\\100]").is_err());
assert!(Cookie::parse("[10%%]").is_err()); assert!(parse_cookie::<VerboseError<&str>>("[10%%]").is_err());
} }

View file

@ -2,6 +2,7 @@ use std::borrow::Cow;
use nom::{ use nom::{
bytes::complete::{tag, take_while1}, bytes::complete::{tag, take_while1},
error::ParseError,
sequence::delimited, sequence::delimited,
IResult, IResult,
}; };
@ -18,19 +19,8 @@ pub struct Drawer<'a> {
} }
impl Drawer<'_> { impl Drawer<'_> {
#[inline] pub(crate) fn parse(input: &str) -> Option<(&str, (Drawer<'_>, &str))> {
pub(crate) fn parse(input: &str) -> IResult<&str, (Drawer<'_>, &str)> { parse_drawer::<()>(input).ok()
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 fn into_owned(self) -> Drawer<'static> { 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] #[test]
fn parse() { fn parse() {
use nom::error::VerboseError;
assert_eq!( assert_eq!(
Drawer::parse(":PROPERTIES:\n :CUSTOM_ID: id\n :END:"), parse_drawer::<VerboseError<&str>>(":PROPERTIES:\n :CUSTOM_ID: id\n :END:"),
Ok(( Ok((
"", "",
( (

View file

@ -3,6 +3,7 @@ use std::borrow::Cow;
use nom::{ use nom::{
bytes::complete::tag_no_case, bytes::complete::tag_no_case,
character::complete::{alpha1, space1}, character::complete::{alpha1, space1},
error::ParseError,
IResult, IResult,
}; };
@ -21,30 +22,8 @@ pub struct DynBlock<'a> {
} }
impl DynBlock<'_> { impl DynBlock<'_> {
#[inline] pub(crate) fn parse(input: &str) -> Option<(&str, (DynBlock<'_>, &str))> {
pub(crate) fn parse(input: &str) -> IResult<&str, (DynBlock<'_>, &str)> { parse_dyn_block::<()>(input).ok()
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 fn into_owned(self) -> DynBlock<'static> { 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] #[test]
fn parse() { fn parse() {
use nom::error::VerboseError;
// TODO: testing // TODO: testing
assert_eq!( assert_eq!(
DynBlock::parse( parse_dyn_block::<VerboseError<&str>>(
r#"#+BEGIN: clocktable :scope file r#"#+BEGIN: clocktable :scope file
CONTENTS CONTENTS
#+END: #+END:

View file

@ -2,6 +2,7 @@ use std::borrow::Cow;
use nom::{ use nom::{
bytes::complete::{tag, take_while1}, bytes::complete::{tag, take_while1},
error::ParseError,
sequence::delimited, sequence::delimited,
IResult, IResult,
}; };
@ -18,24 +19,8 @@ pub struct FnDef<'a> {
} }
impl FnDef<'_> { impl FnDef<'_> {
#[inline] pub(crate) fn parse(input: &str) -> Option<(&str, (FnDef<'_>, &str))> {
pub(crate) fn parse(input: &str) -> IResult<&str, (FnDef<'_>, &str)> { parse_fn_def::<()>(input).ok()
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 fn into_owned(self) -> FnDef<'static> { 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] #[test]
fn parse() { fn parse() {
use nom::error::VerboseError;
assert_eq!( assert_eq!(
FnDef::parse("[fn:1] https://orgmode.org"), parse_fn_def::<VerboseError<&str>>("[fn:1] https://orgmode.org"),
Ok(("", (FnDef { label: "1".into() }, " https://orgmode.org"))) Ok(("", (FnDef { label: "1".into() }, " https://orgmode.org")))
); );
assert_eq!( assert_eq!(
FnDef::parse("[fn:word_1] https://orgmode.org"), parse_fn_def::<VerboseError<&str>>("[fn:word_1] https://orgmode.org"),
Ok(( Ok((
"", "",
( (
@ -64,7 +73,7 @@ fn parse() {
)) ))
); );
assert_eq!( assert_eq!(
FnDef::parse("[fn:WORD-1] https://orgmode.org"), parse_fn_def::<VerboseError<&str>>("[fn:WORD-1] https://orgmode.org"),
Ok(( Ok((
"", "",
( (
@ -76,7 +85,7 @@ fn parse() {
)) ))
); );
assert_eq!( assert_eq!(
FnDef::parse("[fn:WORD]"), parse_fn_def::<VerboseError<&str>>("[fn:WORD]"),
Ok(( Ok((
"", "",
( (
@ -88,7 +97,7 @@ fn parse() {
)) ))
); );
assert!(FnDef::parse("[fn:] https://orgmode.org").is_err()); assert!(parse_fn_def::<VerboseError<&str>>("[fn:] https://orgmode.org").is_err());
assert!(FnDef::parse("[fn:wor d] https://orgmode.org").is_err()); assert!(parse_fn_def::<VerboseError<&str>>("[fn:wor d] https://orgmode.org").is_err());
assert!(FnDef::parse("[fn:WORD https://orgmode.org").is_err()); assert!(parse_fn_def::<VerboseError<&str>>("[fn:WORD https://orgmode.org").is_err());
} }

View file

@ -4,8 +4,7 @@ use memchr::memchr2_iter;
use nom::{ use nom::{
bytes::complete::{tag, take_while}, bytes::complete::{tag, take_while},
combinator::opt, combinator::opt,
error::ErrorKind, error::{ErrorKind, ParseError},
error_position,
sequence::preceded, sequence::preceded,
Err, IResult, Err, IResult,
}; };
@ -21,36 +20,9 @@ pub struct FnRef<'a> {
pub definition: Option<Cow<'a, str>>, pub definition: Option<Cow<'a, str>>,
} }
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<'_> { impl FnRef<'_> {
#[inline] pub(crate) fn parse(input: &str) -> Option<(&str, FnRef<'_>)> {
pub(crate) fn parse(input: &str) -> IResult<&str, FnRef<'_>> { parse_fn_ref::<()>(input).ok()
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 fn into_owned(self) -> FnRef<'static> { 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] #[test]
fn parse() { fn parse() {
use nom::error::VerboseError;
assert_eq!( assert_eq!(
FnRef::parse("[fn:1]"), parse_fn_ref::<VerboseError<&str>>("[fn:1]"),
Ok(( Ok((
"", "",
FnRef { FnRef {
@ -74,7 +79,7 @@ fn parse() {
)) ))
); );
assert_eq!( assert_eq!(
FnRef::parse("[fn:1:2]"), parse_fn_ref::<VerboseError<&str>>("[fn:1:2]"),
Ok(( Ok((
"", "",
FnRef { FnRef {
@ -84,7 +89,7 @@ fn parse() {
)) ))
); );
assert_eq!( assert_eq!(
FnRef::parse("[fn::2]"), parse_fn_ref::<VerboseError<&str>>("[fn::2]"),
Ok(( Ok((
"", "",
FnRef { FnRef {
@ -94,7 +99,7 @@ fn parse() {
)) ))
); );
assert_eq!( assert_eq!(
FnRef::parse("[fn::[]]"), parse_fn_ref::<VerboseError<&str>>("[fn::[]]"),
Ok(( Ok((
"", "",
FnRef { FnRef {
@ -104,5 +109,5 @@ fn parse() {
)) ))
); );
assert!(FnRef::parse("[fn::[]").is_err()); assert!(parse_fn_ref::<VerboseError<&str>>("[fn::[]").is_err());
} }

View file

@ -3,6 +3,7 @@ use std::borrow::Cow;
use nom::{ use nom::{
bytes::complete::{tag, take_till}, bytes::complete::{tag, take_till},
combinator::opt, combinator::opt,
error::ParseError,
sequence::{delimited, preceded}, sequence::{delimited, preceded},
IResult, IResult,
}; };
@ -25,34 +26,8 @@ pub struct InlineCall<'a> {
} }
impl InlineCall<'_> { impl InlineCall<'_> {
#[inline] pub(crate) fn parse(input: &str) -> Option<(&str, InlineCall<'_>)> {
pub(crate) fn parse(input: &str) -> IResult<&str, InlineCall<'_>> { parse_inline_call::<()>(input).ok()
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 fn into_owned(self) -> InlineCall<'static> { 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] #[test]
fn parse() { fn parse() {
use nom::error::VerboseError;
assert_eq!( assert_eq!(
InlineCall::parse("call_square(4)"), parse_inline_call::<VerboseError<&str>>("call_square(4)"),
Ok(( Ok((
"", "",
InlineCall { InlineCall {
@ -80,7 +89,7 @@ fn parse() {
)) ))
); );
assert_eq!( assert_eq!(
InlineCall::parse("call_square[:results output](4)"), parse_inline_call::<VerboseError<&str>>("call_square[:results output](4)"),
Ok(( Ok((
"", "",
InlineCall { InlineCall {
@ -92,7 +101,7 @@ fn parse() {
)) ))
); );
assert_eq!( assert_eq!(
InlineCall::parse("call_square(4)[:results html]"), parse_inline_call::<VerboseError<&str>>("call_square(4)[:results html]"),
Ok(( Ok((
"", "",
InlineCall { InlineCall {
@ -104,7 +113,7 @@ fn parse() {
)) ))
); );
assert_eq!( assert_eq!(
InlineCall::parse("call_square[:results output](4)[:results html]"), parse_inline_call::<VerboseError<&str>>("call_square[:results output](4)[:results html]"),
Ok(( Ok((
"", "",
InlineCall { InlineCall {

View file

@ -3,6 +3,7 @@ use std::borrow::Cow;
use nom::{ use nom::{
bytes::complete::{tag, take_till, take_while1}, bytes::complete::{tag, take_till, take_while1},
combinator::opt, combinator::opt,
error::ParseError,
sequence::delimited, sequence::delimited,
IResult, IResult,
}; };
@ -22,27 +23,8 @@ pub struct InlineSrc<'a> {
} }
impl InlineSrc<'_> { impl InlineSrc<'_> {
#[inline] pub(crate) fn parse(input: &str) -> Option<(&str, InlineSrc<'_>)> {
pub(crate) fn parse(input: &str) -> IResult<&str, InlineSrc<'_>> { parse_inline_src::<()>(input).ok()
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 fn into_owned(self) -> InlineSrc<'static> { 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] #[test]
fn parse() { fn parse() {
use nom::error::VerboseError;
assert_eq!( assert_eq!(
InlineSrc::parse("src_C{int a = 0;}"), parse_inline_src::<VerboseError<&str>>("src_C{int a = 0;}"),
Ok(( Ok((
"", "",
InlineSrc { InlineSrc {
@ -68,7 +76,7 @@ fn parse() {
)) ))
); );
assert_eq!( assert_eq!(
InlineSrc::parse("src_xml[:exports code]{<tag>text</tag>}"), parse_inline_src::<VerboseError<&str>>("src_xml[:exports code]{<tag>text</tag>}"),
Ok(( Ok((
"", "",
InlineSrc { InlineSrc {
@ -79,7 +87,11 @@ fn parse() {
)) ))
); );
assert!(InlineSrc::parse("src_xml[:exports code]{<tag>text</tag>").is_err()); assert!(
assert!(InlineSrc::parse("src_[:exports code]{<tag>text</tag>}").is_err()); parse_inline_src::<VerboseError<&str>>("src_xml[:exports code]{<tag>text</tag>").is_err()
assert!(InlineSrc::parse("src_xml[:exports code]").is_err()); );
assert!(
parse_inline_src::<VerboseError<&str>>("src_[:exports code]{<tag>text</tag>}").is_err()
);
assert!(parse_inline_src::<VerboseError<&str>>("src_xml[:exports code]").is_err());
} }

View file

@ -3,6 +3,7 @@ use std::borrow::Cow;
use nom::{ use nom::{
bytes::complete::{tag, take_till}, bytes::complete::{tag, take_till},
combinator::opt, combinator::opt,
error::ParseError,
sequence::delimited, sequence::delimited,
IResult, 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, _) = tag("#+")(input)?;
let (input, key) = take_till(|c: char| c.is_ascii_whitespace() || c == ':' || c == '[')(input)?; let (input, key) = take_till(|c: char| c.is_ascii_whitespace() || c == ':' || c == '[')(input)?;
let (input, optional) = opt(delimited( let (input, optional) = opt(delimited(
@ -64,36 +73,44 @@ pub(crate) fn parse_keyword(input: &str) -> IResult<&str, (&str, Option<&str>, &
#[test] #[test]
fn parse() { fn parse() {
assert_eq!(parse_keyword("#+KEY:"), Ok(("", ("KEY", None, "")))); use nom::error::VerboseError;
assert_eq!( assert_eq!(
parse_keyword("#+KEY: VALUE"), parse_keyword_internal::<VerboseError<&str>>("#+KEY:"),
Ok(("", ("KEY", None, "")))
);
assert_eq!(
parse_keyword_internal::<VerboseError<&str>>("#+KEY: VALUE"),
Ok(("", ("KEY", None, "VALUE"))) Ok(("", ("KEY", None, "VALUE")))
); );
assert_eq!( assert_eq!(
parse_keyword("#+K_E_Y: VALUE"), parse_keyword_internal::<VerboseError<&str>>("#+K_E_Y: VALUE"),
Ok(("", ("K_E_Y", None, "VALUE"))) Ok(("", ("K_E_Y", None, "VALUE")))
); );
assert_eq!( assert_eq!(
parse_keyword("#+KEY:VALUE\n"), parse_keyword_internal::<VerboseError<&str>>("#+KEY:VALUE\n"),
Ok(("", ("KEY", None, "VALUE"))) Ok(("", ("KEY", None, "VALUE")))
); );
assert!(parse_keyword("#+KE Y: VALUE").is_err()); assert!(parse_keyword_internal::<VerboseError<&str>>("#+KE Y: VALUE").is_err());
assert!(parse_keyword("#+ KEY: VALUE").is_err()); assert!(parse_keyword_internal::<VerboseError<&str>>("#+ KEY: VALUE").is_err());
assert_eq!(parse_keyword("#+RESULTS:"), Ok(("", ("RESULTS", None, ""))));
assert_eq!( assert_eq!(
parse_keyword("#+ATTR_LATEX: :width 5cm\n"), parse_keyword_internal::<VerboseError<&str>>("#+RESULTS:"),
Ok(("", ("RESULTS", None, "")))
);
assert_eq!(
parse_keyword_internal::<VerboseError<&str>>("#+ATTR_LATEX: :width 5cm\n"),
Ok(("", ("ATTR_LATEX", None, ":width 5cm"))) Ok(("", ("ATTR_LATEX", None, ":width 5cm")))
); );
assert_eq!( assert_eq!(
parse_keyword("#+CALL: double(n=4)"), parse_keyword_internal::<VerboseError<&str>>("#+CALL: double(n=4)"),
Ok(("", ("CALL", None, "double(n=4)"))) Ok(("", ("CALL", None, "double(n=4)")))
); );
assert_eq!( assert_eq!(
parse_keyword("#+CAPTION[Short caption]: Longer caption."), parse_keyword_internal::<VerboseError<&str>>("#+CAPTION[Short caption]: Longer caption."),
Ok(("", ("CAPTION", Some("Short caption"), "Longer caption.",))) Ok(("", ("CAPTION", Some("Short caption"), "Longer caption.",)))
); );
} }

View file

@ -3,6 +3,7 @@ use std::borrow::Cow;
use nom::{ use nom::{
bytes::complete::{tag, take_while}, bytes::complete::{tag, take_while},
combinator::opt, combinator::opt,
error::ParseError,
sequence::delimited, sequence::delimited,
IResult, IResult,
}; };
@ -20,25 +21,8 @@ pub struct Link<'a> {
impl Link<'_> { impl Link<'_> {
#[inline] #[inline]
pub(crate) fn parse(input: &str) -> IResult<&str, Link<'_>> { pub(crate) fn parse(input: &str) -> Option<(&str, Link<'_>)> {
let (input, path) = delimited( parse_link::<()>(input).ok()
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 fn into_owned(self) -> Link<'static> { 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] #[test]
fn parse() { fn parse() {
use nom::error::VerboseError;
assert_eq!( assert_eq!(
Link::parse("[[#id]]"), parse_link::<VerboseError<&str>>("[[#id]]"),
Ok(( Ok((
"", "",
Link { Link {
@ -62,7 +70,7 @@ fn parse() {
)) ))
); );
assert_eq!( assert_eq!(
Link::parse("[[#id][desc]]"), parse_link::<VerboseError<&str>>("[[#id][desc]]"),
Ok(( Ok((
"", "",
Link { Link {
@ -71,5 +79,5 @@ fn parse() {
} }
)) ))
); );
assert!(Link::parse("[[#id][desc]").is_err()); assert!(parse_link::<VerboseError<&str>>("[[#id][desc]").is_err());
} }

View file

@ -3,6 +3,7 @@ use std::borrow::Cow;
use nom::{ use nom::{
bytes::complete::{tag, take, take_until, take_while1}, bytes::complete::{tag, take, take_until, take_while1},
combinator::{opt, verify}, combinator::{opt, verify},
error::ParseError,
sequence::delimited, sequence::delimited,
IResult, IResult,
}; };
@ -20,23 +21,8 @@ pub struct Macros<'a> {
} }
impl Macros<'_> { impl Macros<'_> {
#[inline] pub(crate) fn parse(input: &str) -> Option<(&str, Macros<'_>)> {
pub(crate) fn parse(input: &str) -> IResult<&str, Macros<'_>> { parse_macros::<()>(input).ok()
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 fn into_owned(self) -> Macros<'static> { 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] #[test]
fn parse() { fn parse() {
use nom::error::VerboseError;
assert_eq!( assert_eq!(
Macros::parse("{{{poem(red,blue)}}}"), parse_macros::<VerboseError<&str>>("{{{poem(red,blue)}}}"),
Ok(( Ok((
"", "",
Macros { Macros {
@ -60,7 +67,7 @@ fn parse() {
)) ))
); );
assert_eq!( assert_eq!(
Macros::parse("{{{poem())}}}"), parse_macros::<VerboseError<&str>>("{{{poem())}}}"),
Ok(( Ok((
"", "",
Macros { Macros {
@ -70,7 +77,7 @@ fn parse() {
)) ))
); );
assert_eq!( assert_eq!(
Macros::parse("{{{author}}}"), parse_macros::<VerboseError<&str>>("{{{author}}}"),
Ok(( Ok((
"", "",
Macros { Macros {
@ -79,8 +86,8 @@ fn parse() {
} }
)) ))
); );
assert!(Macros::parse("{{{0uthor}}}").is_err()); assert!(parse_macros::<VerboseError<&str>>("{{{0uthor}}}").is_err());
assert!(Macros::parse("{{{author}}").is_err()); assert!(parse_macros::<VerboseError<&str>>("{{{author}}").is_err());
assert!(Macros::parse("{{{poem(}}}").is_err()); assert!(parse_macros::<VerboseError<&str>>("{{{poem(}}}").is_err());
assert!(Macros::parse("{{{poem)}}}").is_err()); assert!(parse_macros::<VerboseError<&str>>("{{{poem)}}}").is_err());
} }

View file

@ -1,32 +1,27 @@
//! Org-mode elements //! Org-mode elements
mod block; pub(crate) mod block;
mod clock; pub(crate) mod clock;
mod cookie; pub(crate) mod cookie;
mod drawer; pub(crate) mod drawer;
mod dyn_block; pub(crate) mod dyn_block;
mod emphasis; pub(crate) mod emphasis;
mod fn_def; pub(crate) mod fn_def;
mod fn_ref; pub(crate) mod fn_ref;
mod inline_call; pub(crate) mod inline_call;
mod inline_src; pub(crate) mod inline_src;
mod keyword; pub(crate) mod keyword;
mod link; pub(crate) mod link;
mod list; pub(crate) mod list;
mod macros; pub(crate) mod macros;
mod planning; pub(crate) mod planning;
mod radio_target; pub(crate) mod radio_target;
mod rule; pub(crate) mod rule;
mod snippet; pub(crate) mod snippet;
mod table; pub(crate) mod table;
mod target; pub(crate) mod target;
mod timestamp; pub(crate) mod timestamp;
mod title; pub(crate) 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 use self::{ pub use self::{
block::{ block::{

View file

@ -30,23 +30,18 @@ impl Planning<'_> {
let next = &tail[i + 1..].trim_start(); let next = &tail[i + 1..].trim_start();
macro_rules! set_timestamp { macro_rules! set_timestamp {
($timestamp:expr) => { ($timestamp:expr) => {{
if $timestamp.is_none() { let (new_tail, timestamp) =
let (new_tail, timestamp) = Timestamp::parse_active(next) Timestamp::parse_active(next).or(Timestamp::parse_inactive(next))?;
.or_else(|_| Timestamp::parse_inactive(next)) $timestamp = Some(timestamp);
.ok()?; tail = new_tail.trim_start();
$timestamp = Some(timestamp); }};
tail = new_tail.trim_start();
} else {
return None;
}
};
} }
match &tail[..i] { match &tail[..i] {
"DEADLINE:" => set_timestamp!(deadline), "DEADLINE:" if deadline.is_none() => set_timestamp!(deadline),
"SCHEDULED:" => set_timestamp!(scheduled), "SCHEDULED:" if scheduled.is_none() => set_timestamp!(scheduled),
"CLOSED:" => set_timestamp!(closed), "CLOSED:" if closed.is_none() => set_timestamp!(closed),
_ => return None, _ => return None,
} }
} }

View file

@ -1,6 +1,7 @@
use nom::{ use nom::{
bytes::complete::{tag, take_while}, bytes::complete::{tag, take_while},
combinator::verify, combinator::verify,
error::ParseError,
sequence::delimited, sequence::delimited,
IResult, IResult,
}; };
@ -8,7 +9,14 @@ use nom::{
// TODO: text-markup, entities, latex-fragments, subscript and superscript // TODO: text-markup, entities, latex-fragments, subscript and superscript
#[inline] #[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( let (input, contents) = delimited(
tag("<<<"), tag("<<<"),
verify( verify(
@ -23,12 +31,20 @@ pub(crate) fn parse_radio_target(input: &str) -> IResult<&str, &str> {
#[test] #[test]
fn parse() { fn parse() {
assert_eq!(parse_radio_target("<<<target>>>"), Ok(("", "target"))); use nom::error::VerboseError;
assert_eq!(parse_radio_target("<<<tar get>>>"), Ok(("", "tar get")));
assert!(parse_radio_target("<<<target >>>").is_err()); assert_eq!(
assert!(parse_radio_target("<<< target>>>").is_err()); parse_radio_target_internal::<VerboseError<&str>>("<<<target>>>"),
assert!(parse_radio_target("<<<ta<get>>>").is_err()); Ok(("", "target"))
assert!(parse_radio_target("<<<ta>get>>>").is_err()); );
assert!(parse_radio_target("<<<ta\nget>>>").is_err()); assert_eq!(
assert!(parse_radio_target("<<<target>>").is_err()); parse_radio_target_internal::<VerboseError<&str>>("<<<tar get>>>"),
Ok(("", "tar get"))
);
assert!(parse_radio_target_internal::<VerboseError<&str>>("<<<target >>>").is_err());
assert!(parse_radio_target_internal::<VerboseError<&str>>("<<< target>>>").is_err());
assert!(parse_radio_target_internal::<VerboseError<&str>>("<<<ta<get>>>").is_err());
assert!(parse_radio_target_internal::<VerboseError<&str>>("<<<ta>get>>>").is_err());
assert!(parse_radio_target_internal::<VerboseError<&str>>("<<<ta\nget>>>").is_err());
assert!(parse_radio_target_internal::<VerboseError<&str>>("<<<target>>").is_err());
} }

View file

@ -1,10 +1,16 @@
use std::usize; 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; 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, _) = take_while_m_n(5, usize::MAX, |c| c == '-')(input)?;
let (input, _) = eol(input)?; let (input, _) = eol(input)?;
Ok((input, ())) Ok((input, ()))
@ -12,18 +18,32 @@ pub(crate) fn parse_rule(input: &str) -> IResult<&str, ()> {
#[test] #[test]
fn parse() { fn parse() {
assert_eq!(parse_rule("-----"), Ok(("", ()))); use nom::error::VerboseError;
assert_eq!(parse_rule("--------"), Ok(("", ())));
assert_eq!(parse_rule("-----\n"), Ok(("", ()))); assert_eq!(
assert_eq!(parse_rule("----- \n"), Ok(("", ()))); parse_rule_internal::<VerboseError<&str>>("-----"),
assert!(parse_rule("").is_err()); Ok(("", ()))
assert!(parse_rule("----").is_err()); );
assert!(parse_rule("----").is_err()); assert_eq!(
assert!(parse_rule("None----").is_err()); parse_rule_internal::<VerboseError<&str>>("--------"),
assert!(parse_rule("None ----").is_err()); Ok(("", ()))
assert!(parse_rule("None------").is_err()); );
assert!(parse_rule("----None----").is_err()); assert_eq!(
assert!(parse_rule("\t\t----").is_err()); parse_rule_internal::<VerboseError<&str>>("-----\n"),
assert!(parse_rule("------None").is_err()); Ok(("", ()))
assert!(parse_rule("----- None").is_err()); );
assert_eq!(
parse_rule_internal::<VerboseError<&str>>("----- \n"),
Ok(("", ()))
);
assert!(parse_rule_internal::<VerboseError<&str>>("").is_err());
assert!(parse_rule_internal::<VerboseError<&str>>("----").is_err());
assert!(parse_rule_internal::<VerboseError<&str>>("----").is_err());
assert!(parse_rule_internal::<VerboseError<&str>>("None----").is_err());
assert!(parse_rule_internal::<VerboseError<&str>>("None ----").is_err());
assert!(parse_rule_internal::<VerboseError<&str>>("None------").is_err());
assert!(parse_rule_internal::<VerboseError<&str>>("----None----").is_err());
assert!(parse_rule_internal::<VerboseError<&str>>("\t\t----").is_err());
assert!(parse_rule_internal::<VerboseError<&str>>("------None").is_err());
assert!(parse_rule_internal::<VerboseError<&str>>("----- None").is_err());
} }

View file

@ -2,6 +2,7 @@ use std::borrow::Cow;
use nom::{ use nom::{
bytes::complete::{tag, take, take_until, take_while1}, bytes::complete::{tag, take, take_until, take_while1},
error::ParseError,
sequence::{delimited, separated_pair}, sequence::{delimited, separated_pair},
IResult, IResult,
}; };
@ -18,25 +19,8 @@ pub struct Snippet<'a> {
} }
impl Snippet<'_> { impl Snippet<'_> {
#[inline] pub(crate) fn parse(input: &str) -> Option<(&str, Snippet<'_>)> {
pub(crate) fn parse(input: &str) -> IResult<&str, Snippet<'_>> { parse_snippet::<()>(input).ok()
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 fn into_owned(self) -> Snippet<'static> { 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] #[test]
fn parse() { fn parse() {
use nom::error::VerboseError;
assert_eq!( assert_eq!(
Snippet::parse("@@html:<b>@@"), parse_snippet::<VerboseError<&str>>("@@html:<b>@@"),
Ok(( Ok((
"", "",
Snippet { Snippet {
@ -60,7 +67,7 @@ fn parse() {
)) ))
); );
assert_eq!( assert_eq!(
Snippet::parse("@@latex:any arbitrary LaTeX code@@"), parse_snippet::<VerboseError<&str>>("@@latex:any arbitrary LaTeX code@@"),
Ok(( Ok((
"", "",
Snippet { Snippet {
@ -70,7 +77,7 @@ fn parse() {
)) ))
); );
assert_eq!( assert_eq!(
Snippet::parse("@@html:@@"), parse_snippet::<VerboseError<&str>>("@@html:@@"),
Ok(( Ok((
"", "",
Snippet { Snippet {
@ -80,7 +87,7 @@ fn parse() {
)) ))
); );
assert_eq!( assert_eq!(
Snippet::parse("@@html:<p>@</p>@@"), parse_snippet::<VerboseError<&str>>("@@html:<p>@</p>@@"),
Ok(( Ok((
"", "",
Snippet { Snippet {
@ -89,7 +96,7 @@ fn parse() {
} }
)) ))
); );
assert!(Snippet::parse("@@html:<b>@").is_err()); assert!(parse_snippet::<VerboseError<&str>>("@@html:<b>@").is_err());
assert!(Snippet::parse("@@html<b>@@").is_err()); assert!(parse_snippet::<VerboseError<&str>>("@@html<b>@@").is_err());
assert!(Snippet::parse("@@:<b>@@").is_err()); assert!(parse_snippet::<VerboseError<&str>>("@@:<b>@@").is_err());
} }

View file

@ -2,6 +2,7 @@ use std::borrow::Cow;
use nom::{ use nom::{
combinator::{peek, verify}, combinator::{peek, verify},
error::ParseError,
IResult, 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 (input, _) = peek(verify(line, |s: &str| {
let s = s.trim(); let s = s.trim();
s.starts_with("+-") && s.as_bytes().iter().all(|&c| c == b'+' || c == b'-') s.starts_with("+-") && s.as_bytes().iter().all(|&c| c == b'+' || c == b'-')
}))(input)?; }))(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] #[test]
fn parse_table_el_() { fn parse_table_el_() {
use nom::error::VerboseError;
assert_eq!( assert_eq!(
parse_table_el( parse_table_el_internal::<VerboseError<&str>>(
r#"+---+ r#"+---+
| | | |
+---+ +---+
@ -85,6 +97,6 @@ fn parse_table_el_() {
"# "#
)) ))
); );
assert!(parse_table_el("").is_err()); assert!(parse_table_el_internal::<VerboseError<&str>>("").is_err());
assert!(parse_table_el("+----|---").is_err()); assert!(parse_table_el_internal::<VerboseError<&str>>("+----|---").is_err());
} }

View file

@ -3,6 +3,7 @@ use std::borrow::Cow;
use nom::{ use nom::{
bytes::complete::{tag, take_while}, bytes::complete::{tag, take_while},
combinator::verify, combinator::verify,
error::ParseError,
sequence::delimited, sequence::delimited,
IResult, IResult,
}; };
@ -18,22 +19,8 @@ pub struct Target<'a> {
impl Target<'_> { impl Target<'_> {
#[inline] #[inline]
pub(crate) fn parse(input: &str) -> IResult<&str, Target<'_>> { pub(crate) fn parse(input: &str) -> Option<(&str, Target<'_>)> {
let (input, target) = delimited( parse_target::<()>(input).ok()
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 fn into_owned(self) -> Target<'static> { 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] #[test]
fn parse() { fn parse() {
use nom::error::VerboseError;
assert_eq!( assert_eq!(
Target::parse("<<target>>"), parse_target::<VerboseError<&str>>("<<target>>"),
Ok(( Ok((
"", "",
Target { Target {
@ -55,7 +63,7 @@ fn parse() {
)) ))
); );
assert_eq!( assert_eq!(
Target::parse("<<tar get>>"), parse_target::<VerboseError<&str>>("<<tar get>>"),
Ok(( Ok((
"", "",
Target { Target {
@ -63,10 +71,10 @@ fn parse() {
} }
)) ))
); );
assert!(Target::parse("<<target >>").is_err()); assert!(parse_target::<VerboseError<&str>>("<<target >>").is_err());
assert!(Target::parse("<< target>>").is_err()); assert!(parse_target::<VerboseError<&str>>("<< target>>").is_err());
assert!(Target::parse("<<ta<get>>").is_err()); assert!(parse_target::<VerboseError<&str>>("<<ta<get>>").is_err());
assert!(Target::parse("<<ta>get>>").is_err()); assert!(parse_target::<VerboseError<&str>>("<<ta>get>>").is_err());
assert!(Target::parse("<<ta\nget>>").is_err()); assert!(parse_target::<VerboseError<&str>>("<<ta\nget>>").is_err());
assert!(Target::parse("<<target>").is_err()); assert!(parse_target::<VerboseError<&str>>("<<target>").is_err());
} }

View file

@ -4,6 +4,7 @@ use nom::{
bytes::complete::{tag, take, take_till, take_while, take_while_m_n}, bytes::complete::{tag, take, take_till, take_while, take_while_m_n},
character::complete::{space0, space1}, character::complete::{space0, space1},
combinator::{map, map_res, opt}, combinator::{map, map_res, opt},
error::ParseError,
sequence::preceded, sequence::preceded,
IResult, IResult,
}; };
@ -137,123 +138,16 @@ pub enum Timestamp<'a> {
} }
impl Timestamp<'_> { impl Timestamp<'_> {
pub(crate) fn parse_active(input: &str) -> IResult<&str, Timestamp<'_>> { pub(crate) fn parse_active(input: &str) -> Option<(&str, Timestamp<'_>)> {
let (input, _) = tag("<")(input)?; parse_active::<()>(input).ok()
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_inactive(input: &str) -> IResult<&str, Timestamp<'_>> { pub(crate) fn parse_inactive(input: &str) -> Option<(&str, Timestamp<'_>)> {
let (input, _) = tag("[")(input)?; parse_inactive::<()>(input).ok()
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_diary(input: &str) -> IResult<&str, Timestamp<'_>> { pub(crate) fn parse_diary(input: &str) -> Option<(&str, Timestamp<'_>)> {
let (input, _) = tag("<%%(")(input)?; parse_diary::<()>(input).ok()
let (input, value) = take_till(|c| c == ')' || c == '>' || c == '\n')(input)?;
let (input, _) = tag(")>")(input)?;
Ok((
input,
Timestamp::Diary {
value: value.into(),
},
))
} }
pub fn into_owned(self) -> Timestamp<'static> { 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| { let (input, hour) = map_res(take_while_m_n(1, 2, |c: char| c.is_ascii_digit()), |num| {
u8::from_str_radix(num, 10) u8::from_str_radix(num, 10)
})(input)?; })(input)?;
@ -314,7 +333,7 @@ fn parse_time(input: &str) -> IResult<&str, (u8, u8)> {
Ok((input, (hour, minute))) 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 parse_u8 = |num| u8::from_str_radix(num, 10);
let (input, year) = map_res(take(4usize), |num| u16::from_str_radix(num, 10))(input)?; 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] #[test]
fn parse() { fn parse() {
use nom::error::VerboseError;
assert_eq!( assert_eq!(
Timestamp::parse_inactive("[2003-09-16 Tue]"), parse_inactive::<VerboseError<&str>>("[2003-09-16 Tue]"),
Ok(( Ok((
"", "",
Timestamp::Inactive { Timestamp::Inactive {
@ -416,7 +437,7 @@ fn parse() {
)) ))
); );
assert_eq!( assert_eq!(
Timestamp::parse_inactive("[2003-09-16 Tue 09:39]--[2003-09-16 Tue 10:39]"), parse_inactive::<VerboseError<&str>>("[2003-09-16 Tue 09:39]--[2003-09-16 Tue 10:39]"),
Ok(( Ok((
"", "",
Timestamp::InactiveRange { Timestamp::InactiveRange {
@ -442,7 +463,7 @@ fn parse() {
)) ))
); );
assert_eq!( assert_eq!(
Timestamp::parse_active("<2003-09-16 Tue 09:39-10:39>"), parse_active::<VerboseError<&str>>("<2003-09-16 Tue 09:39-10:39>"),
Ok(( Ok((
"", "",
Timestamp::ActiveRange { Timestamp::ActiveRange {

View file

@ -8,15 +8,14 @@ use nom::{
bytes::complete::{tag, take_until, take_while}, bytes::complete::{tag, take_until, take_while},
character::complete::{anychar, space1}, character::complete::{anychar, space1},
combinator::{map, map_parser, opt, verify}, combinator::{map, map_parser, opt, verify},
error::ErrorKind, error::{ErrorKind, ParseError},
error_position,
multi::fold_many0, multi::fold_many0,
sequence::{delimited, preceded}, sequence::{delimited, preceded},
Err, IResult, Err, IResult,
}; };
use crate::config::ParseConfig; 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}; use crate::parsers::{line, skip_empty_lines, take_one_word};
/// Title Elemenet /// Title Elemenet
@ -46,68 +45,11 @@ pub struct Title<'a> {
} }
impl Title<'_> { impl Title<'_> {
#[inline]
pub(crate) fn parse<'a>( pub(crate) fn parse<'a>(
input: &'a str, input: &'a str,
config: &ParseConfig, config: &ParseConfig,
) -> IResult<&'a str, (Title<'a>, &'a str)> { ) -> Option<(&'a str, (Title<'a>, &'a str))> {
let (input, level) = map(take_while(|c: char| c == '*'), |s: &str| s.len())(input)?; parse_title::<()>(input, config).ok()
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,
),
))
} }
// TODO: fn is_archived(&self) -> bool { } // TODO: fn is_archived(&self) -> bool { }
@ -136,6 +78,11 @@ impl Title<'_> {
.and_then(|planning| planning.deadline.as_ref()) .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> { pub fn into_owned(self) -> Title<'static> {
Title { Title {
level: self.level, level: self.level,
@ -171,10 +118,77 @@ impl Default for Title<'_> {
} }
} }
fn parse_properties_drawer(input: &str) -> IResult<&str, HashMap<Cow<'_, str>, Cow<'_, str>>> { #[inline]
let (input, (drawer, content)) = Drawer::parse(input.trim_start())?; 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>, Cow<'a, str>>, E> {
let (input, (drawer, content)) = parse_drawer(input.trim_start())?;
if drawer.name != "PROPERTIES" { 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( let (_, map) = fold_many0(
parse_node_property, parse_node_property,
@ -187,7 +201,10 @@ fn parse_properties_drawer(input: &str) -> IResult<&str, HashMap<Cow<'_, str>, C
Ok((input, map)) 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 = skip_empty_lines(input).trim_start();
let (input, name) = map(delimited(tag(":"), take_until(":"), tag(":")), |s: &str| { let (input, name) = map(delimited(tag(":"), take_until(":"), tag(":")), |s: &str| {
s.trim_end_matches('+') s.trim_end_matches('+')
@ -196,19 +213,17 @@ fn parse_node_property(input: &str) -> IResult<&str, (&str, &str)> {
Ok((input, (name, value.trim()))) 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] #[test]
fn parse_title() { fn parse_title_() {
use nom::error::VerboseError;
use crate::config::DEFAULT_CONFIG; use crate::config::DEFAULT_CONFIG;
assert_eq!( assert_eq!(
Title::parse("**** DONE [#A] COMMENT Title :tag:a2%:", &DEFAULT_CONFIG), parse_title::<VerboseError<&str>>(
"**** DONE [#A] COMMENT Title :tag:a2%:",
&DEFAULT_CONFIG
),
Ok(( Ok((
"", "",
( (
@ -226,7 +241,7 @@ fn parse_title() {
)) ))
); );
assert_eq!( assert_eq!(
Title::parse("**** ToDO [#A] COMMENT Title", &DEFAULT_CONFIG), parse_title::<VerboseError<&str>>("**** ToDO [#A] COMMENT Title", &DEFAULT_CONFIG),
Ok(( Ok((
"", "",
( (
@ -244,7 +259,7 @@ fn parse_title() {
)) ))
); );
assert_eq!( assert_eq!(
Title::parse("**** T0DO [#A] COMMENT Title", &DEFAULT_CONFIG), parse_title::<VerboseError<&str>>("**** T0DO [#A] COMMENT Title", &DEFAULT_CONFIG),
Ok(( Ok((
"", "",
( (
@ -262,7 +277,7 @@ fn parse_title() {
)) ))
); );
assert_eq!( assert_eq!(
Title::parse("**** DONE [#1] COMMENT Title", &DEFAULT_CONFIG), parse_title::<VerboseError<&str>>("**** DONE [#1] COMMENT Title", &DEFAULT_CONFIG),
Ok(( Ok((
"", "",
( (
@ -280,7 +295,7 @@ fn parse_title() {
)) ))
); );
assert_eq!( assert_eq!(
Title::parse("**** DONE [#a] COMMENT Title", &DEFAULT_CONFIG), parse_title::<VerboseError<&str>>("**** DONE [#a] COMMENT Title", &DEFAULT_CONFIG),
Ok(( Ok((
"", "",
( (
@ -298,7 +313,7 @@ fn parse_title() {
)) ))
); );
assert_eq!( assert_eq!(
Title::parse("**** Title :tag:a2%", &DEFAULT_CONFIG), parse_title::<VerboseError<&str>>("**** Title :tag:a2%", &DEFAULT_CONFIG),
Ok(( Ok((
"", "",
( (
@ -316,7 +331,7 @@ fn parse_title() {
)) ))
); );
assert_eq!( assert_eq!(
Title::parse("**** Title tag:a2%:", &DEFAULT_CONFIG), parse_title::<VerboseError<&str>>("**** Title tag:a2%:", &DEFAULT_CONFIG),
Ok(( Ok((
"", "",
( (
@ -335,7 +350,7 @@ fn parse_title() {
); );
assert_eq!( assert_eq!(
Title::parse( parse_title::<VerboseError<&str>>(
"**** DONE Title", "**** DONE Title",
&ParseConfig { &ParseConfig {
done_keywords: vec![], done_keywords: vec![],
@ -359,7 +374,7 @@ fn parse_title() {
)) ))
); );
assert_eq!( assert_eq!(
Title::parse( parse_title::<VerboseError<&str>>(
"**** TASK [#A] Title", "**** TASK [#A] Title",
&ParseConfig { &ParseConfig {
todo_keywords: vec!["TASK".to_string()], todo_keywords: vec!["TASK".to_string()],
@ -386,8 +401,12 @@ fn parse_title() {
#[test] #[test]
fn parse_properties_drawer_() { fn parse_properties_drawer_() {
use nom::error::VerboseError;
assert_eq!( assert_eq!(
parse_properties_drawer(" :PROPERTIES:\n :CUSTOM_ID: id\n :END:"), parse_properties_drawer::<VerboseError<&str>>(
" :PROPERTIES:\n :CUSTOM_ID: id\n :END:"
),
Ok(( Ok((
"", "",
vec![("CUSTOM_ID".into(), "id".into())] vec![("CUSTOM_ID".into(), "id".into())]
@ -399,14 +418,14 @@ fn parse_properties_drawer_() {
// #[test] // #[test]
// fn is_commented() { // fn is_commented() {
// assert!(Title::parse("* COMMENT Title", &DEFAULT_CONFIG) // assert!(parse_title::<VerboseError<&str>>("* COMMENT Title", &DEFAULT_CONFIG)
// .1 // .1
// .is_commented()); // .is_commented());
// assert!(!Title::parse("* Title", &DEFAULT_CONFIG).1.is_commented()); // assert!(!parse_title::<VerboseError<&str>>("* Title", &DEFAULT_CONFIG).1.is_commented());
// assert!(!Title::parse("* C0MMENT Title", &DEFAULT_CONFIG) // assert!(!parse_title::<VerboseError<&str>>("* C0MMENT Title", &DEFAULT_CONFIG)
// .1 // .1
// .is_commented()); // .is_commented());
// assert!(!Title::parse("* comment Title", &DEFAULT_CONFIG) // assert!(!parse_title::<VerboseError<&str>>("* comment Title", &DEFAULT_CONFIG)
// .1 // .1
// .is_commented()); // .is_commented());
// } // }

View file

@ -7,13 +7,16 @@ use std::marker::PhantomData;
use indextree::{Arena, NodeId}; use indextree::{Arena, NodeId};
use jetscii::{bytes, BytesConst}; use jetscii::{bytes, BytesConst};
use memchr::{memchr, memchr_iter}; use memchr::{memchr, memchr_iter};
use nom::{ use nom::{bytes::complete::take_while1, combinator::verify, error::ParseError, IResult};
bytes::complete::take_while1, combinator::verify, error::ErrorKind, error_position, Err,
IResult,
};
use crate::config::ParseConfig; 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> { pub trait ElementArena<'a> {
fn append_element<T: Into<Element<'a>>>(&mut self, element: T, parent: NodeId) -> NodeId; fn append_element<T: Into<Element<'a>>>(&mut self, element: T, parent: NodeId) -> NodeId;
@ -72,13 +75,8 @@ impl<'a> ElementArena<'a> for OwnedArena<'a, '_, '_> {
element: T, element: T,
parent: NodeId, parent: NodeId,
) -> NodeId { ) -> NodeId {
if let Some(child) = self.arena[parent].last_child() { self.arena
let node = self.arena.new_node(element.into().into_owned()); .insert_before_last_child(element.into().into_owned(), parent)
child.insert_before(node, self.arena);
node
} else {
self.append_element(element, parent)
}
} }
} }
@ -170,7 +168,7 @@ pub fn parse_section_and_headlines<'a, T: ElementArena<'a>>(
let mut last_end = 0; let mut last_end = 0;
for i in memchr_iter(b'\n', content.as_bytes()).chain(once(content.len())) { 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 { if last_end != 0 {
let node = arena.append_element(Element::Section, parent); let node = arena.append_element(Element::Section, parent);
let content = &content[0..last_end]; let content = &content[0..last_end];
@ -183,7 +181,7 @@ pub fn parse_section_and_headlines<'a, T: ElementArena<'a>>(
node, 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); debug_assert_ne!(tail, new_tail);
let node = arena.append_element(Element::Headline { level }, parent); let node = arena.append_element(Element::Headline { level }, parent);
containers.push(Container::Headline { content, node }); containers.push(Container::Headline { content, node });
@ -266,7 +264,7 @@ pub fn parse_block<'a, T: ElementArena<'a>>(
parent: NodeId, parent: NodeId,
containers: &mut Vec<Container<'a>>, containers: &mut Vec<Container<'a>>,
) -> Option<&'a str> { ) -> 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); let node = arena.append_element(fn_def, parent);
containers.push(Container::Block { content, node }); containers.push(Container::Block { content, node });
return Some(tail); return Some(tail);
@ -285,42 +283,37 @@ pub fn parse_block<'a, T: ElementArena<'a>>(
match contents.as_bytes().get(0)? { match contents.as_bytes().get(0)? {
b'C' => { b'C' => {
if let Ok((tail, clock)) = Clock::parse(contents) { let (tail, clock) = Clock::parse(contents)?;
arena.append_element(clock, parent); arena.append_element(clock, parent);
return Some(tail); Some(tail)
}
} }
b'\'' => { b'\'' => {
// TODO: LaTeX environment // TODO: LaTeX environment
None
} }
b'-' => { b'-' => {
if let Ok((tail, _)) = parse_rule(contents) { let tail = parse_rule(contents)?;
arena.append_element(Element::Rule, parent); arena.append_element(Element::Rule, parent);
return Some(tail); Some(tail)
}
} }
b':' => { 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); let node = arena.append_element(drawer, parent);
containers.push(Container::Block { content, node }); containers.push(Container::Block { content, node });
return Some(tail); Some(tail)
} else if let Ok((tail, value)) = parse_fixed_width(contents) { } else {
arena.append_element( let (tail, value) = parse_fixed_width(contents)?;
Element::FixedWidth { let value = value.into();
value: value.into(), arena.append_element(Element::FixedWidth { value }, parent);
}, Some(tail)
parent,
);
return Some(tail);
} }
} }
b'|' => { b'|' => {
if let Some(tail) = parse_table(arena, contents, containers, parent) { let tail = parse_table(arena, contents, containers, parent)?;
return Some(tail); Some(tail)
}
} }
b'#' => { 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( match_block(
arena, arena,
parent, parent,
@ -329,12 +322,12 @@ pub fn parse_block<'a, T: ElementArena<'a>>(
args.map(Into::into), args.map(Into::into),
content, content,
); );
return Some(tail); Some(tail)
} else if let Ok((tail, (dyn_block, content))) = DynBlock::parse(contents) { } else if let Some((tail, (dyn_block, content))) = DynBlock::parse(contents) {
let node = arena.append_element(dyn_block, parent); let node = arena.append_element(dyn_block, parent);
containers.push(Container::Block { content, node }); containers.push(Container::Block { content, node });
return Some(tail); Some(tail)
} else if let Ok((tail, (key, optional, value))) = parse_keyword(contents) { } else if let Some((tail, (key, optional, value))) = parse_keyword(contents) {
if (&*key).eq_ignore_ascii_case("CALL") { if (&*key).eq_ignore_ascii_case("CALL") {
arena.append_element( arena.append_element(
BabelCall { BabelCall {
@ -352,21 +345,16 @@ pub fn parse_block<'a, T: ElementArena<'a>>(
parent, parent,
); );
} }
return Some(tail); Some(tail)
} else if let Ok((tail, value)) = parse_comment(contents) { } else {
arena.append_element( let (tail, value) = parse_comment(contents)?;
Element::Comment { let value = value.into();
value: value.into(), arena.append_element(Element::Comment { value }, parent);
}, Some(tail)
parent,
);
return Some(tail);
} }
} }
_ => (), _ => None,
} }
None
} }
pub fn match_block<'a, T: ElementArena<'a>>( pub fn match_block<'a, T: ElementArena<'a>>(
@ -542,116 +530,97 @@ pub fn parse_inline<'a, T: ElementArena<'a>>(
return None; return None;
} }
let bytes = contents.as_bytes(); match contents.as_bytes()[0] {
match bytes[0] {
b'@' => { b'@' => {
if let Ok((tail, snippet)) = Snippet::parse(contents) { let (tail, snippet) = Snippet::parse(contents)?;
arena.append_element(snippet, parent); arena.append_element(snippet, parent);
return Some(tail); Some(tail)
}
} }
b'{' => { b'{' => {
if let Ok((tail, macros)) = Macros::parse(contents) { let (tail, macros) = Macros::parse(contents)?;
arena.append_element(macros, parent); arena.append_element(macros, parent);
return Some(tail); Some(tail)
}
} }
b'<' => { 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); arena.append_element(Element::RadioTarget, parent);
return Some(tail); Some(tail)
} else if let Ok((tail, target)) = Target::parse(contents) { } else if let Some((tail, target)) = Target::parse(contents) {
arena.append_element(target, parent); arena.append_element(target, parent);
return Some(tail); Some(tail)
} else if let Ok((tail, timestamp)) = Timestamp::parse_active(contents) { } else if let Some((tail, timestamp)) = Timestamp::parse_active(contents) {
arena.append_element(timestamp, parent); arena.append_element(timestamp, parent);
return Some(tail); Some(tail)
} else if let Ok((tail, timestamp)) = Timestamp::parse_diary(contents) { } else {
let (tail, timestamp) = Timestamp::parse_diary(contents)?;
arena.append_element(timestamp, parent); arena.append_element(timestamp, parent);
return Some(tail); Some(tail)
} }
} }
b'[' => { 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); arena.append_element(fn_ref, parent);
return Some(tail); Some(tail)
} else if let Ok((tail, link)) = Link::parse(contents) { } else if let Some((tail, link)) = Link::parse(contents) {
arena.append_element(link, parent); arena.append_element(link, parent);
return Some(tail); Some(tail)
} else if let Ok((tail, cookie)) = Cookie::parse(contents) { } else if let Some((tail, cookie)) = Cookie::parse(contents) {
arena.append_element(cookie, parent); arena.append_element(cookie, parent);
return Some(tail); Some(tail)
} else if let Ok((tail, timestamp)) = Timestamp::parse_inactive(contents) { } else {
let (tail, timestamp) = Timestamp::parse_inactive(contents)?;
arena.append_element(timestamp, parent); arena.append_element(timestamp, parent);
return Some(tail); Some(tail)
} }
} }
b'*' => { b'*' => {
if let Some((tail, content)) = parse_emphasis(contents, b'*') { let (tail, content) = parse_emphasis(contents, b'*')?;
let node = arena.append_element(Element::Bold, parent); let node = arena.append_element(Element::Bold, parent);
containers.push(Container::Inline { content, node }); containers.push(Container::Inline { content, node });
return Some(tail); Some(tail)
}
} }
b'+' => { b'+' => {
if let Some((tail, content)) = parse_emphasis(contents, b'+') { let (tail, content) = parse_emphasis(contents, b'+')?;
let node = arena.append_element(Element::Strike, parent); let node = arena.append_element(Element::Strike, parent);
containers.push(Container::Inline { content, node }); containers.push(Container::Inline { content, node });
return Some(tail); Some(tail)
}
} }
b'/' => { b'/' => {
if let Some((tail, content)) = parse_emphasis(contents, b'/') { let (tail, content) = parse_emphasis(contents, b'/')?;
let node = arena.append_element(Element::Italic, parent); let node = arena.append_element(Element::Italic, parent);
containers.push(Container::Inline { content, node }); containers.push(Container::Inline { content, node });
return Some(tail); Some(tail)
}
} }
b'_' => { b'_' => {
if let Some((tail, content)) = parse_emphasis(contents, b'_') { let (tail, content) = parse_emphasis(contents, b'_')?;
let node = arena.append_element(Element::Underline, parent); let node = arena.append_element(Element::Underline, parent);
containers.push(Container::Inline { content, node }); containers.push(Container::Inline { content, node });
return Some(tail); Some(tail)
}
} }
b'=' => { b'=' => {
if let Some((tail, value)) = parse_emphasis(contents, b'=') { let (tail, value) = parse_emphasis(contents, b'=')?;
arena.append_element( let value = value.into();
Element::Verbatim { arena.append_element(Element::Verbatim { value }, parent);
value: value.into(), Some(tail)
},
parent,
);
return Some(tail);
}
} }
b'~' => { b'~' => {
if let Some((tail, value)) = parse_emphasis(contents, b'~') { let (tail, value) = parse_emphasis(contents, b'~')?;
arena.append_element( let value = value.into();
Element::Code { arena.append_element(Element::Code { value }, parent);
value: value.into(), Some(tail)
},
parent,
);
return Some(tail);
}
} }
b's' => { b's' => {
if let Ok((tail, inline_src)) = InlineSrc::parse(contents) { let (tail, inline_src) = InlineSrc::parse(contents)?;
arena.append_element(inline_src, parent); arena.append_element(inline_src, parent);
return Some(tail); Some(tail)
}
} }
b'c' => { b'c' => {
if let Ok((tail, inline_call)) = InlineCall::parse(contents) { let (tail, inline_call) = InlineCall::parse(contents)?;
arena.append_element(inline_call, parent); arena.append_element(inline_call, parent);
return Some(tail); Some(tail)
}
} }
_ => (), _ => None,
} }
None
} }
pub fn parse_list_items<'a, T: ElementArena<'a>>( pub fn parse_list_items<'a, T: ElementArena<'a>>(
@ -701,20 +670,16 @@ pub fn parse_table<'a, T: ElementArena<'a>>(
} }
Some("") Some("")
} else if let Ok((tail, value)) = parse_table_el(contents) {
arena.append_element(
Table::TableEl {
value: value.into(),
},
parent,
);
Some(tail)
} else { } 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 let Some(i) = memchr(b'\n', input.as_bytes()) {
if i > 0 && input.as_bytes()[i - 1] == b'\r' { if i > 0 && input.as_bytes()[i - 1] == b'\r' {
Ok((&input[i + 1..], &input[0..i - 1])) 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) 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| { move |input| {
let mut last_end = 0; let mut last_end = 0;
for i in memchr_iter(b'\n', input.as_bytes()) { for i in memchr_iter(b'\n', input.as_bytes()) {
if i > 0 && input.as_bytes()[i - 1] == b'\r' { if i > 0 && input.as_bytes()[i - 1] == b'\r' {
if !predicate(&input[last_end..i - 1]) { 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]) { } 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; last_end = i + 1;
} }
if !predicate(&input[last_end..]) { if !predicate(&input[last_end..]) {
Ok((&input[last_end..], &input[0..last_end])) (&input[last_end..], &input[0..last_end])
} else { } else {
Ok(("", input)) ("", input)
} }
} }
} }
pub fn skip_empty_lines(input: &str) -> &str { pub fn skip_empty_lines(input: &str) -> &str {
take_lines_while(|line| line.trim().is_empty())(input) take_lines_while(|line| line.trim().is_empty())(input).0
.map(|(tail, _)| tail)
.unwrap_or(input)
} }
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_, level) = parse_headline_level(input)?;
let (input_, content) = take_lines_while(move |line| { let (input_, content) = take_lines_while(move |line| {
if let Ok((_, l)) = parse_headline_level(line) { parse_headline_level(line)
l > level .map(|(_, l)| l > level)
} else { .unwrap_or(true)
true })(input_);
} Some((input_, (&input[0..level + content.len()], level)))
})(input_)?;
Ok((input_, (&input[0..level + content.len()], level)))
} }
pub fn parse_headline_level(input: &str) -> IResult<&str, usize> { pub fn parse_headline_level(input: &str) -> Option<(&str, usize)> {
let (input, stars) = take_while1(|c: char| c == '*')(input)?; let (input, stars) = take_while1::<_, _, ()>(|c: char| c == '*')(input).ok()?;
if input.starts_with(' ') || input.starts_with('\n') || input.is_empty() { if input.starts_with(' ') || input.starts_with('\n') || input.is_empty() {
Ok((input, stars.len())) Some((input, stars.len()))
} else { } else {
Err(Err::Error(error_position!(input, ErrorKind::Tag))) None
} }
} }
pub fn parse_fixed_width(input: &str) -> IResult<&str, &str> { pub fn parse_fixed_width(input: &str) -> Option<(&str, &str)> {
verify( let (input, content) = take_lines_while(|line| line == ":" || line.starts_with(": "))(input);
take_lines_while(|line| line == ":" || line.starts_with(": ")),
|s: &str| !s.is_empty(), if !content.is_empty() {
)(input) Some((input, content))
} else {
None
}
} }
pub fn parse_comment(input: &str) -> IResult<&str, &str> { pub fn parse_comment(input: &str) -> Option<(&str, &str)> {
verify( let (input, content) = take_lines_while(|line| line == "#" || line.starts_with("# "))(input);
take_lines_while(|line| line == "#" || line.starts_with("# ")),
|s: &str| !s.is_empty(), if !content.is_empty() {
)(input) 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) take_while1(|c: char| !c.is_ascii_whitespace())(input)
} }