feat(elements): cleanup and minor refactor
This commit is contained in:
parent
c1465a6d77
commit
9bc260627b
|
@ -2,71 +2,7 @@ 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, sequence::preceded, IResult};
|
||||||
|
|
||||||
use crate::parsers::{take_lines_till, take_until_eol};
|
use crate::parsers::{line, take_lines_while};
|
||||||
|
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Block<'a> {
|
|
||||||
pub name: Cow<'a, str>,
|
|
||||||
pub args: Option<Cow<'a, str>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Block<'_> {
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn parse(input: &str) -> IResult<&str, (Block<'_>, &str)> {
|
|
||||||
let (input, name) = preceded(tag_no_case("#+BEGIN_"), alpha1)(input)?;
|
|
||||||
let (input, args) = take_until_eol(input)?;
|
|
||||||
let end_line = format!(r"#+END_{}", name);
|
|
||||||
let (input, contents) =
|
|
||||||
take_lines_till(|line| line.eq_ignore_ascii_case(&end_line))(input)?;
|
|
||||||
|
|
||||||
Ok((
|
|
||||||
input,
|
|
||||||
(
|
|
||||||
Block {
|
|
||||||
name: name.into(),
|
|
||||||
args: if args.is_empty() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(args.into())
|
|
||||||
},
|
|
||||||
},
|
|
||||||
contents,
|
|
||||||
),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse() {
|
|
||||||
assert_eq!(
|
|
||||||
Block::parse("#+BEGIN_SRC\n#+END_SRC"),
|
|
||||||
Ok((
|
|
||||||
"",
|
|
||||||
(
|
|
||||||
Block {
|
|
||||||
name: "SRC".into(),
|
|
||||||
args: None,
|
|
||||||
},
|
|
||||||
""
|
|
||||||
)
|
|
||||||
))
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
Block::parse("#+BEGIN_SRC javascript \nconsole.log('Hello World!');\n#+END_SRC\n"),
|
|
||||||
Ok((
|
|
||||||
"",
|
|
||||||
(
|
|
||||||
Block {
|
|
||||||
name: "SRC".into(),
|
|
||||||
args: Some("javascript".into()),
|
|
||||||
},
|
|
||||||
"console.log('Hello World!');\n"
|
|
||||||
)
|
|
||||||
))
|
|
||||||
);
|
|
||||||
// TODO: more testing
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
|
@ -129,3 +65,60 @@ pub struct SourceBlock<'a> {
|
||||||
pub language: Cow<'a, str>,
|
pub language: Cow<'a, str>,
|
||||||
pub arguments: Cow<'a, str>,
|
pub arguments: Cow<'a, str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn parse_block_element(input: &str) -> IResult<&str, (&str, Option<&str>, &str)> {
|
||||||
|
let (input, name) = preceded(tag_no_case("#+BEGIN_"), alpha1)(input)?;
|
||||||
|
let (input, args) = line(input)?;
|
||||||
|
let end_line = format!(r"#+END_{}", name);
|
||||||
|
let (input, contents) =
|
||||||
|
take_lines_while(|line| !line.trim().eq_ignore_ascii_case(&end_line))(input)?;
|
||||||
|
let (input, _) = line(input)?;
|
||||||
|
|
||||||
|
Ok((
|
||||||
|
input,
|
||||||
|
(
|
||||||
|
name,
|
||||||
|
if args.trim().is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(args.trim())
|
||||||
|
},
|
||||||
|
contents,
|
||||||
|
),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse() {
|
||||||
|
assert_eq!(
|
||||||
|
parse_block_element(
|
||||||
|
r#"#+BEGIN_SRC
|
||||||
|
#+END_SRC"#
|
||||||
|
),
|
||||||
|
Ok(("", ("SRC".into(), None, "")))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
parse_block_element(
|
||||||
|
r#"#+begin_src
|
||||||
|
#+end_src"#
|
||||||
|
),
|
||||||
|
Ok(("", ("src".into(), None, "")))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
parse_block_element(
|
||||||
|
r#"#+BEGIN_SRC javascript
|
||||||
|
console.log('Hello World!');
|
||||||
|
#+END_SRC
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
Ok((
|
||||||
|
"",
|
||||||
|
(
|
||||||
|
"SRC".into(),
|
||||||
|
Some("javascript".into()),
|
||||||
|
"console.log('Hello World!');\n"
|
||||||
|
)
|
||||||
|
))
|
||||||
|
);
|
||||||
|
// TODO: more testing
|
||||||
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ use nom::{
|
||||||
IResult,
|
IResult,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::elements::{Datetime, Element, Timestamp};
|
use crate::elements::{Datetime, Timestamp};
|
||||||
use crate::parsers::eol;
|
use crate::parsers::eol;
|
||||||
|
|
||||||
/// clock elements
|
/// clock elements
|
||||||
|
@ -40,7 +40,7 @@ pub enum Clock<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Clock<'_> {
|
impl Clock<'_> {
|
||||||
pub(crate) fn parse(input: &str) -> IResult<&str, Element<'_>> {
|
pub(crate) fn parse(input: &str) -> IResult<&str, Clock<'_>> {
|
||||||
let (input, _) = tag("CLOCK:")(input)?;
|
let (input, _) = tag("CLOCK:")(input)?;
|
||||||
let (input, _) = space0(input)?;
|
let (input, _) = space0(input)?;
|
||||||
let (input, timestamp) = Timestamp::parse_inactive(input)?;
|
let (input, timestamp) = Timestamp::parse_inactive(input)?;
|
||||||
|
@ -60,13 +60,13 @@ impl Clock<'_> {
|
||||||
let (input, _) = eol(input)?;
|
let (input, _) = eol(input)?;
|
||||||
Ok((
|
Ok((
|
||||||
input,
|
input,
|
||||||
Element::Clock(Clock::Closed {
|
Clock::Closed {
|
||||||
start,
|
start,
|
||||||
end,
|
end,
|
||||||
repeater,
|
repeater,
|
||||||
delay,
|
delay,
|
||||||
duration: duration.into(),
|
duration: duration.into(),
|
||||||
}),
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
Timestamp::Inactive {
|
Timestamp::Inactive {
|
||||||
|
@ -77,11 +77,11 @@ impl Clock<'_> {
|
||||||
let (input, _) = eol(input)?;
|
let (input, _) = eol(input)?;
|
||||||
Ok((
|
Ok((
|
||||||
input,
|
input,
|
||||||
Element::Clock(Clock::Running {
|
Clock::Running {
|
||||||
start,
|
start,
|
||||||
repeater,
|
repeater,
|
||||||
delay,
|
delay,
|
||||||
}),
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
_ => unreachable!(
|
_ => unreachable!(
|
||||||
|
@ -148,7 +148,7 @@ fn parse() {
|
||||||
Clock::parse("CLOCK: [2003-09-16 Tue 09:39]"),
|
Clock::parse("CLOCK: [2003-09-16 Tue 09:39]"),
|
||||||
Ok((
|
Ok((
|
||||||
"",
|
"",
|
||||||
Element::Clock(Clock::Running {
|
Clock::Running {
|
||||||
start: Datetime {
|
start: Datetime {
|
||||||
year: 2003,
|
year: 2003,
|
||||||
month: 9,
|
month: 9,
|
||||||
|
@ -159,14 +159,14 @@ fn parse() {
|
||||||
},
|
},
|
||||||
repeater: None,
|
repeater: None,
|
||||||
delay: None,
|
delay: None,
|
||||||
})
|
}
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Clock::parse("CLOCK: [2003-09-16 Tue 09:39]--[2003-09-16 Tue 10:39] => 1:00"),
|
Clock::parse("CLOCK: [2003-09-16 Tue 09:39]--[2003-09-16 Tue 10:39] => 1:00"),
|
||||||
Ok((
|
Ok((
|
||||||
"",
|
"",
|
||||||
Element::Clock(Clock::Closed {
|
Clock::Closed {
|
||||||
start: Datetime {
|
start: Datetime {
|
||||||
year: 2003,
|
year: 2003,
|
||||||
month: 9,
|
month: 9,
|
||||||
|
@ -186,7 +186,7 @@ fn parse() {
|
||||||
repeater: None,
|
repeater: None,
|
||||||
delay: None,
|
delay: None,
|
||||||
duration: "1:00".into(),
|
duration: "1:00".into(),
|
||||||
})
|
}
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use crate::parsers::{eol, take_lines_till};
|
use crate::parsers::{eol, line, take_lines_while};
|
||||||
|
|
||||||
use nom::{
|
use nom::{
|
||||||
bytes::complete::{tag, take_while1},
|
bytes::complete::{tag, take_while1},
|
||||||
|
@ -24,7 +24,9 @@ impl Drawer<'_> {
|
||||||
tag(":"),
|
tag(":"),
|
||||||
)(input)?;
|
)(input)?;
|
||||||
let (input, _) = eol(input)?;
|
let (input, _) = eol(input)?;
|
||||||
let (input, contents) = take_lines_till(|line| line.eq_ignore_ascii_case(":END:"))(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)))
|
Ok((input, (Drawer { name: name.into() }, contents)))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use crate::elements::Element;
|
use crate::parsers::{line, take_lines_while};
|
||||||
use crate::parsers::{take_lines_till, take_until_eol};
|
|
||||||
|
|
||||||
use nom::{
|
use nom::{
|
||||||
bytes::complete::tag_no_case,
|
bytes::complete::tag_no_case,
|
||||||
|
@ -20,25 +19,26 @@ pub struct DynBlock<'a> {
|
||||||
|
|
||||||
impl DynBlock<'_> {
|
impl DynBlock<'_> {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn parse(input: &str) -> IResult<&str, (Element<'_>, &str)> {
|
pub(crate) fn parse(input: &str) -> IResult<&str, (DynBlock<'_>, &str)> {
|
||||||
let (input, _) = tag_no_case("#+BEGIN:")(input)?;
|
let (input, _) = tag_no_case("#+BEGIN:")(input)?;
|
||||||
let (input, _) = space1(input)?;
|
let (input, _) = space1(input)?;
|
||||||
let (input, name) = alpha1(input)?;
|
let (input, name) = alpha1(input)?;
|
||||||
let (input, args) = take_until_eol(input)?;
|
let (input, args) = line(input)?;
|
||||||
|
let (input, contents) =
|
||||||
let (input, contents) = take_lines_till(|line| line.eq_ignore_ascii_case("#+END:"))(input)?;
|
take_lines_while(|line| !line.trim().eq_ignore_ascii_case("#+END:"))(input)?;
|
||||||
|
let (input, _) = line(input)?;
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
input,
|
input,
|
||||||
(
|
(
|
||||||
Element::DynBlock(DynBlock {
|
DynBlock {
|
||||||
block_name: name.into(),
|
block_name: name.into(),
|
||||||
arguments: if args.is_empty() {
|
arguments: if args.trim().is_empty() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(args.into())
|
Some(args.trim().into())
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}),
|
|
||||||
contents,
|
contents,
|
||||||
),
|
),
|
||||||
))
|
))
|
||||||
|
@ -49,14 +49,19 @@ impl DynBlock<'_> {
|
||||||
fn parse() {
|
fn parse() {
|
||||||
// TODO: testing
|
// TODO: testing
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
DynBlock::parse("#+BEGIN: clocktable :scope file\nCONTENTS\n#+END:\n"),
|
DynBlock::parse(
|
||||||
|
r#"#+BEGIN: clocktable :scope file
|
||||||
|
CONTENTS
|
||||||
|
#+END:
|
||||||
|
"#
|
||||||
|
),
|
||||||
Ok((
|
Ok((
|
||||||
"",
|
"",
|
||||||
(
|
(
|
||||||
Element::DynBlock(DynBlock {
|
DynBlock {
|
||||||
block_name: "clocktable".into(),
|
block_name: "clocktable".into(),
|
||||||
arguments: Some(":scope file".into()),
|
arguments: Some(":scope file".into()),
|
||||||
}),
|
},
|
||||||
"CONTENTS\n"
|
"CONTENTS\n"
|
||||||
)
|
)
|
||||||
))
|
))
|
||||||
|
|
|
@ -2,7 +2,7 @@ use bytecount::count;
|
||||||
use memchr::memchr_iter;
|
use memchr::memchr_iter;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn parse(text: &str, marker: u8) -> Option<(&str, &str)> {
|
pub(crate) fn parse_emphasis(text: &str, marker: u8) -> Option<(&str, &str)> {
|
||||||
debug_assert!(text.len() >= 3);
|
debug_assert!(text.len() >= 3);
|
||||||
|
|
||||||
let bytes = text.as_bytes();
|
let bytes = text.as_bytes();
|
||||||
|
@ -35,19 +35,14 @@ fn validate_marker(pos: usize, text: &str) -> bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse() {
|
fn parse() {
|
||||||
use super::parse;
|
assert_eq!(parse_emphasis("*bold*", b'*'), Some(("", "bold")));
|
||||||
|
assert_eq!(parse_emphasis("*bo*ld*", b'*'), Some(("", "bo*ld")));
|
||||||
assert_eq!(parse("*bold*", b'*'), Some(("", "bold")));
|
assert_eq!(parse_emphasis("*bo\nld*", b'*'), Some(("", "bo\nld")));
|
||||||
assert_eq!(parse("*bo*ld*", b'*'), Some(("", "bo*ld")));
|
assert_eq!(parse_emphasis("*bold*a", b'*'), None);
|
||||||
assert_eq!(parse("*bo\nld*", b'*'), Some(("", "bo\nld")));
|
assert_eq!(parse_emphasis("*bold*", b'/'), None);
|
||||||
assert_eq!(parse("*bold*a", b'*'), None);
|
assert_eq!(parse_emphasis("*bold *", b'*'), None);
|
||||||
assert_eq!(parse("*bold*", b'/'), None);
|
assert_eq!(parse_emphasis("* bold*", b'*'), None);
|
||||||
assert_eq!(parse("*bold *", b'*'), None);
|
assert_eq!(parse_emphasis("*b\nol\nd*", b'*'), None);
|
||||||
assert_eq!(parse("* bold*", b'*'), None);
|
|
||||||
assert_eq!(parse("*b\nol\nd*", b'*'), None);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use memchr::memchr;
|
|
||||||
use nom::{
|
use nom::{
|
||||||
bytes::complete::{tag, take_while1},
|
bytes::complete::{tag, take_while1},
|
||||||
sequence::delimited,
|
sequence::delimited,
|
||||||
IResult,
|
IResult,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::parsers::line;
|
||||||
|
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -14,29 +15,24 @@ pub struct FnDef<'a> {
|
||||||
pub label: Cow<'a, str>,
|
pub label: Cow<'a, str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_label(input: &str) -> IResult<&str, &str> {
|
impl FnDef<'_> {
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn parse(input: &str) -> IResult<&str, (FnDef<'_>, &str)> {
|
||||||
let (input, label) = delimited(
|
let (input, label) = delimited(
|
||||||
tag("[fn:"),
|
tag("[fn:"),
|
||||||
take_while1(|c: char| c.is_ascii_alphanumeric() || c == '-' || c == '_'),
|
take_while1(|c: char| c.is_ascii_alphanumeric() || c == '-' || c == '_'),
|
||||||
tag("]"),
|
tag("]"),
|
||||||
)(input)?;
|
)(input)?;
|
||||||
|
let (input, content) = line(input)?;
|
||||||
|
|
||||||
Ok((input, label))
|
Ok((
|
||||||
}
|
input,
|
||||||
|
(
|
||||||
impl FnDef<'_> {
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn parse(text: &str) -> Option<(&str, FnDef<'_>, &str)> {
|
|
||||||
let (tail, label) = parse_label(text).ok()?;
|
|
||||||
|
|
||||||
let end = memchr(b'\n', tail.as_bytes()).unwrap_or_else(|| tail.len());
|
|
||||||
|
|
||||||
Some((
|
|
||||||
&tail[end..],
|
|
||||||
FnDef {
|
FnDef {
|
||||||
label: label.into(),
|
label: label.into(),
|
||||||
},
|
},
|
||||||
&tail[0..end],
|
content,
|
||||||
|
),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,39 +41,46 @@ impl FnDef<'_> {
|
||||||
fn parse() {
|
fn parse() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
FnDef::parse("[fn:1] https://orgmode.org"),
|
FnDef::parse("[fn:1] https://orgmode.org"),
|
||||||
Some(("", 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"),
|
FnDef::parse("[fn:word_1] https://orgmode.org"),
|
||||||
Some((
|
Ok((
|
||||||
"",
|
"",
|
||||||
|
(
|
||||||
FnDef {
|
FnDef {
|
||||||
label: "word_1".into()
|
label: "word_1".into()
|
||||||
},
|
},
|
||||||
" https://orgmode.org"
|
" https://orgmode.org"
|
||||||
|
)
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
FnDef::parse("[fn:WORD-1] https://orgmode.org"),
|
FnDef::parse("[fn:WORD-1] https://orgmode.org"),
|
||||||
Some((
|
Ok((
|
||||||
"",
|
"",
|
||||||
|
(
|
||||||
FnDef {
|
FnDef {
|
||||||
label: "WORD-1".into()
|
label: "WORD-1".into()
|
||||||
},
|
},
|
||||||
" https://orgmode.org"
|
" https://orgmode.org"
|
||||||
|
)
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
FnDef::parse("[fn:WORD]"),
|
FnDef::parse("[fn:WORD]"),
|
||||||
Some((
|
Ok((
|
||||||
"",
|
"",
|
||||||
|
(
|
||||||
FnDef {
|
FnDef {
|
||||||
label: "WORD".into()
|
label: "WORD".into()
|
||||||
},
|
},
|
||||||
""
|
""
|
||||||
|
)
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
assert_eq!(FnDef::parse("[fn:] https://orgmode.org"), None);
|
|
||||||
assert_eq!(FnDef::parse("[fn:wor d] https://orgmode.org"), None);
|
assert!(FnDef::parse("[fn:] https://orgmode.org").is_err());
|
||||||
assert_eq!(FnDef::parse("[fn:WORD https://orgmode.org"), None);
|
assert!(FnDef::parse("[fn:wor d] https://orgmode.org").is_err());
|
||||||
|
assert!(FnDef::parse("[fn:WORD https://orgmode.org").is_err());
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,8 +7,6 @@ use nom::{
|
||||||
IResult,
|
IResult,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::elements::Element;
|
|
||||||
|
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -21,9 +19,9 @@ pub struct InlineCall<'a> {
|
||||||
pub end_header: Option<Cow<'a, str>>,
|
pub end_header: Option<Cow<'a, str>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> InlineCall<'a> {
|
impl InlineCall<'_> {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn parse(input: &str) -> IResult<&str, Element<'_>> {
|
pub(crate) fn parse(input: &str) -> IResult<&str, InlineCall<'_>> {
|
||||||
let (input, name) = preceded(
|
let (input, name) = preceded(
|
||||||
tag("call_"),
|
tag("call_"),
|
||||||
take_till(|c| c == '[' || c == '\n' || c == '(' || c == ')'),
|
take_till(|c| c == '[' || c == '\n' || c == '(' || c == ')'),
|
||||||
|
@ -43,12 +41,12 @@ impl<'a> InlineCall<'a> {
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
input,
|
input,
|
||||||
Element::InlineCall(InlineCall {
|
InlineCall {
|
||||||
name: name.into(),
|
name: name.into(),
|
||||||
arguments: arguments.into(),
|
arguments: arguments.into(),
|
||||||
inside_header: inside_header.map(Into::into),
|
inside_header: inside_header.map(Into::into),
|
||||||
end_header: end_header.map(Into::into),
|
end_header: end_header.map(Into::into),
|
||||||
}),
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -59,48 +57,48 @@ fn parse() {
|
||||||
InlineCall::parse("call_square(4)"),
|
InlineCall::parse("call_square(4)"),
|
||||||
Ok((
|
Ok((
|
||||||
"",
|
"",
|
||||||
Element::InlineCall(InlineCall {
|
InlineCall {
|
||||||
name: "square".into(),
|
name: "square".into(),
|
||||||
arguments: "4".into(),
|
arguments: "4".into(),
|
||||||
inside_header: None,
|
inside_header: None,
|
||||||
end_header: None,
|
end_header: None,
|
||||||
}),
|
}
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
InlineCall::parse("call_square[:results output](4)"),
|
InlineCall::parse("call_square[:results output](4)"),
|
||||||
Ok((
|
Ok((
|
||||||
"",
|
"",
|
||||||
Element::InlineCall(InlineCall {
|
InlineCall {
|
||||||
name: "square".into(),
|
name: "square".into(),
|
||||||
arguments: "4".into(),
|
arguments: "4".into(),
|
||||||
inside_header: Some(":results output".into()),
|
inside_header: Some(":results output".into()),
|
||||||
end_header: None,
|
end_header: None,
|
||||||
}),
|
},
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
InlineCall::parse("call_square(4)[:results html]"),
|
InlineCall::parse("call_square(4)[:results html]"),
|
||||||
Ok((
|
Ok((
|
||||||
"",
|
"",
|
||||||
Element::InlineCall(InlineCall {
|
InlineCall {
|
||||||
name: "square".into(),
|
name: "square".into(),
|
||||||
arguments: "4".into(),
|
arguments: "4".into(),
|
||||||
inside_header: None,
|
inside_header: None,
|
||||||
end_header: Some(":results html".into()),
|
end_header: Some(":results html".into()),
|
||||||
}),
|
},
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
InlineCall::parse("call_square[:results output](4)[:results html]"),
|
InlineCall::parse("call_square[:results output](4)[:results html]"),
|
||||||
Ok((
|
Ok((
|
||||||
"",
|
"",
|
||||||
Element::InlineCall(InlineCall {
|
InlineCall {
|
||||||
name: "square".into(),
|
name: "square".into(),
|
||||||
arguments: "4".into(),
|
arguments: "4".into(),
|
||||||
inside_header: Some(":results output".into()),
|
inside_header: Some(":results output".into()),
|
||||||
end_header: Some(":results html".into()),
|
end_header: Some(":results html".into()),
|
||||||
}),
|
},
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,8 +7,6 @@ use nom::{
|
||||||
IResult,
|
IResult,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::elements::Element;
|
|
||||||
|
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -21,7 +19,7 @@ pub struct InlineSrc<'a> {
|
||||||
|
|
||||||
impl InlineSrc<'_> {
|
impl InlineSrc<'_> {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn parse(input: &str) -> IResult<&str, Element<'_>> {
|
pub(crate) fn parse(input: &str) -> IResult<&str, InlineSrc<'_>> {
|
||||||
let (input, _) = tag("src_")(input)?;
|
let (input, _) = tag("src_")(input)?;
|
||||||
let (input, lang) =
|
let (input, lang) =
|
||||||
take_while1(|c: char| !c.is_ascii_whitespace() && c != '[' && c != '{')(input)?;
|
take_while1(|c: char| !c.is_ascii_whitespace() && c != '[' && c != '{')(input)?;
|
||||||
|
@ -35,11 +33,11 @@ impl InlineSrc<'_> {
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
input,
|
input,
|
||||||
Element::InlineSrc(InlineSrc {
|
InlineSrc {
|
||||||
lang: lang.into(),
|
lang: lang.into(),
|
||||||
options: options.map(Into::into),
|
options: options.map(Into::into),
|
||||||
body: body.into(),
|
body: body.into(),
|
||||||
}),
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -50,22 +48,22 @@ fn parse() {
|
||||||
InlineSrc::parse("src_C{int a = 0;}"),
|
InlineSrc::parse("src_C{int a = 0;}"),
|
||||||
Ok((
|
Ok((
|
||||||
"",
|
"",
|
||||||
Element::InlineSrc(InlineSrc {
|
InlineSrc {
|
||||||
lang: "C".into(),
|
lang: "C".into(),
|
||||||
options: None,
|
options: None,
|
||||||
body: "int a = 0;".into()
|
body: "int a = 0;".into()
|
||||||
}),
|
},
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
InlineSrc::parse("src_xml[:exports code]{<tag>text</tag>}"),
|
InlineSrc::parse("src_xml[:exports code]{<tag>text</tag>}"),
|
||||||
Ok((
|
Ok((
|
||||||
"",
|
"",
|
||||||
Element::InlineSrc(InlineSrc {
|
InlineSrc {
|
||||||
lang: "xml".into(),
|
lang: "xml".into(),
|
||||||
options: Some(":exports code".into()),
|
options: Some(":exports code".into()),
|
||||||
body: "<tag>text</tag>".into(),
|
body: "<tag>text</tag>".into(),
|
||||||
}),
|
},
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -7,8 +7,7 @@ use nom::{
|
||||||
IResult,
|
IResult,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::elements::Element;
|
use crate::parsers::line;
|
||||||
use crate::parsers::take_until_eol;
|
|
||||||
|
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
||||||
|
@ -27,132 +26,52 @@ pub struct BabelCall<'a> {
|
||||||
pub value: Cow<'a, str>,
|
pub value: Cow<'a, str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Keyword<'_> {
|
pub(crate) fn parse_keyword(input: &str) -> IResult<&str, (&str, Option<&str>, &str)> {
|
||||||
#[inline]
|
|
||||||
pub(crate) fn parse(input: &str) -> IResult<&str, Element<'_>> {
|
|
||||||
let (input, _) = tag("#+")(input)?;
|
let (input, _) = tag("#+")(input)?;
|
||||||
let (input, key) =
|
let (input, key) = take_till(|c: char| c.is_ascii_whitespace() || c == ':' || c == '[')(input)?;
|
||||||
take_till(|c: char| c.is_ascii_whitespace() || c == ':' || c == '[')(input)?;
|
|
||||||
let (input, optional) = opt(delimited(
|
let (input, optional) = opt(delimited(
|
||||||
tag("["),
|
tag("["),
|
||||||
take_till(|c| c == ']' || c == '\n'),
|
take_till(|c| c == ']' || c == '\n'),
|
||||||
tag("]"),
|
tag("]"),
|
||||||
))(input)?;
|
))(input)?;
|
||||||
let (input, _) = tag(":")(input)?;
|
let (input, _) = tag(":")(input)?;
|
||||||
let (input, value) = take_until_eol(input)?;
|
let (input, value) = line(input)?;
|
||||||
|
|
||||||
if key.eq_ignore_ascii_case("CALL") {
|
Ok((input, (key, optional, value.trim())))
|
||||||
Ok((
|
|
||||||
input,
|
|
||||||
Element::BabelCall(BabelCall {
|
|
||||||
value: value.into(),
|
|
||||||
}),
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
Ok((
|
|
||||||
input,
|
|
||||||
Element::Keyword(Keyword {
|
|
||||||
key: key.into(),
|
|
||||||
optional: optional.map(Into::into),
|
|
||||||
value: value.into(),
|
|
||||||
}),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse() {
|
fn parse() {
|
||||||
|
assert_eq!(parse_keyword("#+KEY:"), Ok(("", ("KEY", None, ""))));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Keyword::parse("#+KEY:"),
|
parse_keyword("#+KEY: VALUE"),
|
||||||
Ok((
|
Ok(("", ("KEY", None, "VALUE")))
|
||||||
"",
|
|
||||||
Element::Keyword(Keyword {
|
|
||||||
key: "KEY".into(),
|
|
||||||
optional: None,
|
|
||||||
value: "".into(),
|
|
||||||
})
|
|
||||||
))
|
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Keyword::parse("#+KEY: VALUE"),
|
parse_keyword("#+K_E_Y: VALUE"),
|
||||||
Ok((
|
Ok(("", ("K_E_Y", None, "VALUE")))
|
||||||
"",
|
|
||||||
Element::Keyword(Keyword {
|
|
||||||
key: "KEY".into(),
|
|
||||||
optional: None,
|
|
||||||
value: "VALUE".into(),
|
|
||||||
})
|
|
||||||
))
|
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Keyword::parse("#+K_E_Y: VALUE"),
|
parse_keyword("#+KEY:VALUE\n"),
|
||||||
Ok((
|
Ok(("", ("KEY", None, "VALUE")))
|
||||||
"",
|
|
||||||
Element::Keyword(Keyword {
|
|
||||||
key: "K_E_Y".into(),
|
|
||||||
optional: None,
|
|
||||||
value: "VALUE".into(),
|
|
||||||
})
|
|
||||||
))
|
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert!(parse_keyword("#+KE Y: VALUE").is_err());
|
||||||
Keyword::parse("#+KEY:VALUE\n"),
|
assert!(parse_keyword("#+ KEY: VALUE").is_err());
|
||||||
Ok((
|
|
||||||
"",
|
assert_eq!(parse_keyword("#+RESULTS:"), Ok(("", ("RESULTS", None, ""))));
|
||||||
Element::Keyword(Keyword {
|
|
||||||
key: "KEY".into(),
|
|
||||||
optional: None,
|
|
||||||
value: "VALUE".into(),
|
|
||||||
})
|
|
||||||
))
|
|
||||||
);
|
|
||||||
assert!(Keyword::parse("#+KE Y: VALUE").is_err());
|
|
||||||
assert!(Keyword::parse("#+ KEY: VALUE").is_err());
|
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Keyword::parse("#+RESULTS:"),
|
parse_keyword("#+ATTR_LATEX: :width 5cm\n"),
|
||||||
Ok((
|
Ok(("", ("ATTR_LATEX", None, ":width 5cm")))
|
||||||
"",
|
|
||||||
Element::Keyword(Keyword {
|
|
||||||
key: "RESULTS".into(),
|
|
||||||
optional: None,
|
|
||||||
value: "".into(),
|
|
||||||
})
|
|
||||||
))
|
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Keyword::parse("#+ATTR_LATEX: :width 5cm\n"),
|
parse_keyword("#+CALL: double(n=4)"),
|
||||||
Ok((
|
Ok(("", ("CALL", None, "double(n=4)")))
|
||||||
"",
|
|
||||||
Element::Keyword(Keyword {
|
|
||||||
key: "ATTR_LATEX".into(),
|
|
||||||
optional: None,
|
|
||||||
value: ":width 5cm".into(),
|
|
||||||
})
|
|
||||||
))
|
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Keyword::parse("#+CALL: double(n=4)"),
|
parse_keyword("#+CAPTION[Short caption]: Longer caption."),
|
||||||
Ok((
|
Ok(("", ("CAPTION", Some("Short caption"), "Longer caption.",)))
|
||||||
"",
|
|
||||||
Element::BabelCall(BabelCall {
|
|
||||||
value: "double(n=4)".into(),
|
|
||||||
})
|
|
||||||
))
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
Keyword::parse("#+CAPTION[Short caption]: Longer caption."),
|
|
||||||
Ok((
|
|
||||||
"",
|
|
||||||
Element::Keyword(Keyword {
|
|
||||||
key: "CAPTION".into(),
|
|
||||||
optional: Some("Short caption".into()),
|
|
||||||
value: "Longer caption.".into(),
|
|
||||||
})
|
|
||||||
))
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,8 +7,6 @@ use nom::{
|
||||||
IResult,
|
IResult,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::elements::Element;
|
|
||||||
|
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -20,7 +18,7 @@ pub struct Link<'a> {
|
||||||
|
|
||||||
impl Link<'_> {
|
impl Link<'_> {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn parse(input: &str) -> IResult<&str, Element<'_>> {
|
pub(crate) fn parse(input: &str) -> IResult<&str, Link<'_>> {
|
||||||
let (input, path) = delimited(
|
let (input, path) = delimited(
|
||||||
tag("[["),
|
tag("[["),
|
||||||
take_while(|c: char| c != '<' && c != '>' && c != '\n' && c != ']'),
|
take_while(|c: char| c != '<' && c != '>' && c != '\n' && c != ']'),
|
||||||
|
@ -34,10 +32,10 @@ impl Link<'_> {
|
||||||
let (input, _) = tag("]")(input)?;
|
let (input, _) = tag("]")(input)?;
|
||||||
Ok((
|
Ok((
|
||||||
input,
|
input,
|
||||||
Element::Link(Link {
|
Link {
|
||||||
path: path.into(),
|
path: path.into(),
|
||||||
desc: desc.map(Into::into),
|
desc: desc.map(Into::into),
|
||||||
}),
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,20 +46,20 @@ fn parse() {
|
||||||
Link::parse("[[#id]]"),
|
Link::parse("[[#id]]"),
|
||||||
Ok((
|
Ok((
|
||||||
"",
|
"",
|
||||||
Element::Link(Link {
|
Link {
|
||||||
path: "#id".into(),
|
path: "#id".into(),
|
||||||
desc: None
|
desc: None
|
||||||
},)
|
}
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Link::parse("[[#id][desc]]"),
|
Link::parse("[[#id][desc]]"),
|
||||||
Ok((
|
Ok((
|
||||||
"",
|
"",
|
||||||
Element::Link(Link {
|
Link {
|
||||||
path: "#id".into(),
|
path: "#id".into(),
|
||||||
desc: Some("desc".into())
|
desc: Some("desc".into())
|
||||||
})
|
}
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
assert!(Link::parse("[[#id][desc]").is_err());
|
assert!(Link::parse("[[#id][desc]").is_err());
|
||||||
|
|
|
@ -7,8 +7,6 @@ use nom::{
|
||||||
IResult,
|
IResult,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::elements::Element;
|
|
||||||
|
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -20,7 +18,7 @@ pub struct Macros<'a> {
|
||||||
|
|
||||||
impl Macros<'_> {
|
impl Macros<'_> {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn parse(input: &str) -> IResult<&str, Element<'_>> {
|
pub(crate) fn parse(input: &str) -> IResult<&str, Macros<'_>> {
|
||||||
let (input, _) = tag("{{{")(input)?;
|
let (input, _) = tag("{{{")(input)?;
|
||||||
let (input, name) = verify(
|
let (input, name) = verify(
|
||||||
take_while1(|c: char| c.is_ascii_alphanumeric() || c == '-' || c == '_'),
|
take_while1(|c: char| c.is_ascii_alphanumeric() || c == '-' || c == '_'),
|
||||||
|
@ -31,10 +29,10 @@ impl Macros<'_> {
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
input,
|
input,
|
||||||
Element::Macros(Macros {
|
Macros {
|
||||||
name: name.into(),
|
name: name.into(),
|
||||||
arguments: arguments.map(Into::into),
|
arguments: arguments.map(Into::into),
|
||||||
}),
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,30 +43,30 @@ fn parse() {
|
||||||
Macros::parse("{{{poem(red,blue)}}}"),
|
Macros::parse("{{{poem(red,blue)}}}"),
|
||||||
Ok((
|
Ok((
|
||||||
"",
|
"",
|
||||||
Element::Macros(Macros {
|
Macros {
|
||||||
name: "poem".into(),
|
name: "poem".into(),
|
||||||
arguments: Some("red,blue".into())
|
arguments: Some("red,blue".into())
|
||||||
})
|
}
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Macros::parse("{{{poem())}}}"),
|
Macros::parse("{{{poem())}}}"),
|
||||||
Ok((
|
Ok((
|
||||||
"",
|
"",
|
||||||
Element::Macros(Macros {
|
Macros {
|
||||||
name: "poem".into(),
|
name: "poem".into(),
|
||||||
arguments: Some(")".into())
|
arguments: Some(")".into())
|
||||||
})
|
}
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Macros::parse("{{{author}}}"),
|
Macros::parse("{{{author}}}"),
|
||||||
Ok((
|
Ok((
|
||||||
"",
|
"",
|
||||||
Element::Macros(Macros {
|
Macros {
|
||||||
name: "author".into(),
|
name: "author".into(),
|
||||||
arguments: None
|
arguments: None
|
||||||
})
|
}
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
assert!(Macros::parse("{{{0uthor}}}").is_err());
|
assert!(Macros::parse("{{{0uthor}}}").is_err());
|
||||||
|
|
|
@ -23,8 +23,10 @@ mod target;
|
||||||
mod timestamp;
|
mod timestamp;
|
||||||
mod title;
|
mod title;
|
||||||
|
|
||||||
pub(crate) use block::Block;
|
pub(crate) use self::{
|
||||||
pub(crate) use emphasis::parse as parse_emphasis;
|
block::parse_block_element, emphasis::parse_emphasis, keyword::parse_keyword, rule::parse_rule,
|
||||||
|
table::parse_table_el,
|
||||||
|
};
|
||||||
|
|
||||||
pub use self::{
|
pub use self::{
|
||||||
block::{
|
block::{
|
||||||
|
@ -45,7 +47,6 @@ pub use self::{
|
||||||
macros::Macros,
|
macros::Macros,
|
||||||
planning::Planning,
|
planning::Planning,
|
||||||
radio_target::RadioTarget,
|
radio_target::RadioTarget,
|
||||||
rule::Rule,
|
|
||||||
snippet::Snippet,
|
snippet::Snippet,
|
||||||
table::{Table, TableRow},
|
table::{Table, TableRow},
|
||||||
target::Target,
|
target::Target,
|
||||||
|
@ -53,6 +54,8 @@ pub use self::{
|
||||||
title::Title,
|
title::Title,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
/// Org-mode element enum
|
/// Org-mode element enum
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
|
@ -95,10 +98,10 @@ pub enum Element<'a> {
|
||||||
Strike,
|
Strike,
|
||||||
Italic,
|
Italic,
|
||||||
Underline,
|
Underline,
|
||||||
Verbatim { value: &'a str },
|
Verbatim { value: Cow<'a, str> },
|
||||||
Code { value: &'a str },
|
Code { value: Cow<'a, str> },
|
||||||
Comment { value: &'a str },
|
Comment { value: Cow<'a, str> },
|
||||||
FixedWidth { value: &'a str },
|
FixedWidth { value: Cow<'a, str> },
|
||||||
Title(Title<'a>),
|
Title(Title<'a>),
|
||||||
Table(Table<'a>),
|
Table(Table<'a>),
|
||||||
TableRow(TableRow),
|
TableRow(TableRow),
|
||||||
|
@ -176,6 +179,7 @@ impl_from!(
|
||||||
Target,
|
Target,
|
||||||
Timestamp,
|
Timestamp,
|
||||||
Table,
|
Table,
|
||||||
|
Title,
|
||||||
VerseBlock;
|
VerseBlock;
|
||||||
RadioTarget,
|
RadioTarget,
|
||||||
List,
|
List,
|
||||||
|
|
|
@ -1,37 +1,28 @@
|
||||||
use nom::{bytes::complete::take_while_m_n, character::complete::space0, IResult};
|
use nom::{bytes::complete::take_while_m_n, IResult};
|
||||||
use std::usize;
|
use std::usize;
|
||||||
|
|
||||||
use crate::elements::Element;
|
|
||||||
use crate::parsers::eol;
|
use crate::parsers::eol;
|
||||||
|
|
||||||
pub struct Rule;
|
pub(crate) fn parse_rule(input: &str) -> IResult<&str, ()> {
|
||||||
|
|
||||||
impl Rule {
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn parse(input: &str) -> IResult<&str, Element<'_>> {
|
|
||||||
let (input, _) = space0(input)?;
|
|
||||||
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, Element::Rule))
|
Ok((input, ()))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse() {
|
fn parse() {
|
||||||
assert_eq!(Rule::parse("-----"), Ok(("", Element::Rule)));
|
assert_eq!(parse_rule("-----"), Ok(("", ())));
|
||||||
assert_eq!(Rule::parse("--------"), Ok(("", Element::Rule)));
|
assert_eq!(parse_rule("--------"), Ok(("", ())));
|
||||||
assert_eq!(Rule::parse(" -----"), Ok(("", Element::Rule)));
|
assert_eq!(parse_rule("-----\n"), Ok(("", ())));
|
||||||
assert_eq!(Rule::parse("\t\t-----"), Ok(("", Element::Rule)));
|
assert_eq!(parse_rule("----- \n"), Ok(("", ())));
|
||||||
assert_eq!(Rule::parse("\t\t-----\n"), Ok(("", Element::Rule)));
|
assert!(parse_rule("").is_err());
|
||||||
assert_eq!(Rule::parse("\t\t----- \n"), Ok(("", Element::Rule)));
|
assert!(parse_rule("----").is_err());
|
||||||
assert!(Rule::parse("").is_err());
|
assert!(parse_rule("----").is_err());
|
||||||
assert!(Rule::parse("----").is_err());
|
assert!(parse_rule("None----").is_err());
|
||||||
assert!(Rule::parse(" ----").is_err());
|
assert!(parse_rule("None ----").is_err());
|
||||||
assert!(Rule::parse(" None----").is_err());
|
assert!(parse_rule("None------").is_err());
|
||||||
assert!(Rule::parse("None ----").is_err());
|
assert!(parse_rule("----None----").is_err());
|
||||||
assert!(Rule::parse("None------").is_err());
|
assert!(parse_rule("\t\t----").is_err());
|
||||||
assert!(Rule::parse("----None----").is_err());
|
assert!(parse_rule("------None").is_err());
|
||||||
assert!(Rule::parse("\t\t----").is_err());
|
assert!(parse_rule("----- None").is_err());
|
||||||
assert!(Rule::parse("------None").is_err());
|
|
||||||
assert!(Rule::parse("----- None").is_err());
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,8 +6,6 @@ use nom::{
|
||||||
IResult,
|
IResult,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::elements::Element;
|
|
||||||
|
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -18,7 +16,7 @@ pub struct Snippet<'a> {
|
||||||
|
|
||||||
impl Snippet<'_> {
|
impl Snippet<'_> {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn parse(input: &str) -> IResult<&str, Element<'_>> {
|
pub(crate) fn parse(input: &str) -> IResult<&str, Snippet<'_>> {
|
||||||
let (input, (name, value)) = delimited(
|
let (input, (name, value)) = delimited(
|
||||||
tag("@@"),
|
tag("@@"),
|
||||||
separated_pair(
|
separated_pair(
|
||||||
|
@ -31,10 +29,10 @@ impl Snippet<'_> {
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
input,
|
input,
|
||||||
Element::Snippet(Snippet {
|
Snippet {
|
||||||
name: name.into(),
|
name: name.into(),
|
||||||
value: value.into(),
|
value: value.into(),
|
||||||
}),
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,40 +43,40 @@ fn parse() {
|
||||||
Snippet::parse("@@html:<b>@@"),
|
Snippet::parse("@@html:<b>@@"),
|
||||||
Ok((
|
Ok((
|
||||||
"",
|
"",
|
||||||
Element::Snippet(Snippet {
|
Snippet {
|
||||||
name: "html".into(),
|
name: "html".into(),
|
||||||
value: "<b>".into()
|
value: "<b>".into()
|
||||||
})
|
}
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Snippet::parse("@@latex:any arbitrary LaTeX code@@"),
|
Snippet::parse("@@latex:any arbitrary LaTeX code@@"),
|
||||||
Ok((
|
Ok((
|
||||||
"",
|
"",
|
||||||
Element::Snippet(Snippet {
|
Snippet {
|
||||||
name: "latex".into(),
|
name: "latex".into(),
|
||||||
value: "any arbitrary LaTeX code".into(),
|
value: "any arbitrary LaTeX code".into(),
|
||||||
})
|
}
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Snippet::parse("@@html:@@"),
|
Snippet::parse("@@html:@@"),
|
||||||
Ok((
|
Ok((
|
||||||
"",
|
"",
|
||||||
Element::Snippet(Snippet {
|
Snippet {
|
||||||
name: "html".into(),
|
name: "html".into(),
|
||||||
value: "".into(),
|
value: "".into(),
|
||||||
})
|
}
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Snippet::parse("@@html:<p>@</p>@@"),
|
Snippet::parse("@@html:<p>@</p>@@"),
|
||||||
Ok((
|
Ok((
|
||||||
"",
|
"",
|
||||||
Element::Snippet(Snippet {
|
Snippet {
|
||||||
name: "html".into(),
|
name: "html".into(),
|
||||||
value: "<p>@</p>".into(),
|
value: "<p>@</p>".into(),
|
||||||
})
|
}
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
assert!(Snippet::parse("@@html:<b>@").is_err());
|
assert!(Snippet::parse("@@html:<b>@").is_err());
|
||||||
|
|
|
@ -1,5 +1,12 @@
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
use nom::{
|
||||||
|
combinator::{peek, verify},
|
||||||
|
IResult,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::parsers::{line, take_lines_while};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
||||||
|
@ -34,3 +41,35 @@ impl TableRow {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn parse_table_el(input: &str) -> IResult<&str, &str> {
|
||||||
|
let (input, _) = peek(verify(line, |s: &str| {
|
||||||
|
let s = s.trim();
|
||||||
|
s.starts_with("+-") && s.as_bytes().iter().all(|&c| c == b'+' || c == b'-')
|
||||||
|
}))(input)?;
|
||||||
|
|
||||||
|
take_lines_while(|line| line.starts_with('|') || line.starts_with('+'))(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_table_el_() {
|
||||||
|
assert_eq!(
|
||||||
|
parse_table_el(
|
||||||
|
r#"+---+
|
||||||
|
| |
|
||||||
|
+---+
|
||||||
|
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
Ok((
|
||||||
|
r#"
|
||||||
|
"#,
|
||||||
|
r#"+---+
|
||||||
|
| |
|
||||||
|
+---+
|
||||||
|
"#
|
||||||
|
))
|
||||||
|
);
|
||||||
|
assert!(parse_table_el("").is_err());
|
||||||
|
assert!(parse_table_el("+----|---").is_err());
|
||||||
|
}
|
||||||
|
|
|
@ -7,8 +7,6 @@ use nom::{
|
||||||
IResult,
|
IResult,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::elements::Element;
|
|
||||||
|
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -18,7 +16,7 @@ pub struct Target<'a> {
|
||||||
|
|
||||||
impl Target<'_> {
|
impl Target<'_> {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn parse(input: &str) -> IResult<&str, Element<'_>> {
|
pub(crate) fn parse(input: &str) -> IResult<&str, Target<'_>> {
|
||||||
let (input, target) = delimited(
|
let (input, target) = delimited(
|
||||||
tag("<<"),
|
tag("<<"),
|
||||||
verify(
|
verify(
|
||||||
|
@ -30,9 +28,9 @@ impl Target<'_> {
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
input,
|
input,
|
||||||
Element::Target(Target {
|
Target {
|
||||||
target: target.into(),
|
target: target.into(),
|
||||||
}),
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,18 +41,18 @@ fn parse() {
|
||||||
Target::parse("<<target>>"),
|
Target::parse("<<target>>"),
|
||||||
Ok((
|
Ok((
|
||||||
"",
|
"",
|
||||||
Element::Target(Target {
|
Target {
|
||||||
target: "target".into()
|
target: "target".into()
|
||||||
})
|
}
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Target::parse("<<tar get>>"),
|
Target::parse("<<tar get>>"),
|
||||||
Ok((
|
Ok((
|
||||||
"",
|
"",
|
||||||
Element::Target(Target {
|
Target {
|
||||||
target: "tar get".into()
|
target: "tar get".into()
|
||||||
})
|
}
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
assert!(Target::parse("<<target >>").is_err());
|
assert!(Target::parse("<<target >>").is_err());
|
||||||
|
|
|
@ -17,7 +17,7 @@ use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::config::ParseConfig;
|
use crate::config::ParseConfig;
|
||||||
use crate::elements::{Drawer, Planning};
|
use crate::elements::{Drawer, Planning};
|
||||||
use crate::parsers::{skip_empty_lines, take_one_word, take_until_eol};
|
use crate::parsers::{line, skip_empty_lines, take_one_word};
|
||||||
|
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
||||||
|
@ -108,7 +108,8 @@ fn parse_headline<'a>(
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
))(input)?;
|
))(input)?;
|
||||||
let (input, tail) = take_until_eol(input)?;
|
let (input, tail) = line(input)?;
|
||||||
|
let tail = tail.trim();
|
||||||
let (raw, tags) = memrchr(b' ', tail.as_bytes())
|
let (raw, tags) = memrchr(b' ', tail.as_bytes())
|
||||||
.map(|i| (tail[0..i].trim(), &tail[i + 1..]))
|
.map(|i| (tail[0..i].trim(), &tail[i + 1..]))
|
||||||
.filter(|(_, x)| x.len() > 2 && x.starts_with(':') && x.ends_with(':'))
|
.filter(|(_, x)| x.len() > 2 && x.starts_with(':') && x.ends_with(':'))
|
||||||
|
@ -130,7 +131,7 @@ fn parse_headline<'a>(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_properties_drawer(input: &str) -> IResult<&str, HashMap<Cow<'_, str>, Cow<'_, str>>> {
|
fn parse_properties_drawer(input: &str) -> IResult<&str, HashMap<Cow<'_, str>, Cow<'_, str>>> {
|
||||||
let (input, (drawer, content)) = Drawer::parse(input)?;
|
let (input, (drawer, content)) = Drawer::parse(input.trim_start())?;
|
||||||
if drawer.name != "PROPERTIES" {
|
if drawer.name != "PROPERTIES" {
|
||||||
return Err(Err::Error(error_position!(input, ErrorKind::Tag)));
|
return Err(Err::Error(error_position!(input, ErrorKind::Tag)));
|
||||||
}
|
}
|
||||||
|
@ -146,12 +147,12 @@ fn parse_properties_drawer(input: &str) -> IResult<&str, HashMap<Cow<'_, str>, C
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_node_property(input: &str) -> IResult<&str, (&str, &str)> {
|
fn parse_node_property(input: &str) -> IResult<&str, (&str, &str)> {
|
||||||
let input = skip_empty_lines(input);
|
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('+')
|
||||||
})(input)?;
|
})(input)?;
|
||||||
let (input, value) = take_until_eol(input)?;
|
let (input, value) = line(input)?;
|
||||||
Ok((input, (name, value)))
|
Ok((input, (name, value.trim())))
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Title<'_> {
|
impl Title<'_> {
|
||||||
|
@ -228,6 +229,19 @@ fn parse_headline_() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_properties_drawer_() {
|
||||||
|
assert_eq!(
|
||||||
|
parse_properties_drawer(" :PROPERTIES:\n :CUSTOM_ID: id\n :END:"),
|
||||||
|
Ok((
|
||||||
|
"",
|
||||||
|
vec![("CUSTOM_ID".into(), "id".into())]
|
||||||
|
.into_iter()
|
||||||
|
.collect::<HashMap<_, _>>()
|
||||||
|
))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// #[test]
|
// #[test]
|
||||||
// fn is_commented() {
|
// fn is_commented() {
|
||||||
// assert!(Title::parse("* COMMENT Title", &CONFIG)
|
// assert!(Title::parse("* COMMENT Title", &CONFIG)
|
||||||
|
|
34
src/org.rs
34
src/org.rs
|
@ -9,7 +9,7 @@ use crate::parsers::*;
|
||||||
|
|
||||||
pub struct Org<'a> {
|
pub struct Org<'a> {
|
||||||
pub(crate) arena: Arena<Element<'a>>,
|
pub(crate) arena: Arena<Element<'a>>,
|
||||||
document: NodeId,
|
root: NodeId,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -25,12 +25,9 @@ impl Org<'_> {
|
||||||
|
|
||||||
pub fn parse_with_config<'a>(content: &'a str, config: &ParseConfig) -> Org<'a> {
|
pub fn parse_with_config<'a>(content: &'a str, config: &ParseConfig) -> Org<'a> {
|
||||||
let mut arena = Arena::new();
|
let mut arena = Arena::new();
|
||||||
let document = arena.new_node(Element::Document);
|
let node = arena.new_node(Element::Document);
|
||||||
|
|
||||||
let containers = &mut vec![Container::Document {
|
let containers = &mut vec![Container::Document { content, node }];
|
||||||
content,
|
|
||||||
node: document,
|
|
||||||
}];
|
|
||||||
|
|
||||||
while let Some(container) = containers.pop() {
|
while let Some(container) = containers.pop() {
|
||||||
match container {
|
match container {
|
||||||
|
@ -38,8 +35,7 @@ impl Org<'_> {
|
||||||
parse_section_and_headlines(&mut arena, content, node, containers);
|
parse_section_and_headlines(&mut arena, content, node, containers);
|
||||||
}
|
}
|
||||||
Container::Headline { content, node } => {
|
Container::Headline { content, node } => {
|
||||||
let content = parse_title(&mut arena, content, node, containers, config);
|
parse_headline_content(&mut arena, content, node, containers, config);
|
||||||
parse_section_and_headlines(&mut arena, content, node, containers);
|
|
||||||
}
|
}
|
||||||
Container::Block { content, node } => {
|
Container::Block { content, node } => {
|
||||||
parse_blocks(&mut arena, content, node, containers);
|
parse_blocks(&mut arena, content, node, containers);
|
||||||
|
@ -57,28 +53,24 @@ impl Org<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Org { arena, document }
|
Org { arena, root: node }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn iter<'a>(&'a self) -> impl Iterator<Item = Event<'_>> + 'a {
|
pub fn iter(&self) -> impl Iterator<Item = Event<'_>> + '_ {
|
||||||
self.document
|
self.root.traverse(&self.arena).map(move |edge| match edge {
|
||||||
.traverse(&self.arena)
|
|
||||||
.map(move |edge| match edge {
|
|
||||||
NodeEdge::Start(e) => Event::Start(self.arena[e].get()),
|
NodeEdge::Start(e) => Event::Start(self.arena[e].get()),
|
||||||
NodeEdge::End(e) => Event::End(self.arena[e].get()),
|
NodeEdge::End(e) => Event::End(self.arena[e].get()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn headlines(&self) -> Vec<HeadlineNode> {
|
pub fn headlines(&self) -> impl Iterator<Item = HeadlineNode> + '_ {
|
||||||
self.document
|
self.root
|
||||||
.descendants(&self.arena)
|
.descendants(&self.arena)
|
||||||
.skip(1)
|
.skip(1)
|
||||||
.filter(|&node| match self.arena[node].get() {
|
.filter_map(move |node| match self.arena[node].get() {
|
||||||
Element::Headline => true,
|
Element::Headline => Some(HeadlineNode(node)),
|
||||||
_ => false,
|
_ => None,
|
||||||
})
|
})
|
||||||
.map(|node| HeadlineNode(node))
|
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn html<W: Write>(&self, wrtier: W) -> Result<(), Error> {
|
pub fn html<W: Write>(&self, wrtier: W) -> Result<(), Error> {
|
||||||
|
@ -130,6 +122,6 @@ impl Serialize for Org<'_> {
|
||||||
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||||
use serde_indextree::Node;
|
use serde_indextree::Node;
|
||||||
|
|
||||||
serializer.serialize_newtype_struct("Node", &Node::new(self.document, &self.arena))
|
serializer.serialize_newtype_struct("Org", &Node::new(self.root, &self.arena))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
812
src/parsers.rs
812
src/parsers.rs
File diff suppressed because it is too large
Load diff
|
@ -23,22 +23,18 @@ test_suite!(
|
||||||
|
|
||||||
test_suite!(
|
test_suite!(
|
||||||
section_and_headline,
|
section_and_headline,
|
||||||
r#"* Title 1
|
r#"* title 1
|
||||||
*Section 1*
|
section 1
|
||||||
** Title 2
|
** title 2
|
||||||
_Section 2_
|
section 2
|
||||||
* Title 3
|
* title 3
|
||||||
/Section 3/
|
section 3
|
||||||
* Title 4
|
* title 4
|
||||||
=Section 4="#,
|
section 4"#,
|
||||||
"<main><h1>Title 1</h1>\
|
"<main><h1>title 1</h1><section><p>section 1</p></section>\
|
||||||
<section><p><b>Section 1</b></p></section>\
|
<h2>title 2</h2><section><p>section 2</p></section>\
|
||||||
<h2>Title 2</h2>\
|
<h1>title 3</h1><section><p>section 3</p></section>\
|
||||||
<section><p><u>Section 2</u></p></section>\
|
<h1>title 4</h1><section><p>section 4</p></section></main>"
|
||||||
<h1>Title 3</h1>\
|
|
||||||
<section><p><i>Section 3</i></p></section>\
|
|
||||||
<h1>Title 4</h1>\
|
|
||||||
<section><p><code>Section 4</code></p></section></main>"
|
|
||||||
);
|
);
|
||||||
|
|
||||||
test_suite!(
|
test_suite!(
|
||||||
|
|
Loading…
Reference in a new issue