feat(element): rename Headline with Title struct

This commit is contained in:
PoiScript 2019-07-31 15:09:01 +08:00
parent e835103ac6
commit 53d8f9dc90
10 changed files with 268 additions and 251 deletions

View file

@ -24,8 +24,8 @@ chrono = { version = "0.4.7", optional = true }
indextree = "3.3.0" indextree = "3.3.0"
jetscii = "0.4.4" jetscii = "0.4.4"
memchr = "2.2.1" memchr = "2.2.1"
serde = { version = "1.0.97", optional = true, features = ["derive"] }
nom = "5.0.0" nom = "5.0.0"
serde = { version = "1.0.97", optional = true, features = ["derive"] }
[dev-dependencies] [dev-dependencies]
lazy_static = "1.3.0" lazy_static = "1.3.0"

View file

@ -5,7 +5,7 @@ use std::io::{Error as IOError, Write};
use std::result::Result; use std::result::Result;
use std::string::FromUtf8Error; use std::string::FromUtf8Error;
use orgize::export::{html::Escape, DefaultHtmlHandler, HtmlHandler}; use orgize::export::{DefaultHtmlHandler, HtmlHandler};
use orgize::{Element, Org}; use orgize::{Element, Org};
use slugify::slugify; use slugify::slugify;
@ -34,16 +34,15 @@ struct MyHtmlHandler(DefaultHtmlHandler);
impl HtmlHandler<MyError> for MyHtmlHandler { impl HtmlHandler<MyError> for MyHtmlHandler {
fn start<W: Write>(&mut self, mut w: W, element: &Element<'_>) -> Result<(), MyError> { fn start<W: Write>(&mut self, mut w: W, element: &Element<'_>) -> Result<(), MyError> {
match element { match element {
Element::Headline(headline) => { Element::Title(title) => {
if headline.level > 6 { if title.level > 6 {
return Err(MyError::Heading); return Err(MyError::Heading);
} else { } else {
write!( write!(
w, w,
"<h{0}><a id=\"{1}\" href=\"#{1}\">{2}</a></h{0}>", "<h{0}><a id=\"{1}\" href=\"#{1}\">",
headline.level, title.level,
slugify!(headline.title), slugify!(title.raw),
Escape(headline.title),
)?; )?;
} }
} }
@ -52,6 +51,16 @@ impl HtmlHandler<MyError> for MyHtmlHandler {
} }
Ok(()) Ok(())
} }
fn end<W: Write>(&mut self, mut w: W, element: &Element<'_>) -> Result<(), MyError> {
match element {
Element::Title(title) => {
write!(w, "</a></h{}>", title.level,)?;
}
_ => self.0.end(w, element)?,
}
Ok(())
}
} }
fn main() -> Result<(), MyError> { fn main() -> Result<(), MyError> {

View file

@ -67,7 +67,8 @@ fn parse() {
}), }),
)) ))
); );
assert!(InlineSrc::parse("src_xml[:exports code]{<tag>text</tag>").is_err(),);
assert!(InlineSrc::parse("src_[:exports code]{<tag>text</tag>}").is_err(),); assert!(InlineSrc::parse("src_xml[:exports code]{<tag>text</tag>").is_err());
// assert_eq!(parse("src_xml[:exports code]"), None); assert!(InlineSrc::parse("src_[:exports code]{<tag>text</tag>}").is_err());
assert!(InlineSrc::parse("src_xml[:exports code]").is_err());
} }

View file

@ -8,7 +8,6 @@ mod dyn_block;
mod emphasis; mod emphasis;
mod fn_def; mod fn_def;
mod fn_ref; mod fn_ref;
mod headline;
mod inline_call; mod inline_call;
mod inline_src; mod inline_src;
mod keyword; mod keyword;
@ -21,6 +20,7 @@ mod rule;
mod snippet; mod snippet;
mod target; mod target;
mod timestamp; mod timestamp;
mod title;
pub(crate) use emphasis::parse as parse_emphasis; pub(crate) use emphasis::parse as parse_emphasis;
@ -32,7 +32,6 @@ pub use self::{
dyn_block::DynBlock, dyn_block::DynBlock,
fn_def::FnDef, fn_def::FnDef,
fn_ref::FnRef, fn_ref::FnRef,
headline::Headline,
inline_call::InlineCall, inline_call::InlineCall,
inline_src::InlineSrc, inline_src::InlineSrc,
keyword::{BabelCall, Keyword}, keyword::{BabelCall, Keyword},
@ -45,14 +44,10 @@ pub use self::{
snippet::Snippet, snippet::Snippet,
target::Target, target::Target,
timestamp::{Date, Time, Timestamp}, timestamp::{Date, Time, Timestamp},
title::Title,
}; };
/// Org-mode element enum /// Org-mode element enum
///
/// Generally, each variant contains a element struct and
/// a set of properties which indicate the position of the
/// element in the original string.
///
#[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))]
@ -69,7 +64,7 @@ pub enum Element<'a> {
DynBlock(DynBlock<'a>), DynBlock(DynBlock<'a>),
FnDef(FnDef<'a>), FnDef(FnDef<'a>),
FnRef(FnRef<'a>), FnRef(FnRef<'a>),
Headline(Headline<'a>), Headline,
InlineCall(InlineCall<'a>), InlineCall(InlineCall<'a>),
InlineSrc(InlineSrc<'a>), InlineSrc(InlineSrc<'a>),
Keyword(Keyword<'a>), Keyword(Keyword<'a>),
@ -92,6 +87,28 @@ pub enum Element<'a> {
Code { value: &'a str }, Code { value: &'a str },
Comment { value: &'a str }, Comment { value: &'a str },
FixedWidth { value: &'a str }, FixedWidth { value: &'a str },
Title(Title<'a>),
}
impl Element<'_> {
pub fn is_container(&self) -> bool {
match self {
Element::Block(_)
| Element::Bold
| Element::Document
| Element::DynBlock(_)
| Element::Headline
| Element::Italic
| Element::List(_)
| Element::ListItem(_)
| Element::Paragraph
| Element::Section
| Element::Strike
| Element::Underline
| Element::Title(_) => true,
_ => false,
}
}
} }
macro_rules! impl_from { macro_rules! impl_from {
@ -112,7 +129,6 @@ impl_from!(Drawer);
impl_from!(DynBlock); impl_from!(DynBlock);
impl_from!(FnDef); impl_from!(FnDef);
impl_from!(FnRef); impl_from!(FnRef);
impl_from!(Headline);
impl_from!(InlineCall); impl_from!(InlineCall);
impl_from!(InlineSrc); impl_from!(InlineSrc);
impl_from!(Keyword); impl_from!(Keyword);

View file

@ -1,6 +1,5 @@
//! Headline //! Headline Title
use jetscii::ByteSubstring;
use memchr::{memchr, memchr2, memrchr}; use memchr::{memchr, memchr2, memrchr};
use crate::config::ParseConfig; use crate::config::ParseConfig;
@ -8,7 +7,7 @@ use crate::config::ParseConfig;
#[cfg_attr(test, derive(PartialEq))] #[cfg_attr(test, derive(PartialEq))]
#[cfg_attr(feature = "serde", derive(serde::Serialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[derive(Debug)] #[derive(Debug)]
pub struct Headline<'a> { pub struct Title<'a> {
/// headline level, number of stars /// headline level, number of stars
pub level: usize, pub level: usize,
/// priority cookie /// priority cookie
@ -17,47 +16,35 @@ pub struct Headline<'a> {
/// headline tags, including the sparated colons /// headline tags, including the sparated colons
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Vec::is_empty"))] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Vec::is_empty"))]
pub tags: Vec<&'a str>, pub tags: Vec<&'a str>,
/// headline title
pub title: &'a str,
/// headline keyword /// headline keyword
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub keyword: Option<&'a str>, pub keyword: Option<&'a str>,
pub raw: &'a str,
} }
impl Headline<'_> { impl Title<'_> {
pub(crate) fn parse<'a>( #[inline]
text: &'a str, pub(crate) fn parse<'a>(text: &'a str, config: &ParseConfig) -> (&'a str, Title<'a>, &'a str) {
config: &ParseConfig,
) -> (&'a str, Headline<'a>, &'a str) {
let level = memchr2(b'\n', b' ', text.as_bytes()).unwrap_or_else(|| text.len()); let level = memchr2(b'\n', b' ', text.as_bytes()).unwrap_or_else(|| text.len());
debug_assert!(level > 0); debug_assert!(level > 0);
debug_assert!(text.as_bytes()[0..level].iter().all(|&c| c == b'*')); debug_assert!(text.as_bytes()[0..level].iter().all(|&c| c == b'*'));
let (off, end) = memchr(b'\n', text.as_bytes()) let off = memchr(b'\n', text.as_bytes())
.map(|i| { .map(|i| i + 1)
( .unwrap_or_else(|| text.len());
i + 1,
if i + 1 == text.len() {
i + 1
} else {
Headline::find_level(&text[i + 1..], level) + i + 1
},
)
})
.unwrap_or_else(|| (text.len(), text.len()));
if level == off { if level == off {
return ( return (
&text[end..], "",
Headline { Title {
level, level,
keyword: None, keyword: None,
priority: None, priority: None,
title: "",
tags: Vec::new(), tags: Vec::new(),
raw: "",
}, },
&text[off..end], "",
); );
} }
@ -103,47 +90,18 @@ impl Headline<'_> {
}; };
( (
&text[end..], &text[off..],
Headline { Title {
level, level,
keyword, keyword,
priority, priority,
title,
tags: tags.split(':').filter(|s| !s.is_empty()).collect(), tags: tags.split(':').filter(|s| !s.is_empty()).collect(),
raw: title,
}, },
&text[off..end], title,
) )
} }
pub(crate) fn find_level(text: &str, level: usize) -> usize {
let bytes = text.as_bytes();
if bytes[0] == b'*' {
if let Some(stars) = memchr2(b'\n', b' ', bytes) {
if stars <= level && bytes[0..stars].iter().all(|&c| c == b'*') {
return 0;
}
}
}
let mut pos = 0;
while let Some(off) = ByteSubstring::new(b"\n*").find(&bytes[pos..]) {
pos += off + 1;
if let Some(stars) = memchr2(b'\n', b' ', &bytes[pos..]) {
if stars > 0 && stars <= level && bytes[pos..pos + stars].iter().all(|&c| c == b'*')
{
return pos;
}
}
}
text.len()
}
/// checks if this headline is "commented"
pub fn is_commented(&self) -> bool {
self.title.starts_with("COMMENT ")
}
/// checks if this headline is "archived" /// checks if this headline is "archived"
pub fn is_archived(&self) -> bool { pub fn is_archived(&self) -> bool {
self.tags.contains(&"ARCHIVE") self.tags.contains(&"ARCHIVE")
@ -158,123 +116,120 @@ lazy_static::lazy_static! {
#[test] #[test]
fn parse() { fn parse() {
assert_eq!( assert_eq!(
Headline::parse("**** DONE [#A] COMMENT Title :tag:a2%:", &CONFIG), Title::parse("**** DONE [#A] COMMENT Title :tag:a2%:", &CONFIG),
( (
"", "",
Headline { Title {
level: 4, level: 4,
priority: Some('A'), priority: Some('A'),
keyword: Some("DONE"), keyword: Some("DONE"),
title: "COMMENT Title",
tags: vec!["tag", "a2%"], tags: vec!["tag", "a2%"],
raw: "COMMENT Title"
}, },
"" "COMMENT Title"
) )
); );
assert_eq!( assert_eq!(
Headline::parse("**** ToDO [#A] COMMENT Title :tag:a2%:", &CONFIG), Title::parse("**** ToDO [#A] COMMENT Title :tag:a2%:", &CONFIG),
( (
"", "",
Headline { Title {
level: 4, level: 4,
priority: None, priority: None,
tags: vec!["tag", "a2%"], tags: vec!["tag", "a2%"],
title: "ToDO [#A] COMMENT Title",
keyword: None, keyword: None,
raw: "ToDO [#A] COMMENT Title"
}, },
"" "ToDO [#A] COMMENT Title"
) )
); );
assert_eq!( assert_eq!(
Headline::parse("**** T0DO [#A] COMMENT Title :tag:a2%:", &CONFIG), Title::parse("**** T0DO [#A] COMMENT Title :tag:a2%:", &CONFIG),
( (
"", "",
Headline { Title {
level: 4, level: 4,
priority: None, priority: None,
tags: vec!["tag", "a2%"], tags: vec!["tag", "a2%"],
title: "T0DO [#A] COMMENT Title",
keyword: None, keyword: None,
raw: "T0DO [#A] COMMENT Title"
}, },
"" "T0DO [#A] COMMENT Title"
) )
); );
assert_eq!( assert_eq!(
Headline::parse("**** DONE [#1] COMMENT Title :tag:a2%:", &CONFIG), Title::parse("**** DONE [#1] COMMENT Title :tag:a2%:", &CONFIG),
( (
"", "",
Headline { Title {
level: 4, level: 4,
priority: None, priority: None,
tags: vec!["tag", "a2%"], tags: vec!["tag", "a2%"],
title: "[#1] COMMENT Title",
keyword: Some("DONE"), keyword: Some("DONE"),
raw: "[#1] COMMENT Title"
}, },
"" "[#1] COMMENT Title"
) )
); );
assert_eq!( assert_eq!(
Headline::parse("**** DONE [#a] COMMENT Title :tag:a2%:", &CONFIG), Title::parse("**** DONE [#a] COMMENT Title :tag:a2%:", &CONFIG),
( (
"", "",
Headline { Title {
level: 4, level: 4,
priority: None, priority: None,
tags: vec!["tag", "a2%"], tags: vec!["tag", "a2%"],
title: "[#a] COMMENT Title",
keyword: Some("DONE"), keyword: Some("DONE"),
raw: "[#a] COMMENT Title"
}, },
"" "[#a] COMMENT Title"
) )
); );
assert_eq!( assert_eq!(
Headline::parse("**** DONE [#A] COMMENT Title :tag:a2%", &CONFIG), Title::parse("**** DONE [#A] COMMENT Title :tag:a2%", &CONFIG),
( (
"", "",
Headline { Title {
level: 4, level: 4,
priority: Some('A'), priority: Some('A'),
tags: Vec::new(), tags: Vec::new(),
title: "COMMENT Title :tag:a2%",
keyword: Some("DONE"), keyword: Some("DONE"),
raw: "COMMENT Title :tag:a2%"
}, },
"" "COMMENT Title :tag:a2%"
) )
); );
assert_eq!( assert_eq!(
Headline::parse("**** DONE [#A] COMMENT Title tag:a2%:", &CONFIG), Title::parse("**** DONE [#A] COMMENT Title tag:a2%:", &CONFIG),
( (
"", "",
Headline { Title {
level: 4, level: 4,
priority: Some('A'), priority: Some('A'),
tags: Vec::new(), tags: Vec::new(),
title: "COMMENT Title tag:a2%:",
keyword: Some("DONE"), keyword: Some("DONE"),
raw: "COMMENT Title tag:a2%:"
}, },
"" "COMMENT Title tag:a2%:"
) )
); );
assert_eq!( assert_eq!(
Headline::parse("**** COMMENT Title tag:a2%:", &CONFIG), Title::parse("**** COMMENT Title tag:a2%:", &CONFIG),
( (
"", "",
Headline { Title {
level: 4, level: 4,
priority: None, priority: None,
tags: Vec::new(), tags: Vec::new(),
title: "COMMENT Title tag:a2%:",
keyword: None, keyword: None,
raw: "COMMENT Title tag:a2%:"
}, },
"" "COMMENT Title tag:a2%:"
) )
); );
}
#[test]
fn parse_todo_keywords() {
assert_eq!( assert_eq!(
Headline::parse( Title::parse(
"**** DONE [#A] COMMENT Title :tag:a2%:", "**** DONE [#A] COMMENT Title :tag:a2%:",
&ParseConfig { &ParseConfig {
done_keywords: vec![], done_keywords: vec![],
@ -283,18 +238,18 @@ fn parse_todo_keywords() {
), ),
( (
"", "",
Headline { Title {
level: 4, level: 4,
priority: None, priority: None,
keyword: None, keyword: None,
title: "DONE [#A] COMMENT Title",
tags: vec!["tag", "a2%"], tags: vec!["tag", "a2%"],
raw: "DONE [#A] COMMENT Title"
}, },
"" "DONE [#A] COMMENT Title"
) )
); );
assert_eq!( assert_eq!(
Headline::parse( Title::parse(
"**** TASK [#A] COMMENT Title :tag:a2%:", "**** TASK [#A] COMMENT Title :tag:a2%:",
&ParseConfig { &ParseConfig {
todo_keywords: vec!["TASK".to_string()], todo_keywords: vec!["TASK".to_string()],
@ -303,53 +258,39 @@ fn parse_todo_keywords() {
), ),
( (
"", "",
Headline { Title {
level: 4, level: 4,
priority: Some('A'), priority: Some('A'),
keyword: Some("TASK"), keyword: Some("TASK"),
title: "COMMENT Title",
tags: vec!["tag", "a2%"], tags: vec!["tag", "a2%"],
raw: "COMMENT Title"
}, },
"" "COMMENT Title"
) )
); );
} }
#[test] // #[test]
fn is_commented() { // fn is_commented() {
assert!(Headline::parse("* COMMENT Title", &CONFIG).1.is_commented()); // assert!(Title::parse("* COMMENT Title", &CONFIG)
assert!(!Headline::parse("* Title", &CONFIG).1.is_commented()); // .1
assert!(!Headline::parse("* C0MMENT Title", &CONFIG).1.is_commented()); // .is_commented());
assert!(!Headline::parse("* comment Title", &CONFIG).1.is_commented()); // assert!(!Title::parse("* Title", &CONFIG).1.is_commented());
} // assert!(!Title::parse("* C0MMENT Title", &CONFIG)
// .1
// .is_commented());
// assert!(!Title::parse("* comment Title", &CONFIG)
// .1
// .is_commented());
// }
#[test] #[test]
fn is_archived() { fn is_archived() {
assert!(Headline::parse("* Title :ARCHIVE:", &CONFIG) assert!(Title::parse("* Title :ARCHIVE:", &CONFIG).1.is_archived());
.1 assert!(Title::parse("* Title :t:ARCHIVE:", &CONFIG).1.is_archived());
.is_archived()); assert!(Title::parse("* Title :ARCHIVE:t:", &CONFIG).1.is_archived());
assert!(Headline::parse("* Title :t:ARCHIVE:", &CONFIG) assert!(!Title::parse("* Title", &CONFIG).1.is_archived());
.1 assert!(!Title::parse("* Title :ARCHIVED:", &CONFIG).1.is_archived());
.is_archived()); assert!(!Title::parse("* Title :ARCHIVES:", &CONFIG).1.is_archived());
assert!(Headline::parse("* Title :ARCHIVE:t:", &CONFIG) assert!(!Title::parse("* Title :archive:", &CONFIG).1.is_archived());
.1
.is_archived());
assert!(!Headline::parse("* Title", &CONFIG).1.is_commented());
assert!(!Headline::parse("* Title :ARCHIVED:", &CONFIG)
.1
.is_archived());
assert!(!Headline::parse("* Title :ARCHIVES:", &CONFIG)
.1
.is_archived());
assert!(!Headline::parse("* Title :archive:", &CONFIG)
.1
.is_archived());
}
#[test]
fn find_level() {
assert_eq!(
Headline::find_level("\n** Title\n* Title\n** Title\n", 1),
"\n** Title\n".len()
);
} }

View file

@ -35,17 +35,10 @@ pub trait HtmlHandler<E: From<Error>> {
match element { match element {
// container elements // container elements
Block(_block) => write!(w, "<div>")?, Block(_block) => write!(w, "<div>")?,
Bold { .. } => write!(w, "<b>")?, Bold => write!(w, "<b>")?,
Document { .. } => write!(w, "<main>")?, Document => write!(w, "<main>")?,
DynBlock(_dyn_block) => (), DynBlock(_dyn_block) => (),
Headline(headline) => { Headline => (),
let level = if headline.level <= 6 {
headline.level
} else {
6
};
write!(w, "<h{0}>{1}</h{0}>", level, Escape(headline.title))?;
}
List(list) => { List(list) => {
if list.ordered { if list.ordered {
write!(w, "<ol>")?; write!(w, "<ol>")?;
@ -53,12 +46,12 @@ pub trait HtmlHandler<E: From<Error>> {
write!(w, "<ul>")?; write!(w, "<ul>")?;
} }
} }
Italic { .. } => write!(w, "<i>")?, Italic => write!(w, "<i>")?,
ListItem { .. } => write!(w, "<li>")?, ListItem(_) => write!(w, "<li>")?,
Paragraph { .. } => write!(w, "<p>")?, Paragraph => write!(w, "<p>")?,
Section { .. } => write!(w, "<section>")?, Section => write!(w, "<section>")?,
Strike { .. } => write!(w, "<s>")?, Strike => write!(w, "<s>")?,
Underline { .. } => write!(w, "<u>")?, Underline => write!(w, "<u>")?,
// non-container elements // non-container elements
BabelCall(_babel_call) => (), BabelCall(_babel_call) => (),
InlineSrc(inline_src) => write!(w, "<code>{}</code>", Escape(inline_src.body))?, InlineSrc(inline_src) => write!(w, "<code>{}</code>", Escape(inline_src.body))?,
@ -91,6 +84,7 @@ pub trait HtmlHandler<E: From<Error>> {
Drawer(_drawer) => (), Drawer(_drawer) => (),
Rule => write!(w, "<hr>")?, Rule => write!(w, "<hr>")?,
Cookie(_cookie) => (), Cookie(_cookie) => (),
Title(title) => write!(w, "<h{}>", if title.level <= 6 { title.level } else { 6 })?,
} }
Ok(()) Ok(())
@ -101,10 +95,10 @@ pub trait HtmlHandler<E: From<Error>> {
match element { match element {
// container elements // container elements
Block(_block) => write!(w, "</div>")?, Block(_block) => write!(w, "</div>")?,
Bold { .. } => write!(w, "</b>")?, Bold => write!(w, "</b>")?,
Document { .. } => write!(w, "</main>")?, Document => write!(w, "</main>")?,
DynBlock(_dyn_block) => (), DynBlock(_dyn_block) => (),
Headline(_headline) => (), Headline => (),
List(list) => { List(list) => {
if list.ordered { if list.ordered {
write!(w, "</ol>")?; write!(w, "</ol>")?;
@ -112,14 +106,15 @@ pub trait HtmlHandler<E: From<Error>> {
write!(w, "</ul>")?; write!(w, "</ul>")?;
} }
} }
Italic { .. } => write!(w, "</i>")?, Italic => write!(w, "</i>")?,
ListItem { .. } => write!(w, "</li>")?, ListItem(_) => write!(w, "</li>")?,
Paragraph { .. } => write!(w, "</p>")?, Paragraph => write!(w, "</p>")?,
Section { .. } => write!(w, "</section>")?, Section => write!(w, "</section>")?,
Strike { .. } => write!(w, "</s>")?, Strike => write!(w, "</s>")?,
Underline { .. } => write!(w, "</u>")?, Underline => write!(w, "</u>")?,
Title(title) => write!(w, "</h{}>", if title.level <= 6 { title.level } else { 6 })?,
// non-container elements // non-container elements
_ => (), _ => debug_assert!(!element.is_container()),
} }
Ok(()) Ok(())

View file

@ -1,5 +1,5 @@
pub mod html; pub mod html;
pub mod org; // pub mod org;
pub use html::{DefaultHtmlHandler, HtmlHandler}; pub use html::{DefaultHtmlHandler, HtmlHandler};
pub use org::{DefaultOrgHandler, OrgHandler}; // pub use org::{DefaultOrgHandler, OrgHandler};

View file

@ -117,16 +117,15 @@
//! impl HtmlHandler<MyError> for MyHtmlHandler { //! impl HtmlHandler<MyError> for MyHtmlHandler {
//! fn start<W: Write>(&mut self, mut w: W, element: &Element<'_>) -> Result<(), MyError> { //! fn start<W: Write>(&mut self, mut w: W, element: &Element<'_>) -> Result<(), MyError> {
//! match element { //! match element {
//! Element::Headline(headline) => { //! Element::Title(title) => {
//! if headline.level > 6 { //! if title.level > 6 {
//! return Err(MyError::Heading); //! return Err(MyError::Heading);
//! } else { //! } else {
//! write!( //! write!(
//! w, //! w,
//! "<h{0}><a id=\"{1}\" href=\"#{1}\">{2}</a></h{0}>", //! "<h{0}><a id=\"{1}\" href=\"#{1}\">",
//! headline.level, //! title.level,
//! slugify!(headline.title), //! slugify!(title.raw),
//! Escape(headline.title),
//! )?; //! )?;
//! } //! }
//! } //! }
@ -135,6 +134,16 @@
//! } //! }
//! Ok(()) //! Ok(())
//! } //! }
//!
//! fn end<W: Write>(&mut self, mut w: W, element: &Element<'_>) -> Result<(), MyError> {
//! match element {
//! Element::Title(title) => {
//! write!(w, "</a></h{}>", title.level,)?;
//! }
//! _ => self.0.end(w, element)?,
//! }
//! Ok(())
//! }
//! } //! }
//! //!
//! fn main() -> Result<(), MyError> { //! fn main() -> Result<(), MyError> {

View file

@ -1,12 +1,12 @@
use indextree::{Arena, NodeId}; use indextree::{Arena, NodeId};
use jetscii::bytes; use jetscii::bytes;
use memchr::{memchr, memchr_iter}; use memchr::{memchr, memchr2, memchr_iter};
use std::io::{Error, Write}; 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::Iter; use crate::iter::{Event, Iter};
pub struct Org<'a> { pub struct Org<'a> {
pub(crate) arena: Arena<Element<'a>>, pub(crate) arena: Arena<Element<'a>>,
@ -30,13 +30,13 @@ enum Container<'a> {
content: &'a str, content: &'a str,
node: NodeId, node: NodeId,
}, },
// Headline, Document // Headline
Headline { Headline {
content: &'a str, content: &'a str,
node: NodeId, node: NodeId,
}, },
// Section // Document
Section { Document {
content: &'a str, content: &'a str,
node: NodeId, node: NodeId,
}, },
@ -51,54 +51,61 @@ impl<'a> 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);
let mut containers = vec![Container::Headline { let mut containers = vec![Container::Document {
content, content,
node: document, node: document,
}]; }];
while let Some(container) = containers.pop() { while let Some(container) = containers.pop() {
match container { match container {
Container::Headline { Container::Document {
mut content, content,
node: parent, node: parent,
} => { } => {
if !content.is_empty() { let mut tail = skip_empty_lines(content);
let off = Headline::find_level(content, std::usize::MAX); if let Some((new_tail, content)) = parse_section(tail) {
if off != 0 {
let node = arena.new_node(Element::Section); let node = arena.new_node(Element::Section);
parent.append(node, &mut arena).unwrap(); parent.append(node, &mut arena).unwrap();
containers.push(Container::Section { containers.push(Container::Block { content, node });
content: &content[0..off], tail = new_tail;
node,
});
content = &content[off..];
} }
} while !tail.is_empty() {
while !content.is_empty() { let (new_tail, content) = parse_headline(tail);
let (tail, headline, headline_content) = Headline::parse(content, &config); let node = arena.new_node(Element::Headline);
let headline = Element::Headline(headline);
let node = arena.new_node(headline);
parent.append(node, &mut arena).unwrap(); parent.append(node, &mut arena).unwrap();
containers.push(Container::Headline { containers.push(Container::Headline { content, node });
content: headline_content, tail = new_tail;
node,
});
content = tail;
} }
} }
Container::Section { content, node } => { Container::Headline {
// TODO content,
if let Some((tail, _planning)) = Planning::parse(content) { node: parent,
parse_elements_children(&mut arena, tail, node, &mut containers); } => {
} else { let mut tail = content;
parse_elements_children(&mut arena, content, node, &mut containers); let (new_tail, title, content) = Title::parse(tail, config);
let node = arena.new_node(Element::Title(title));
parent.append(node, &mut arena).unwrap();
containers.push(Container::Inline { content, node });
tail = skip_empty_lines(new_tail);
if let Some((new_tail, content)) = parse_section(tail) {
let node = arena.new_node(Element::Section);
parent.append(node, &mut arena).unwrap();
containers.push(Container::Block { content, node });
tail = new_tail;
}
while !tail.is_empty() {
let (new_tail, content) = parse_headline(tail);
let node = arena.new_node(Element::Headline);
parent.append(node, &mut arena).unwrap();
containers.push(Container::Headline { content, node });
tail = new_tail;
} }
} }
Container::Block { content, node } => { Container::Block { content, node } => {
parse_elements_children(&mut arena, content, node, &mut containers); parse_blocks(&mut arena, content, node, &mut containers);
} }
Container::Inline { content, node } => { Container::Inline { content, node } => {
parse_objects_children(&mut arena, content, node, &mut containers); parse_inlines(&mut arena, content, node, &mut containers);
} }
Container::List { Container::List {
content, content,
@ -130,12 +137,10 @@ impl<'a> Org<'a> {
E: From<Error>, E: From<Error>,
H: HtmlHandler<E>, H: HtmlHandler<E>,
{ {
use crate::iter::Event::*;
for event in self.iter() { for event in self.iter() {
match event { match event {
Start(element) => handler.start(&mut writer, element)?, Event::Start(element) => handler.start(&mut writer, element)?,
End(element) => handler.end(&mut writer, element)?, Event::End(element) => handler.end(&mut writer, element)?,
} }
} }
@ -143,7 +148,49 @@ impl<'a> Org<'a> {
} }
} }
fn parse_elements_children<'a>( fn is_headline(text: &str) -> Option<usize> {
if let Some(off) = memchr2(b'\n', b' ', text.as_bytes()) {
if off > 0 && text[0..off].as_bytes().iter().all(|&c| c == b'*') {
Some(off)
} else {
None
}
} else if text.len() > 0 && text.as_bytes().iter().all(|&c| c == b'*') {
Some(text.len())
} else {
None
}
}
fn parse_section(text: &str) -> Option<(&str, &str)> {
if text.is_empty() || is_headline(text).is_some() {
return None;
}
for i in memchr_iter(b'\n', text.as_bytes()) {
if is_headline(&text[i + 1..]).is_some() {
return Some((&text[i + 1..], &text[0..i + 1]));
}
}
Some(("", text))
}
fn parse_headline(text: &str) -> (&str, &str) {
let level = is_headline(text).unwrap();
for i in memchr_iter(b'\n', text.as_bytes()) {
if let Some(l) = is_headline(&text[i + 1..]) {
if l <= level {
return (&text[i + 1..], &text[0..i + 1]);
}
}
}
("", text)
}
fn parse_blocks<'a>(
arena: &mut Arena<Element<'a>>, arena: &mut Arena<Element<'a>>,
content: &'a str, content: &'a str,
parent: NodeId, parent: NodeId,
@ -151,7 +198,7 @@ fn parse_elements_children<'a>(
) { ) {
let mut tail = skip_empty_lines(content); let mut tail = skip_empty_lines(content);
if let Some((new_tail, element)) = parse_element(content, arena, containers) { if let Some((new_tail, element)) = parse_block(content, arena, containers) {
parent.append(element, arena).unwrap(); parent.append(element, arena).unwrap();
tail = skip_empty_lines(new_tail); tail = skip_empty_lines(new_tail);
} }
@ -173,7 +220,7 @@ fn parse_elements_children<'a>(
}); });
text = tail; text = tail;
pos = 0; pos = 0;
} else if let Some((new_tail, element)) = parse_element(tail, arena, containers) { } else if let Some((new_tail, element)) = parse_block(tail, arena, containers) {
if pos != 0 { if pos != 0 {
let node = arena.new_node(Element::Paragraph); let node = arena.new_node(Element::Paragraph);
parent.append(node, arena).unwrap(); parent.append(node, arena).unwrap();
@ -202,7 +249,7 @@ fn parse_elements_children<'a>(
} }
} }
fn parse_element<'a>( fn parse_block<'a>(
contents: &'a str, contents: &'a str,
arena: &mut Arena<Element<'a>>, arena: &mut Arena<Element<'a>>,
containers: &mut Vec<Container<'a>>, containers: &mut Vec<Container<'a>>,
@ -300,7 +347,7 @@ fn parse_element<'a>(
} }
} }
fn parse_objects_children<'a>( fn parse_inlines<'a>(
arena: &mut Arena<Element<'a>>, arena: &mut Arena<Element<'a>>,
content: &'a str, content: &'a str,
parent: NodeId, parent: NodeId,
@ -308,8 +355,8 @@ fn parse_objects_children<'a>(
) { ) {
let mut tail = content; let mut tail = content;
if let Some((new_tail, obj)) = parse_object(tail, arena, containers) { if let Some((new_tail, element)) = parse_inline(tail, arena, containers) {
parent.append(obj, arena).unwrap(); parent.append(element, arena).unwrap();
tail = new_tail; tail = new_tail;
} }
@ -321,7 +368,7 @@ fn parse_objects_children<'a>(
while let Some(off) = bs.find(tail.as_bytes()) { while let Some(off) = bs.find(tail.as_bytes()) {
match tail.as_bytes()[off] { match tail.as_bytes()[off] {
b'{' => { b'{' => {
if let Some((new_tail, obj)) = parse_object(&tail[off..], arena, containers) { if let Some((new_tail, element)) = parse_inline(&tail[off..], arena, containers) {
if pos != 0 { if pos != 0 {
let node = arena.new_node(Element::Text { let node = arena.new_node(Element::Text {
value: &text[0..pos + off], value: &text[0..pos + off],
@ -329,39 +376,40 @@ fn parse_objects_children<'a>(
parent.append(node, arena).unwrap(); parent.append(node, arena).unwrap();
pos = 0; pos = 0;
} }
parent.append(obj, arena).unwrap(); parent.append(element, arena).unwrap();
tail = new_tail; tail = new_tail;
text = new_tail; text = new_tail;
continue; continue;
} else if let Some((new_tail, obj)) = } else if let Some((new_tail, element)) =
parse_object(&tail[off + 1..], arena, containers) parse_inline(&tail[off + 1..], arena, containers)
{ {
let node = arena.new_node(Element::Text { let node = arena.new_node(Element::Text {
value: &text[0..pos + off + 1], value: &text[0..pos + off + 1],
}); });
parent.append(node, arena).unwrap(); parent.append(node, arena).unwrap();
pos = 0; pos = 0;
parent.append(obj, arena).unwrap(); parent.append(element, arena).unwrap();
tail = new_tail; tail = new_tail;
text = new_tail; text = new_tail;
continue; continue;
} }
} }
b' ' | b'(' | b'\'' | b'"' | b'\n' => { b' ' | b'(' | b'\'' | b'"' | b'\n' => {
if let Some((new_tail, obj)) = parse_object(&tail[off + 1..], arena, containers) { if let Some((new_tail, element)) = parse_inline(&tail[off + 1..], arena, containers)
{
let node = arena.new_node(Element::Text { let node = arena.new_node(Element::Text {
value: &text[0..pos + off + 1], value: &text[0..pos + off + 1],
}); });
parent.append(node, arena).unwrap(); parent.append(node, arena).unwrap();
pos = 0; pos = 0;
parent.append(obj, arena).unwrap(); parent.append(element, arena).unwrap();
tail = new_tail; tail = new_tail;
text = new_tail; text = new_tail;
continue; continue;
} }
} }
_ => { _ => {
if let Some((new_tail, obj)) = parse_object(&tail[off..], arena, containers) { if let Some((new_tail, element)) = parse_inline(&tail[off..], arena, containers) {
if pos != 0 { if pos != 0 {
let node = arena.new_node(Element::Text { let node = arena.new_node(Element::Text {
value: &text[0..pos + off], value: &text[0..pos + off],
@ -369,7 +417,7 @@ fn parse_objects_children<'a>(
parent.append(node, arena).unwrap(); parent.append(node, arena).unwrap();
pos = 0; pos = 0;
} }
parent.append(obj, arena).unwrap(); parent.append(element, arena).unwrap();
tail = new_tail; tail = new_tail;
text = new_tail; text = new_tail;
continue; continue;
@ -386,7 +434,7 @@ fn parse_objects_children<'a>(
} }
} }
fn parse_object<'a>( fn parse_inline<'a>(
contents: &'a str, contents: &'a str,
arena: &mut Arena<Element<'a>>, arena: &mut Arena<Element<'a>>,
containers: &mut Vec<Container<'a>>, containers: &mut Vec<Container<'a>>,

View file

@ -1,17 +1,15 @@
use orgize::Org; use orgize::Org;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use std::io::Result;
macro_rules! test_suite { macro_rules! test_suite {
($name:ident, $content:expr, $expected:expr) => { ($name:ident, $content:expr, $expected:expr) => {
#[test] #[test]
fn $name() -> Result<()> { fn $name() {
let mut writer = Vec::new(); let mut writer = Vec::new();
let org = Org::parse($content); let org = Org::parse($content);
org.html(&mut writer).unwrap(); org.html(&mut writer).unwrap();
let string = String::from_utf8(writer).unwrap(); let string = String::from_utf8(writer).unwrap();
assert_eq!(string, $expected); assert_eq!(string, $expected);
Ok(())
} }
}; };
} }