refactor(elements): cleanup and minor improvements
This commit is contained in:
parent
37c33a82f0
commit
472676d6d7
|
@ -1,8 +1,8 @@
|
||||||
use nom::sequence::separated_pair;
|
|
||||||
use nom::{
|
use nom::{
|
||||||
bytes::complete::tag,
|
bytes::complete::tag,
|
||||||
character::complete::{char, digit1, space0},
|
character::complete::{char, digit1, space0},
|
||||||
combinator::{peek, recognize},
|
combinator::{peek, recognize},
|
||||||
|
sequence::separated_pair,
|
||||||
IResult,
|
IResult,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -14,20 +14,25 @@ use crate::parsers::eol;
|
||||||
/// there are two types of clock: *closed* clock and *running* clock.
|
/// there are two types of clock: *closed* clock and *running* clock.
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
|
||||||
|
#[cfg_attr(feature = "serde", serde(untagged))]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Clock<'a> {
|
pub enum Clock<'a> {
|
||||||
/// closed Clock
|
/// closed Clock
|
||||||
Closed {
|
Closed {
|
||||||
start: Datetime<'a>,
|
start: Datetime<'a>,
|
||||||
end: Datetime<'a>,
|
end: Datetime<'a>,
|
||||||
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
repeater: Option<&'a str>,
|
repeater: Option<&'a str>,
|
||||||
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
delay: Option<&'a str>,
|
delay: Option<&'a str>,
|
||||||
duration: &'a str,
|
duration: &'a str,
|
||||||
},
|
},
|
||||||
/// running Clock
|
/// running Clock
|
||||||
Running {
|
Running {
|
||||||
start: Datetime<'a>,
|
start: Datetime<'a>,
|
||||||
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
repeater: Option<&'a str>,
|
repeater: Option<&'a str>,
|
||||||
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
delay: Option<&'a str>,
|
delay: Option<&'a str>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
use crate::elements::Element;
|
|
||||||
use crate::parsers::{eol, take_lines_till};
|
use crate::parsers::{eol, take_lines_till};
|
||||||
|
|
||||||
use nom::{
|
use nom::{
|
||||||
|
@ -16,7 +15,7 @@ pub struct Drawer<'a> {
|
||||||
|
|
||||||
impl Drawer<'_> {
|
impl Drawer<'_> {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn parse(input: &str) -> IResult<&str, (Element<'_>, &str)> {
|
pub(crate) fn parse(input: &str) -> IResult<&str, (Drawer<'_>, &str)> {
|
||||||
let (input, name) = delimited(
|
let (input, name) = delimited(
|
||||||
tag(":"),
|
tag(":"),
|
||||||
take_while1(|c: char| c.is_ascii_alphabetic() || c == '-' || c == '_'),
|
take_while1(|c: char| c.is_ascii_alphabetic() || c == '-' || c == '_'),
|
||||||
|
@ -25,7 +24,7 @@ impl Drawer<'_> {
|
||||||
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_till(|line| line.eq_ignore_ascii_case(":END:"))(input)?;
|
||||||
|
|
||||||
Ok((input, (Element::Drawer(Drawer { name }), contents)))
|
Ok((input, (Drawer { name }, contents)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,12 +32,6 @@ impl Drawer<'_> {
|
||||||
fn parse() {
|
fn parse() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Drawer::parse(":PROPERTIES:\n :CUSTOM_ID: id\n :END:"),
|
Drawer::parse(":PROPERTIES:\n :CUSTOM_ID: id\n :END:"),
|
||||||
Ok((
|
Ok(("", (Drawer { name: "PROPERTIES" }, " :CUSTOM_ID: id\n")))
|
||||||
"",
|
|
||||||
(
|
|
||||||
Element::Drawer(Drawer { name: "PROPERTIES" }),
|
|
||||||
" :CUSTOM_ID: id\n"
|
|
||||||
)
|
|
||||||
))
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use nom::{
|
use nom::{
|
||||||
bytes::complete::{tag, take_till},
|
bytes::complete::{tag, take_till},
|
||||||
combinator::opt,
|
combinator::opt,
|
||||||
sequence::delimited,
|
sequence::{delimited, preceded},
|
||||||
IResult,
|
IResult,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -19,19 +19,25 @@ pub struct InlineCall<'a> {
|
||||||
pub end_header: Option<&'a str>,
|
pub end_header: Option<&'a str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn header(input: &str) -> IResult<&str, &str> {
|
|
||||||
delimited(tag("["), take_till(|c| c == ']' || c == '\n'), tag("]"))(input)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> InlineCall<'a> {
|
impl<'a> InlineCall<'a> {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn parse(input: &str) -> IResult<&str, Element<'_>> {
|
pub(crate) fn parse(input: &str) -> IResult<&str, Element<'_>> {
|
||||||
let (input, _) = tag("call_")(input)?;
|
let (input, name) = preceded(
|
||||||
let (input, name) = take_till(|c| c == '[' || c == '\n' || c == '(' || c == ')')(input)?;
|
tag("call_"),
|
||||||
let (input, inside_header) = opt(header)(input)?;
|
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) =
|
let (input, arguments) =
|
||||||
delimited(tag("("), take_till(|c| c == ')' || c == '\n'), tag(")"))(input)?;
|
delimited(tag("("), take_till(|c| c == ')' || c == '\n'), tag(")"))(input)?;
|
||||||
let (input, end_header) = opt(header)(input)?;
|
let (input, end_header) = opt(delimited(
|
||||||
|
tag("["),
|
||||||
|
take_till(|c| c == ']' || c == '\n'),
|
||||||
|
tag("]"),
|
||||||
|
))(input)?;
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
input,
|
input,
|
||||||
|
|
|
@ -18,45 +18,46 @@ impl List {
|
||||||
.unwrap_or((0, text));
|
.unwrap_or((0, text));
|
||||||
|
|
||||||
let ordered = is_item(tail)?;
|
let ordered = is_item(tail)?;
|
||||||
let bytes = text.as_bytes();
|
|
||||||
let mut lines = memchr_iter(b'\n', bytes)
|
|
||||||
.map(|i| i + 1)
|
|
||||||
.chain(once(text.len()));
|
|
||||||
let mut pos = lines.next()?;
|
|
||||||
|
|
||||||
while let Some(i) = lines.next() {
|
let mut last_end = 0;
|
||||||
let line = &text[pos..i];
|
let mut start = 0;
|
||||||
return if let Some(line_indent) = line.find(|c: char| !c.is_whitespace()) {
|
|
||||||
// this line is no empty
|
for i in memchr_iter(b'\n', text.as_bytes())
|
||||||
if line_indent < indent
|
.map(|i| i + 1)
|
||||||
|| (line_indent == indent && is_item(&line[line_indent..]).is_none())
|
.chain(once(text.len()))
|
||||||
{
|
{
|
||||||
Some((&text[pos..], List { indent, ordered }, &text[0..pos]))
|
let line = &text[start..i];
|
||||||
} else {
|
|
||||||
pos = i;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
} else if let Some(next_i) = lines.next() {
|
|
||||||
// this line is empty
|
|
||||||
let line = &text[i..next_i];
|
|
||||||
if let Some(line_indent) = line.find(|c: char| !c.is_whitespace()) {
|
if let Some(line_indent) = line.find(|c: char| !c.is_whitespace()) {
|
||||||
if line_indent < indent
|
if line_indent < indent
|
||||||
|| (line_indent == indent && is_item(&line[line_indent..]).is_none())
|
|| (line_indent == indent && is_item(&line[line_indent..]).is_none())
|
||||||
{
|
{
|
||||||
Some((&text[pos..], List { indent, ordered }, &text[0..pos]))
|
return Some((
|
||||||
|
&text[start..],
|
||||||
|
List { indent, ordered },
|
||||||
|
&text[0..start - 1],
|
||||||
|
));
|
||||||
} else {
|
} else {
|
||||||
pos = next_i;
|
last_end = 0;
|
||||||
|
start = i;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Some((&text[next_i..], List { indent, ordered }, &text[0..pos]))
|
// this line is empty
|
||||||
}
|
if last_end != 0 {
|
||||||
|
return Some((&text[i..], List { indent, ordered }, &text[0..last_end]));
|
||||||
} else {
|
} else {
|
||||||
Some((&text[i..], List { indent, ordered }, &text[0..pos]))
|
last_end = start;
|
||||||
};
|
start = i;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Some((&text[pos..], List { indent, ordered }, &text[0..pos]))
|
if last_end != 0 {
|
||||||
|
Some(("", List { indent, ordered }, &text[0..last_end]))
|
||||||
|
} else {
|
||||||
|
Some(("", List { indent, ordered }, text))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -55,7 +55,7 @@ pub use self::{
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
|
||||||
#[cfg_attr(feature = "serde", serde(tag = "type", rename_all = "snake_case"))]
|
#[cfg_attr(feature = "serde", serde(tag = "type", rename_all = "kebab-case"))]
|
||||||
pub enum Element<'a> {
|
pub enum Element<'a> {
|
||||||
SpecialBlock(SpecialBlock<'a>),
|
SpecialBlock(SpecialBlock<'a>),
|
||||||
QuoteBlock(QuoteBlock<'a>),
|
QuoteBlock(QuoteBlock<'a>),
|
||||||
|
@ -83,7 +83,6 @@ pub enum Element<'a> {
|
||||||
List(List),
|
List(List),
|
||||||
ListItem(ListItem<'a>),
|
ListItem(ListItem<'a>),
|
||||||
Macros(Macros<'a>),
|
Macros(Macros<'a>),
|
||||||
Planning(Planning<'a>),
|
|
||||||
Snippet(Snippet<'a>),
|
Snippet(Snippet<'a>),
|
||||||
Text { value: &'a str },
|
Text { value: &'a str },
|
||||||
Paragraph,
|
Paragraph,
|
||||||
|
@ -162,7 +161,6 @@ impl_from!(
|
||||||
Link,
|
Link,
|
||||||
ListItem,
|
ListItem,
|
||||||
Macros,
|
Macros,
|
||||||
Planning,
|
|
||||||
QuoteBlock,
|
QuoteBlock,
|
||||||
Snippet,
|
Snippet,
|
||||||
SourceBlock,
|
SourceBlock,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use nom::{
|
use nom::{
|
||||||
bytes::complete::{tag, take, take_until, take_while1},
|
bytes::complete::{tag, take_until, take_while1},
|
||||||
sequence::{delimited, separated_pair},
|
sequence::{delimited, separated_pair},
|
||||||
IResult,
|
IResult,
|
||||||
};
|
};
|
||||||
|
@ -24,7 +24,7 @@ impl Snippet<'_> {
|
||||||
tag(":"),
|
tag(":"),
|
||||||
take_until("@@"),
|
take_until("@@"),
|
||||||
),
|
),
|
||||||
take(2usize),
|
tag("@@"),
|
||||||
)(input)?;
|
)(input)?;
|
||||||
|
|
||||||
Ok((input, Element::Snippet(Snippet { name, value })))
|
Ok((input, Element::Snippet(Snippet { name, value })))
|
||||||
|
@ -40,7 +40,7 @@ fn parse() {
|
||||||
Element::Snippet(Snippet {
|
Element::Snippet(Snippet {
|
||||||
name: "html",
|
name: "html",
|
||||||
value: "<b>"
|
value: "<b>"
|
||||||
},)
|
})
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -50,7 +50,7 @@ fn parse() {
|
||||||
Element::Snippet(Snippet {
|
Element::Snippet(Snippet {
|
||||||
name: "latex",
|
name: "latex",
|
||||||
value: "any arbitrary LaTeX code",
|
value: "any arbitrary LaTeX code",
|
||||||
},)
|
})
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -60,7 +60,7 @@ fn parse() {
|
||||||
Element::Snippet(Snippet {
|
Element::Snippet(Snippet {
|
||||||
name: "html",
|
name: "html",
|
||||||
value: "",
|
value: "",
|
||||||
},)
|
})
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -70,7 +70,7 @@ fn parse() {
|
||||||
Element::Snippet(Snippet {
|
Element::Snippet(Snippet {
|
||||||
name: "html",
|
name: "html",
|
||||||
value: "<p>@</p>",
|
value: "<p>@</p>",
|
||||||
},)
|
})
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
assert!(Snippet::parse("@@html:<b>@").is_err());
|
assert!(Snippet::parse("@@html:<b>@").is_err());
|
||||||
|
|
|
@ -22,7 +22,9 @@ pub struct Datetime<'a> {
|
||||||
pub month: u8,
|
pub month: u8,
|
||||||
pub day: u8,
|
pub day: u8,
|
||||||
pub dayname: &'a str,
|
pub dayname: &'a str,
|
||||||
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub hour: Option<u8>,
|
pub hour: Option<u8>,
|
||||||
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub minute: Option<u8>,
|
pub minute: Option<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,29 +105,37 @@ mod chrono {
|
||||||
|
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
|
||||||
#[cfg_attr(feature = "serde", serde(tag = "type", rename_all = "snake_case"))]
|
#[cfg_attr(feature = "serde", serde(tag = "timestamp_type", rename_all = "kebab-case"))]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Timestamp<'a> {
|
pub enum Timestamp<'a> {
|
||||||
Active {
|
Active {
|
||||||
start: Datetime<'a>,
|
start: Datetime<'a>,
|
||||||
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
repeater: Option<&'a str>,
|
repeater: Option<&'a str>,
|
||||||
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
delay: Option<&'a str>,
|
delay: Option<&'a str>,
|
||||||
},
|
},
|
||||||
Inactive {
|
Inactive {
|
||||||
start: Datetime<'a>,
|
start: Datetime<'a>,
|
||||||
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
repeater: Option<&'a str>,
|
repeater: Option<&'a str>,
|
||||||
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
delay: Option<&'a str>,
|
delay: Option<&'a str>,
|
||||||
},
|
},
|
||||||
ActiveRange {
|
ActiveRange {
|
||||||
start: Datetime<'a>,
|
start: Datetime<'a>,
|
||||||
end: Datetime<'a>,
|
end: Datetime<'a>,
|
||||||
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
repeater: Option<&'a str>,
|
repeater: Option<&'a str>,
|
||||||
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
delay: Option<&'a str>,
|
delay: Option<&'a str>,
|
||||||
},
|
},
|
||||||
InactiveRange {
|
InactiveRange {
|
||||||
start: Datetime<'a>,
|
start: Datetime<'a>,
|
||||||
end: Datetime<'a>,
|
end: Datetime<'a>,
|
||||||
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
repeater: Option<&'a str>,
|
repeater: Option<&'a str>,
|
||||||
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
delay: Option<&'a str>,
|
delay: Option<&'a str>,
|
||||||
},
|
},
|
||||||
Diary {
|
Diary {
|
||||||
|
|
|
@ -94,7 +94,6 @@ pub trait HtmlHandler<E: From<Error>> {
|
||||||
Escape(link.desc.unwrap_or(link.path)),
|
Escape(link.desc.unwrap_or(link.path)),
|
||||||
)?,
|
)?,
|
||||||
Macros(_macros) => (),
|
Macros(_macros) => (),
|
||||||
Planning(_planning) => (),
|
|
||||||
RadioTarget(_radio_target) => (),
|
RadioTarget(_radio_target) => (),
|
||||||
Snippet(snippet) => {
|
Snippet(snippet) => {
|
||||||
if snippet.name.eq_ignore_ascii_case("HTML") {
|
if snippet.name.eq_ignore_ascii_case("HTML") {
|
||||||
|
|
|
@ -80,7 +80,6 @@ pub trait OrgHandler<E: From<Error>> {
|
||||||
write!(&mut w, "]")?;
|
write!(&mut w, "]")?;
|
||||||
}
|
}
|
||||||
Macros(_macros) => (),
|
Macros(_macros) => (),
|
||||||
Planning(_planning) => (),
|
|
||||||
RadioTarget(_radio_target) => (),
|
RadioTarget(_radio_target) => (),
|
||||||
Snippet(snippet) => write!(w, "@@{}:{}@@", snippet.name, snippet.value)?,
|
Snippet(snippet) => write!(w, "@@{}:{}@@", snippet.name, snippet.value)?,
|
||||||
Target(_target) => (),
|
Target(_target) => (),
|
||||||
|
|
25
src/iter.rs
25
src/iter.rs
|
@ -1,25 +0,0 @@
|
||||||
use indextree::{Arena, NodeEdge, Traverse};
|
|
||||||
|
|
||||||
use crate::elements::Element;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum Event<'a> {
|
|
||||||
Start(&'a Element<'a>),
|
|
||||||
End(&'a Element<'a>),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Iter<'a> {
|
|
||||||
pub(crate) arena: &'a Arena<Element<'a>>,
|
|
||||||
pub(crate) traverse: Traverse<'a, Element<'a>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Iterator for Iter<'a> {
|
|
||||||
type Item = Event<'a>;
|
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
|
||||||
self.traverse.next().map(|edge| match edge {
|
|
||||||
NodeEdge::Start(e) => Event::Start(&self.arena[e].data),
|
|
||||||
NodeEdge::End(e) => Event::End(&self.arena[e].data),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -219,7 +219,6 @@
|
||||||
mod config;
|
mod config;
|
||||||
pub mod elements;
|
pub mod elements;
|
||||||
pub mod export;
|
pub mod export;
|
||||||
mod iter;
|
|
||||||
mod org;
|
mod org;
|
||||||
mod parsers;
|
mod parsers;
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
|
@ -227,5 +226,4 @@ mod serde;
|
||||||
|
|
||||||
pub use config::ParseConfig;
|
pub use config::ParseConfig;
|
||||||
pub use elements::Element;
|
pub use elements::Element;
|
||||||
pub use iter::{Event, Iter};
|
|
||||||
pub use org::Org;
|
pub use org::Org;
|
||||||
|
|
62
src/org.rs
62
src/org.rs
|
@ -1,4 +1,4 @@
|
||||||
use indextree::{Arena, NodeId};
|
use indextree::{Arena, NodeEdge, NodeId};
|
||||||
use jetscii::bytes;
|
use jetscii::bytes;
|
||||||
use memchr::{memchr, memchr2, memchr_iter};
|
use memchr::{memchr, memchr2, memchr_iter};
|
||||||
use std::io::{Error, Write};
|
use std::io::{Error, Write};
|
||||||
|
@ -6,7 +6,7 @@ use std::io::{Error, Write};
|
||||||
use crate::config::ParseConfig;
|
use crate::config::ParseConfig;
|
||||||
use crate::elements::*;
|
use crate::elements::*;
|
||||||
use crate::export::*;
|
use crate::export::*;
|
||||||
use crate::iter::{Event, Iter};
|
use crate::parsers::skip_empty_lines;
|
||||||
|
|
||||||
pub struct Org<'a> {
|
pub struct Org<'a> {
|
||||||
pub(crate) arena: Arena<Element<'a>>,
|
pub(crate) arena: Arena<Element<'a>>,
|
||||||
|
@ -42,12 +42,18 @@ enum Container<'a> {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Org<'a> {
|
#[derive(Debug)]
|
||||||
pub fn parse(text: &'a str) -> Self {
|
pub enum Event<'a> {
|
||||||
|
Start(&'a Element<'a>),
|
||||||
|
End(&'a Element<'a>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Org<'_> {
|
||||||
|
pub fn parse(text: &str) -> Org<'_> {
|
||||||
Org::parse_with_config(text, &ParseConfig::default())
|
Org::parse_with_config(text, &ParseConfig::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_with_config(content: &'a str, config: &ParseConfig) -> Self {
|
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 document = arena.new_node(Element::Document);
|
||||||
|
|
||||||
|
@ -82,7 +88,8 @@ impl<'a> Org<'a> {
|
||||||
node: parent,
|
node: parent,
|
||||||
} => {
|
} => {
|
||||||
let mut tail = content;
|
let mut tail = content;
|
||||||
let (new_tail, title, content) = Title::parse(tail, config);
|
let (new_tail, title) = Title::parse(tail, config).unwrap();
|
||||||
|
let content = title.raw;
|
||||||
let node = arena.new_node(Element::Title(title));
|
let node = arena.new_node(Element::Title(title));
|
||||||
parent.append(node, &mut arena).unwrap();
|
parent.append(node, &mut arena).unwrap();
|
||||||
containers.push(Container::Inline { content, node });
|
containers.push(Container::Inline { content, node });
|
||||||
|
@ -120,11 +127,13 @@ impl<'a> Org<'a> {
|
||||||
Org { arena, document }
|
Org { arena, document }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn iter(&'a self) -> Iter<'a> {
|
pub fn iter<'a>(&'a self) -> impl Iterator<Item = Event<'_>> + 'a {
|
||||||
Iter {
|
self.document
|
||||||
arena: &self.arena,
|
.traverse(&self.arena)
|
||||||
traverse: self.document.traverse(&self.arena),
|
.map(move |edge| match edge {
|
||||||
}
|
NodeEdge::Start(e) => Event::Start(&self.arena[e].data),
|
||||||
|
NodeEdge::End(e) => Event::End(&self.arena[e].data),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn html<W: Write>(&self, wrtier: W) -> Result<(), Error> {
|
pub fn html<W: Write>(&self, wrtier: W) -> Result<(), Error> {
|
||||||
|
@ -305,8 +314,10 @@ fn parse_block<'a>(
|
||||||
}
|
}
|
||||||
|
|
||||||
if tail.starts_with(':') {
|
if tail.starts_with(':') {
|
||||||
if let Ok((tail, (drawer, _content))) = Drawer::parse(tail) {
|
if let Ok((tail, (drawer, content))) = Drawer::parse(tail) {
|
||||||
return Some((tail, arena.new_node(drawer)));
|
let node = arena.new_node(drawer.into());
|
||||||
|
containers.push(Container::Block { content, node });
|
||||||
|
return Some((tail, node));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -629,28 +640,3 @@ fn parse_list_items<'a>(
|
||||||
contents = tail;
|
contents = tail;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn skip_empty_lines(contents: &str) -> &str {
|
|
||||||
let mut i = 0;
|
|
||||||
for pos in memchr_iter(b'\n', contents.as_bytes()) {
|
|
||||||
if contents.as_bytes()[i..pos]
|
|
||||||
.iter()
|
|
||||||
.all(u8::is_ascii_whitespace)
|
|
||||||
{
|
|
||||||
i = pos + 1;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&contents[i..]
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_skip_empty_lines() {
|
|
||||||
assert_eq!(skip_empty_lines("foo"), "foo");
|
|
||||||
assert_eq!(skip_empty_lines(" foo"), " foo");
|
|
||||||
assert_eq!(skip_empty_lines(" \nfoo\n"), "foo\n");
|
|
||||||
assert_eq!(skip_empty_lines(" \n\n\nfoo\n"), "foo\n");
|
|
||||||
assert_eq!(skip_empty_lines(" \n \n\nfoo\n"), "foo\n");
|
|
||||||
assert_eq!(skip_empty_lines(" \n \n\n foo\n"), " foo\n");
|
|
||||||
}
|
|
||||||
|
|
|
@ -2,8 +2,11 @@
|
||||||
|
|
||||||
use memchr::{memchr, memchr_iter};
|
use memchr::{memchr, memchr_iter};
|
||||||
use nom::{
|
use nom::{
|
||||||
bytes::complete::tag, character::complete::space0, error::ErrorKind, error_position, Err,
|
branch::alt,
|
||||||
IResult,
|
bytes::complete::{tag, take_till},
|
||||||
|
character::complete::space0,
|
||||||
|
error::ErrorKind,
|
||||||
|
error_position, Err, IResult,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub(crate) fn eol(input: &str) -> IResult<&str, ()> {
|
pub(crate) fn eol(input: &str) -> IResult<&str, ()> {
|
||||||
|
@ -43,3 +46,34 @@ pub(crate) fn take_lines_till(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn take_one_word(input: &str) -> IResult<&str, &str> {
|
||||||
|
alt((take_till(|c: char| c == ' ' || c == '\t'), |input| {
|
||||||
|
Ok(("", input))
|
||||||
|
}))(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn skip_empty_lines(contents: &str) -> &str {
|
||||||
|
let mut i = 0;
|
||||||
|
for pos in memchr_iter(b'\n', contents.as_bytes()) {
|
||||||
|
if contents.as_bytes()[i..pos]
|
||||||
|
.iter()
|
||||||
|
.all(u8::is_ascii_whitespace)
|
||||||
|
{
|
||||||
|
i = pos + 1;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&contents[i..]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_skip_empty_lines() {
|
||||||
|
assert_eq!(skip_empty_lines("foo"), "foo");
|
||||||
|
assert_eq!(skip_empty_lines(" foo"), " foo");
|
||||||
|
assert_eq!(skip_empty_lines(" \nfoo\n"), "foo\n");
|
||||||
|
assert_eq!(skip_empty_lines(" \n\n\nfoo\n"), "foo\n");
|
||||||
|
assert_eq!(skip_empty_lines(" \n \n\nfoo\n"), "foo\n");
|
||||||
|
assert_eq!(skip_empty_lines(" \n \n\n foo\n"), " foo\n");
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue