feat(elements): cleanup and minor refactor

This commit is contained in:
PoiScript 2019-08-09 22:15:06 +08:00
parent c1465a6d77
commit 9bc260627b
20 changed files with 802 additions and 835 deletions

View file

@ -2,71 +2,7 @@ use std::borrow::Cow;
use nom::{bytes::complete::tag_no_case, character::complete::alpha1, sequence::preceded, IResult};
use crate::parsers::{take_lines_till, take_until_eol};
#[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
}
use crate::parsers::{line, take_lines_while};
#[derive(Debug)]
#[cfg_attr(test, derive(PartialEq))]
@ -129,3 +65,60 @@ pub struct SourceBlock<'a> {
pub language: 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
}

View file

@ -8,7 +8,7 @@ use nom::{
IResult,
};
use crate::elements::{Datetime, Element, Timestamp};
use crate::elements::{Datetime, Timestamp};
use crate::parsers::eol;
/// clock elements
@ -40,7 +40,7 @@ pub enum Clock<'a> {
}
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, _) = space0(input)?;
let (input, timestamp) = Timestamp::parse_inactive(input)?;
@ -60,13 +60,13 @@ impl Clock<'_> {
let (input, _) = eol(input)?;
Ok((
input,
Element::Clock(Clock::Closed {
Clock::Closed {
start,
end,
repeater,
delay,
duration: duration.into(),
}),
},
))
}
Timestamp::Inactive {
@ -77,11 +77,11 @@ impl Clock<'_> {
let (input, _) = eol(input)?;
Ok((
input,
Element::Clock(Clock::Running {
Clock::Running {
start,
repeater,
delay,
}),
},
))
}
_ => unreachable!(
@ -148,7 +148,7 @@ fn parse() {
Clock::parse("CLOCK: [2003-09-16 Tue 09:39]"),
Ok((
"",
Element::Clock(Clock::Running {
Clock::Running {
start: Datetime {
year: 2003,
month: 9,
@ -159,14 +159,14 @@ fn parse() {
},
repeater: None,
delay: None,
})
}
))
);
assert_eq!(
Clock::parse("CLOCK: [2003-09-16 Tue 09:39]--[2003-09-16 Tue 10:39] => 1:00"),
Ok((
"",
Element::Clock(Clock::Closed {
Clock::Closed {
start: Datetime {
year: 2003,
month: 9,
@ -186,7 +186,7 @@ fn parse() {
repeater: None,
delay: None,
duration: "1:00".into(),
})
}
))
);
}

View file

@ -1,6 +1,6 @@
use std::borrow::Cow;
use crate::parsers::{eol, take_lines_till};
use crate::parsers::{eol, line, take_lines_while};
use nom::{
bytes::complete::{tag, take_while1},
@ -24,7 +24,9 @@ impl Drawer<'_> {
tag(":"),
)(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)))
}

View file

@ -1,7 +1,6 @@
use std::borrow::Cow;
use crate::elements::Element;
use crate::parsers::{take_lines_till, take_until_eol};
use crate::parsers::{line, take_lines_while};
use nom::{
bytes::complete::tag_no_case,
@ -20,25 +19,26 @@ pub struct DynBlock<'a> {
impl DynBlock<'_> {
#[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, _) = space1(input)?;
let (input, name) = alpha1(input)?;
let (input, args) = take_until_eol(input)?;
let (input, contents) = take_lines_till(|line| line.eq_ignore_ascii_case("#+END:"))(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,
(
Element::DynBlock(DynBlock {
DynBlock {
block_name: name.into(),
arguments: if args.is_empty() {
arguments: if args.trim().is_empty() {
None
} else {
Some(args.into())
Some(args.trim().into())
},
}),
},
contents,
),
))
@ -49,14 +49,19 @@ impl DynBlock<'_> {
fn parse() {
// TODO: testing
assert_eq!(
DynBlock::parse("#+BEGIN: clocktable :scope file\nCONTENTS\n#+END:\n"),
DynBlock::parse(
r#"#+BEGIN: clocktable :scope file
CONTENTS
#+END:
"#
),
Ok((
"",
(
Element::DynBlock(DynBlock {
DynBlock {
block_name: "clocktable".into(),
arguments: Some(":scope file".into()),
}),
},
"CONTENTS\n"
)
))

View file

@ -2,7 +2,7 @@ use bytecount::count;
use memchr::memchr_iter;
#[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);
let bytes = text.as_bytes();
@ -35,19 +35,14 @@ fn validate_marker(pos: usize, text: &str) -> bool {
}
}
#[cfg(test)]
mod tests {
#[test]
fn parse() {
use super::parse;
assert_eq!(parse("*bold*", b'*'), Some(("", "bold")));
assert_eq!(parse("*bo*ld*", b'*'), Some(("", "bo*ld")));
assert_eq!(parse("*bo\nld*", b'*'), Some(("", "bo\nld")));
assert_eq!(parse("*bold*a", b'*'), None);
assert_eq!(parse("*bold*", b'/'), None);
assert_eq!(parse("*bold *", b'*'), None);
assert_eq!(parse("* bold*", b'*'), None);
assert_eq!(parse("*b\nol\nd*", b'*'), None);
}
#[test]
fn parse() {
assert_eq!(parse_emphasis("*bold*", b'*'), Some(("", "bold")));
assert_eq!(parse_emphasis("*bo*ld*", b'*'), Some(("", "bo*ld")));
assert_eq!(parse_emphasis("*bo\nld*", b'*'), Some(("", "bo\nld")));
assert_eq!(parse_emphasis("*bold*a", b'*'), None);
assert_eq!(parse_emphasis("*bold*", b'/'), None);
assert_eq!(parse_emphasis("*bold *", b'*'), None);
assert_eq!(parse_emphasis("* bold*", b'*'), None);
assert_eq!(parse_emphasis("*b\nol\nd*", b'*'), None);
}

View file

@ -1,12 +1,13 @@
use std::borrow::Cow;
use memchr::memchr;
use nom::{
bytes::complete::{tag, take_while1},
sequence::delimited,
IResult,
};
use crate::parsers::line;
#[cfg_attr(test, derive(PartialEq))]
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
#[derive(Debug)]
@ -14,29 +15,24 @@ pub struct FnDef<'a> {
pub label: Cow<'a, str>,
}
fn parse_label(input: &str) -> IResult<&str, &str> {
let (input, label) = delimited(
tag("[fn:"),
take_while1(|c: char| c.is_ascii_alphanumeric() || c == '-' || c == '_'),
tag("]"),
)(input)?;
Ok((input, label))
}
impl FnDef<'_> {
#[inline]
pub(crate) fn parse(text: &str) -> Option<(&str, FnDef<'_>, &str)> {
let (tail, label) = parse_label(text).ok()?;
pub(crate) fn parse(input: &str) -> IResult<&str, (FnDef<'_>, &str)> {
let (input, label) = delimited(
tag("[fn:"),
take_while1(|c: char| c.is_ascii_alphanumeric() || c == '-' || c == '_'),
tag("]"),
)(input)?;
let (input, content) = line(input)?;
let end = memchr(b'\n', tail.as_bytes()).unwrap_or_else(|| tail.len());
Some((
&tail[end..],
FnDef {
label: label.into(),
},
&tail[0..end],
Ok((
input,
(
FnDef {
label: label.into(),
},
content,
),
))
}
}
@ -45,39 +41,46 @@ impl FnDef<'_> {
fn parse() {
assert_eq!(
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!(
FnDef::parse("[fn:word_1] https://orgmode.org"),
Some((
Ok((
"",
FnDef {
label: "word_1".into()
},
" https://orgmode.org"
(
FnDef {
label: "word_1".into()
},
" https://orgmode.org"
)
))
);
assert_eq!(
FnDef::parse("[fn:WORD-1] https://orgmode.org"),
Some((
Ok((
"",
FnDef {
label: "WORD-1".into()
},
" https://orgmode.org"
(
FnDef {
label: "WORD-1".into()
},
" https://orgmode.org"
)
))
);
assert_eq!(
FnDef::parse("[fn:WORD]"),
Some((
Ok((
"",
FnDef {
label: "WORD".into()
},
""
(
FnDef {
label: "WORD".into()
},
""
)
))
);
assert_eq!(FnDef::parse("[fn:] https://orgmode.org"), None);
assert_eq!(FnDef::parse("[fn:wor d] https://orgmode.org"), None);
assert_eq!(FnDef::parse("[fn:WORD https://orgmode.org"), None);
assert!(FnDef::parse("[fn:] https://orgmode.org").is_err());
assert!(FnDef::parse("[fn:wor d] https://orgmode.org").is_err());
assert!(FnDef::parse("[fn:WORD https://orgmode.org").is_err());
}

View file

@ -7,8 +7,6 @@ use nom::{
IResult,
};
use crate::elements::Element;
#[cfg_attr(test, derive(PartialEq))]
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
#[derive(Debug)]
@ -21,9 +19,9 @@ pub struct InlineCall<'a> {
pub end_header: Option<Cow<'a, str>>,
}
impl<'a> InlineCall<'a> {
impl InlineCall<'_> {
#[inline]
pub(crate) fn parse(input: &str) -> IResult<&str, Element<'_>> {
pub(crate) fn parse(input: &str) -> IResult<&str, InlineCall<'_>> {
let (input, name) = preceded(
tag("call_"),
take_till(|c| c == '[' || c == '\n' || c == '(' || c == ')'),
@ -43,12 +41,12 @@ impl<'a> InlineCall<'a> {
Ok((
input,
Element::InlineCall(InlineCall {
InlineCall {
name: name.into(),
arguments: arguments.into(),
inside_header: inside_header.map(Into::into),
end_header: end_header.map(Into::into),
}),
},
))
}
}
@ -59,48 +57,48 @@ fn parse() {
InlineCall::parse("call_square(4)"),
Ok((
"",
Element::InlineCall(InlineCall {
InlineCall {
name: "square".into(),
arguments: "4".into(),
inside_header: None,
end_header: None,
}),
}
))
);
assert_eq!(
InlineCall::parse("call_square[:results output](4)"),
Ok((
"",
Element::InlineCall(InlineCall {
InlineCall {
name: "square".into(),
arguments: "4".into(),
inside_header: Some(":results output".into()),
end_header: None,
}),
},
))
);
assert_eq!(
InlineCall::parse("call_square(4)[:results html]"),
Ok((
"",
Element::InlineCall(InlineCall {
InlineCall {
name: "square".into(),
arguments: "4".into(),
inside_header: None,
end_header: Some(":results html".into()),
}),
},
))
);
assert_eq!(
InlineCall::parse("call_square[:results output](4)[:results html]"),
Ok((
"",
Element::InlineCall(InlineCall {
InlineCall {
name: "square".into(),
arguments: "4".into(),
inside_header: Some(":results output".into()),
end_header: Some(":results html".into()),
}),
},
))
);
}

View file

@ -7,8 +7,6 @@ use nom::{
IResult,
};
use crate::elements::Element;
#[cfg_attr(test, derive(PartialEq))]
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
#[derive(Debug)]
@ -21,7 +19,7 @@ pub struct InlineSrc<'a> {
impl InlineSrc<'_> {
#[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, lang) =
take_while1(|c: char| !c.is_ascii_whitespace() && c != '[' && c != '{')(input)?;
@ -35,11 +33,11 @@ impl InlineSrc<'_> {
Ok((
input,
Element::InlineSrc(InlineSrc {
InlineSrc {
lang: lang.into(),
options: options.map(Into::into),
body: body.into(),
}),
},
))
}
}
@ -50,22 +48,22 @@ fn parse() {
InlineSrc::parse("src_C{int a = 0;}"),
Ok((
"",
Element::InlineSrc(InlineSrc {
InlineSrc {
lang: "C".into(),
options: None,
body: "int a = 0;".into()
}),
},
))
);
assert_eq!(
InlineSrc::parse("src_xml[:exports code]{<tag>text</tag>}"),
Ok((
"",
Element::InlineSrc(InlineSrc {
InlineSrc {
lang: "xml".into(),
options: Some(":exports code".into()),
body: "<tag>text</tag>".into(),
}),
},
))
);

View file

@ -7,8 +7,7 @@ use nom::{
IResult,
};
use crate::elements::Element;
use crate::parsers::take_until_eol;
use crate::parsers::line;
#[cfg_attr(test, derive(PartialEq))]
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
@ -27,132 +26,52 @@ pub struct BabelCall<'a> {
pub value: Cow<'a, str>,
}
impl Keyword<'_> {
#[inline]
pub(crate) fn parse(input: &str) -> IResult<&str, Element<'_>> {
let (input, _) = tag("#+")(input)?;
let (input, key) =
take_till(|c: char| c.is_ascii_whitespace() || c == ':' || c == '[')(input)?;
let (input, optional) = opt(delimited(
tag("["),
take_till(|c| c == ']' || c == '\n'),
tag("]"),
))(input)?;
let (input, _) = tag(":")(input)?;
let (input, value) = take_until_eol(input)?;
pub(crate) fn parse_keyword(input: &str) -> IResult<&str, (&str, Option<&str>, &str)> {
let (input, _) = tag("#+")(input)?;
let (input, key) = take_till(|c: char| c.is_ascii_whitespace() || c == ':' || c == '[')(input)?;
let (input, optional) = opt(delimited(
tag("["),
take_till(|c| c == ']' || c == '\n'),
tag("]"),
))(input)?;
let (input, _) = tag(":")(input)?;
let (input, value) = line(input)?;
if key.eq_ignore_ascii_case("CALL") {
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(),
}),
))
}
}
Ok((input, (key, optional, value.trim())))
}
#[test]
fn parse() {
assert_eq!(parse_keyword("#+KEY:"), Ok(("", ("KEY", None, ""))));
assert_eq!(
Keyword::parse("#+KEY:"),
Ok((
"",
Element::Keyword(Keyword {
key: "KEY".into(),
optional: None,
value: "".into(),
})
))
parse_keyword("#+KEY: VALUE"),
Ok(("", ("KEY", None, "VALUE")))
);
assert_eq!(
Keyword::parse("#+KEY: VALUE"),
Ok((
"",
Element::Keyword(Keyword {
key: "KEY".into(),
optional: None,
value: "VALUE".into(),
})
))
parse_keyword("#+K_E_Y: VALUE"),
Ok(("", ("K_E_Y", None, "VALUE")))
);
assert_eq!(
Keyword::parse("#+K_E_Y: VALUE"),
Ok((
"",
Element::Keyword(Keyword {
key: "K_E_Y".into(),
optional: None,
value: "VALUE".into(),
})
))
parse_keyword("#+KEY:VALUE\n"),
Ok(("", ("KEY", None, "VALUE")))
);
assert_eq!(
Keyword::parse("#+KEY:VALUE\n"),
Ok((
"",
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!(parse_keyword("#+KE Y: VALUE").is_err());
assert!(parse_keyword("#+ KEY: VALUE").is_err());
assert_eq!(parse_keyword("#+RESULTS:"), Ok(("", ("RESULTS", None, ""))));
assert_eq!(
Keyword::parse("#+RESULTS:"),
Ok((
"",
Element::Keyword(Keyword {
key: "RESULTS".into(),
optional: None,
value: "".into(),
})
))
parse_keyword("#+ATTR_LATEX: :width 5cm\n"),
Ok(("", ("ATTR_LATEX", None, ":width 5cm")))
);
assert_eq!(
Keyword::parse("#+ATTR_LATEX: :width 5cm\n"),
Ok((
"",
Element::Keyword(Keyword {
key: "ATTR_LATEX".into(),
optional: None,
value: ":width 5cm".into(),
})
))
parse_keyword("#+CALL: double(n=4)"),
Ok(("", ("CALL", None, "double(n=4)")))
);
assert_eq!(
Keyword::parse("#+CALL: double(n=4)"),
Ok((
"",
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(),
})
))
parse_keyword("#+CAPTION[Short caption]: Longer caption."),
Ok(("", ("CAPTION", Some("Short caption"), "Longer caption.",)))
);
}

View file

@ -7,8 +7,6 @@ use nom::{
IResult,
};
use crate::elements::Element;
#[cfg_attr(test, derive(PartialEq))]
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
#[derive(Debug)]
@ -20,7 +18,7 @@ pub struct Link<'a> {
impl Link<'_> {
#[inline]
pub(crate) fn parse(input: &str) -> IResult<&str, Element<'_>> {
pub(crate) fn parse(input: &str) -> IResult<&str, Link<'_>> {
let (input, path) = delimited(
tag("[["),
take_while(|c: char| c != '<' && c != '>' && c != '\n' && c != ']'),
@ -34,10 +32,10 @@ impl Link<'_> {
let (input, _) = tag("]")(input)?;
Ok((
input,
Element::Link(Link {
Link {
path: path.into(),
desc: desc.map(Into::into),
}),
},
))
}
}
@ -48,20 +46,20 @@ fn parse() {
Link::parse("[[#id]]"),
Ok((
"",
Element::Link(Link {
Link {
path: "#id".into(),
desc: None
},)
}
))
);
assert_eq!(
Link::parse("[[#id][desc]]"),
Ok((
"",
Element::Link(Link {
Link {
path: "#id".into(),
desc: Some("desc".into())
})
}
))
);
assert!(Link::parse("[[#id][desc]").is_err());

View file

@ -7,8 +7,6 @@ use nom::{
IResult,
};
use crate::elements::Element;
#[cfg_attr(test, derive(PartialEq))]
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
#[derive(Debug)]
@ -20,7 +18,7 @@ pub struct Macros<'a> {
impl Macros<'_> {
#[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, name) = verify(
take_while1(|c: char| c.is_ascii_alphanumeric() || c == '-' || c == '_'),
@ -31,10 +29,10 @@ impl Macros<'_> {
Ok((
input,
Element::Macros(Macros {
Macros {
name: name.into(),
arguments: arguments.map(Into::into),
}),
},
))
}
}
@ -45,30 +43,30 @@ fn parse() {
Macros::parse("{{{poem(red,blue)}}}"),
Ok((
"",
Element::Macros(Macros {
Macros {
name: "poem".into(),
arguments: Some("red,blue".into())
})
}
))
);
assert_eq!(
Macros::parse("{{{poem())}}}"),
Ok((
"",
Element::Macros(Macros {
Macros {
name: "poem".into(),
arguments: Some(")".into())
})
}
))
);
assert_eq!(
Macros::parse("{{{author}}}"),
Ok((
"",
Element::Macros(Macros {
Macros {
name: "author".into(),
arguments: None
})
}
))
);
assert!(Macros::parse("{{{0uthor}}}").is_err());

View file

@ -23,8 +23,10 @@ mod target;
mod timestamp;
mod title;
pub(crate) use block::Block;
pub(crate) use emphasis::parse as parse_emphasis;
pub(crate) use self::{
block::parse_block_element, emphasis::parse_emphasis, keyword::parse_keyword, rule::parse_rule,
table::parse_table_el,
};
pub use self::{
block::{
@ -45,7 +47,6 @@ pub use self::{
macros::Macros,
planning::Planning,
radio_target::RadioTarget,
rule::Rule,
snippet::Snippet,
table::{Table, TableRow},
target::Target,
@ -53,6 +54,8 @@ pub use self::{
title::Title,
};
use std::borrow::Cow;
/// Org-mode element enum
#[derive(Debug)]
#[cfg_attr(test, derive(PartialEq))]
@ -95,10 +98,10 @@ pub enum Element<'a> {
Strike,
Italic,
Underline,
Verbatim { value: &'a str },
Code { value: &'a str },
Comment { value: &'a str },
FixedWidth { value: &'a str },
Verbatim { value: Cow<'a, str> },
Code { value: Cow<'a, str> },
Comment { value: Cow<'a, str> },
FixedWidth { value: Cow<'a, str> },
Title(Title<'a>),
Table(Table<'a>),
TableRow(TableRow),
@ -176,6 +179,7 @@ impl_from!(
Target,
Timestamp,
Table,
Title,
VerseBlock;
RadioTarget,
List,

View file

@ -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 crate::elements::Element;
use crate::parsers::eol;
pub struct Rule;
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, _) = eol(input)?;
Ok((input, Element::Rule))
}
pub(crate) fn parse_rule(input: &str) -> IResult<&str, ()> {
let (input, _) = take_while_m_n(5, usize::MAX, |c| c == '-')(input)?;
let (input, _) = eol(input)?;
Ok((input, ()))
}
#[test]
fn parse() {
assert_eq!(Rule::parse("-----"), Ok(("", Element::Rule)));
assert_eq!(Rule::parse("--------"), Ok(("", Element::Rule)));
assert_eq!(Rule::parse(" -----"), Ok(("", Element::Rule)));
assert_eq!(Rule::parse("\t\t-----"), Ok(("", Element::Rule)));
assert_eq!(Rule::parse("\t\t-----\n"), Ok(("", Element::Rule)));
assert_eq!(Rule::parse("\t\t----- \n"), Ok(("", Element::Rule)));
assert!(Rule::parse("").is_err());
assert!(Rule::parse("----").is_err());
assert!(Rule::parse(" ----").is_err());
assert!(Rule::parse(" None----").is_err());
assert!(Rule::parse("None ----").is_err());
assert!(Rule::parse("None------").is_err());
assert!(Rule::parse("----None----").is_err());
assert!(Rule::parse("\t\t----").is_err());
assert!(Rule::parse("------None").is_err());
assert!(Rule::parse("----- None").is_err());
assert_eq!(parse_rule("-----"), Ok(("", ())));
assert_eq!(parse_rule("--------"), Ok(("", ())));
assert_eq!(parse_rule("-----\n"), Ok(("", ())));
assert_eq!(parse_rule("----- \n"), Ok(("", ())));
assert!(parse_rule("").is_err());
assert!(parse_rule("----").is_err());
assert!(parse_rule("----").is_err());
assert!(parse_rule("None----").is_err());
assert!(parse_rule("None ----").is_err());
assert!(parse_rule("None------").is_err());
assert!(parse_rule("----None----").is_err());
assert!(parse_rule("\t\t----").is_err());
assert!(parse_rule("------None").is_err());
assert!(parse_rule("----- None").is_err());
}

View file

@ -6,8 +6,6 @@ use nom::{
IResult,
};
use crate::elements::Element;
#[cfg_attr(test, derive(PartialEq))]
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
#[derive(Debug)]
@ -18,7 +16,7 @@ pub struct Snippet<'a> {
impl Snippet<'_> {
#[inline]
pub(crate) fn parse(input: &str) -> IResult<&str, Element<'_>> {
pub(crate) fn parse(input: &str) -> IResult<&str, Snippet<'_>> {
let (input, (name, value)) = delimited(
tag("@@"),
separated_pair(
@ -31,10 +29,10 @@ impl Snippet<'_> {
Ok((
input,
Element::Snippet(Snippet {
Snippet {
name: name.into(),
value: value.into(),
}),
},
))
}
}
@ -45,40 +43,40 @@ fn parse() {
Snippet::parse("@@html:<b>@@"),
Ok((
"",
Element::Snippet(Snippet {
Snippet {
name: "html".into(),
value: "<b>".into()
})
}
))
);
assert_eq!(
Snippet::parse("@@latex:any arbitrary LaTeX code@@"),
Ok((
"",
Element::Snippet(Snippet {
Snippet {
name: "latex".into(),
value: "any arbitrary LaTeX code".into(),
})
}
))
);
assert_eq!(
Snippet::parse("@@html:@@"),
Ok((
"",
Element::Snippet(Snippet {
Snippet {
name: "html".into(),
value: "".into(),
})
}
))
);
assert_eq!(
Snippet::parse("@@html:<p>@</p>@@"),
Ok((
"",
Element::Snippet(Snippet {
Snippet {
name: "html".into(),
value: "<p>@</p>".into(),
})
}
))
);
assert!(Snippet::parse("@@html:<b>@").is_err());

View file

@ -1,5 +1,12 @@
use std::borrow::Cow;
use nom::{
combinator::{peek, verify},
IResult,
};
use crate::parsers::{line, take_lines_while};
#[derive(Debug)]
#[cfg_attr(test, derive(PartialEq))]
#[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());
}

View file

@ -7,8 +7,6 @@ use nom::{
IResult,
};
use crate::elements::Element;
#[cfg_attr(test, derive(PartialEq))]
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
#[derive(Debug)]
@ -18,7 +16,7 @@ pub struct Target<'a> {
impl Target<'_> {
#[inline]
pub(crate) fn parse(input: &str) -> IResult<&str, Element<'_>> {
pub(crate) fn parse(input: &str) -> IResult<&str, Target<'_>> {
let (input, target) = delimited(
tag("<<"),
verify(
@ -30,9 +28,9 @@ impl Target<'_> {
Ok((
input,
Element::Target(Target {
Target {
target: target.into(),
}),
},
))
}
}
@ -43,18 +41,18 @@ fn parse() {
Target::parse("<<target>>"),
Ok((
"",
Element::Target(Target {
Target {
target: "target".into()
})
}
))
);
assert_eq!(
Target::parse("<<tar get>>"),
Ok((
"",
Element::Target(Target {
Target {
target: "tar get".into()
})
}
))
);
assert!(Target::parse("<<target >>").is_err());

View file

@ -17,7 +17,7 @@ use std::collections::HashMap;
use crate::config::ParseConfig;
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(feature = "ser", derive(serde::Serialize))]
@ -108,7 +108,8 @@ fn parse_headline<'a>(
),
),
))(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())
.map(|i| (tail[0..i].trim(), &tail[i + 1..]))
.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>>> {
let (input, (drawer, content)) = Drawer::parse(input)?;
let (input, (drawer, content)) = Drawer::parse(input.trim_start())?;
if drawer.name != "PROPERTIES" {
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)> {
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| {
s.trim_end_matches('+')
})(input)?;
let (input, value) = take_until_eol(input)?;
Ok((input, (name, value)))
let (input, value) = line(input)?;
Ok((input, (name, value.trim())))
}
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]
// fn is_commented() {
// assert!(Title::parse("* COMMENT Title", &CONFIG)

View file

@ -9,7 +9,7 @@ use crate::parsers::*;
pub struct Org<'a> {
pub(crate) arena: Arena<Element<'a>>,
document: NodeId,
root: NodeId,
}
#[derive(Debug)]
@ -25,12 +25,9 @@ impl Org<'_> {
pub fn parse_with_config<'a>(content: &'a str, config: &ParseConfig) -> Org<'a> {
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 {
content,
node: document,
}];
let containers = &mut vec![Container::Document { content, node }];
while let Some(container) = containers.pop() {
match container {
@ -38,8 +35,7 @@ impl Org<'_> {
parse_section_and_headlines(&mut arena, content, node, containers);
}
Container::Headline { content, node } => {
let content = parse_title(&mut arena, content, node, containers, config);
parse_section_and_headlines(&mut arena, content, node, containers);
parse_headline_content(&mut arena, content, node, containers, config);
}
Container::Block { content, node } => {
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 {
self.document
.traverse(&self.arena)
.map(move |edge| match edge {
NodeEdge::Start(e) => Event::Start(self.arena[e].get()),
NodeEdge::End(e) => Event::End(self.arena[e].get()),
})
pub fn iter(&self) -> impl Iterator<Item = Event<'_>> + '_ {
self.root.traverse(&self.arena).map(move |edge| match edge {
NodeEdge::Start(e) => Event::Start(self.arena[e].get()),
NodeEdge::End(e) => Event::End(self.arena[e].get()),
})
}
pub fn headlines(&self) -> Vec<HeadlineNode> {
self.document
pub fn headlines(&self) -> impl Iterator<Item = HeadlineNode> + '_ {
self.root
.descendants(&self.arena)
.skip(1)
.filter(|&node| match self.arena[node].get() {
Element::Headline => true,
_ => false,
.filter_map(move |node| match self.arena[node].get() {
Element::Headline => Some(HeadlineNode(node)),
_ => None,
})
.map(|node| HeadlineNode(node))
.collect()
}
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> {
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))
}
}

File diff suppressed because it is too large Load diff

View file

@ -23,22 +23,18 @@ test_suite!(
test_suite!(
section_and_headline,
r#"* Title 1
*Section 1*
** Title 2
_Section 2_
* Title 3
/Section 3/
* Title 4
=Section 4="#,
"<main><h1>Title 1</h1>\
<section><p><b>Section 1</b></p></section>\
<h2>Title 2</h2>\
<section><p><u>Section 2</u></p></section>\
<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>"
r#"* title 1
section 1
** title 2
section 2
* title 3
section 3
* title 4
section 4"#,
"<main><h1>title 1</h1><section><p>section 1</p></section>\
<h2>title 2</h2><section><p>section 2</p></section>\
<h1>title 3</h1><section><p>section 3</p></section>\
<h1>title 4</h1><section><p>section 4</p></section></main>"
);
test_suite!(