feat(parser): generic error type
This commit is contained in:
parent
f4f0fa9828
commit
231d80b464
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
|
@ -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((
|
||||||
"",
|
"",
|
||||||
(
|
(
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.",)))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
|
@ -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::{
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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());
|
||||||
// }
|
// }
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue