refactor(elements): cleanup and minor improvements

This commit is contained in:
PoiScript 2019-08-05 22:23:32 +08:00
parent 37c33a82f0
commit 472676d6d7
13 changed files with 132 additions and 128 deletions

View file

@ -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>,
}, },
} }

View file

@ -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"
)
))
) )
} }

View file

@ -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,

View file

@ -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))
}
} }
} }

View file

@ -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,

View file

@ -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());

View file

@ -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 {

View file

@ -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") {

View file

@ -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) => (),

View file

@ -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),
})
}
}

View file

@ -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;

View file

@ -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");
}

View file

@ -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");
}