diff --git a/Cargo.toml b/Cargo.toml index a421ff6..f195bf5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,8 +24,8 @@ chrono = { version = "0.4.7", optional = true } indextree = "3.3.0" jetscii = "0.4.4" memchr = "2.2.1" -serde = { version = "1.0.97", optional = true, features = ["derive"] } nom = "5.0.0" +serde = { version = "1.0.97", optional = true, features = ["derive"] } [dev-dependencies] lazy_static = "1.3.0" diff --git a/examples/custom.rs b/examples/custom.rs index 7d96f65..7b41e97 100644 --- a/examples/custom.rs +++ b/examples/custom.rs @@ -5,7 +5,7 @@ use std::io::{Error as IOError, Write}; use std::result::Result; use std::string::FromUtf8Error; -use orgize::export::{html::Escape, DefaultHtmlHandler, HtmlHandler}; +use orgize::export::{DefaultHtmlHandler, HtmlHandler}; use orgize::{Element, Org}; use slugify::slugify; @@ -34,16 +34,15 @@ struct MyHtmlHandler(DefaultHtmlHandler); impl HtmlHandler for MyHtmlHandler { fn start(&mut self, mut w: W, element: &Element<'_>) -> Result<(), MyError> { match element { - Element::Headline(headline) => { - if headline.level > 6 { + Element::Title(title) => { + if title.level > 6 { return Err(MyError::Heading); } else { write!( w, - "{2}", - headline.level, - slugify!(headline.title), - Escape(headline.title), + "", + title.level, + slugify!(title.raw), )?; } } @@ -52,6 +51,16 @@ impl HtmlHandler for MyHtmlHandler { } Ok(()) } + + fn end(&mut self, mut w: W, element: &Element<'_>) -> Result<(), MyError> { + match element { + Element::Title(title) => { + write!(w, "", title.level,)?; + } + _ => self.0.end(w, element)?, + } + Ok(()) + } } fn main() -> Result<(), MyError> { diff --git a/src/elements/inline_src.rs b/src/elements/inline_src.rs index c88398f..71aa386 100644 --- a/src/elements/inline_src.rs +++ b/src/elements/inline_src.rs @@ -67,7 +67,8 @@ fn parse() { }), )) ); - assert!(InlineSrc::parse("src_xml[:exports code]{text").is_err(),); - assert!(InlineSrc::parse("src_[:exports code]{text}").is_err(),); - // assert_eq!(parse("src_xml[:exports code]"), None); + + assert!(InlineSrc::parse("src_xml[:exports code]{text").is_err()); + assert!(InlineSrc::parse("src_[:exports code]{text}").is_err()); + assert!(InlineSrc::parse("src_xml[:exports code]").is_err()); } diff --git a/src/elements/mod.rs b/src/elements/mod.rs index 44a6579..2ef29ca 100644 --- a/src/elements/mod.rs +++ b/src/elements/mod.rs @@ -8,7 +8,6 @@ mod dyn_block; mod emphasis; mod fn_def; mod fn_ref; -mod headline; mod inline_call; mod inline_src; mod keyword; @@ -21,6 +20,7 @@ mod rule; mod snippet; mod target; mod timestamp; +mod title; pub(crate) use emphasis::parse as parse_emphasis; @@ -32,7 +32,6 @@ pub use self::{ dyn_block::DynBlock, fn_def::FnDef, fn_ref::FnRef, - headline::Headline, inline_call::InlineCall, inline_src::InlineSrc, keyword::{BabelCall, Keyword}, @@ -45,14 +44,10 @@ pub use self::{ snippet::Snippet, target::Target, timestamp::{Date, Time, Timestamp}, + title::Title, }; /// 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)] #[cfg_attr(test, derive(PartialEq))] #[cfg_attr(feature = "serde", derive(serde::Serialize))] @@ -69,7 +64,7 @@ pub enum Element<'a> { DynBlock(DynBlock<'a>), FnDef(FnDef<'a>), FnRef(FnRef<'a>), - Headline(Headline<'a>), + Headline, InlineCall(InlineCall<'a>), InlineSrc(InlineSrc<'a>), Keyword(Keyword<'a>), @@ -92,6 +87,28 @@ pub enum Element<'a> { Code { value: &'a str }, Comment { 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 { @@ -112,7 +129,6 @@ impl_from!(Drawer); impl_from!(DynBlock); impl_from!(FnDef); impl_from!(FnRef); -impl_from!(Headline); impl_from!(InlineCall); impl_from!(InlineSrc); impl_from!(Keyword); diff --git a/src/elements/headline.rs b/src/elements/title.rs similarity index 54% rename from src/elements/headline.rs rename to src/elements/title.rs index 0e57d83..0a02b0f 100644 --- a/src/elements/headline.rs +++ b/src/elements/title.rs @@ -1,6 +1,5 @@ -//! Headline +//! Headline Title -use jetscii::ByteSubstring; use memchr::{memchr, memchr2, memrchr}; use crate::config::ParseConfig; @@ -8,7 +7,7 @@ use crate::config::ParseConfig; #[cfg_attr(test, derive(PartialEq))] #[cfg_attr(feature = "serde", derive(serde::Serialize))] #[derive(Debug)] -pub struct Headline<'a> { +pub struct Title<'a> { /// headline level, number of stars pub level: usize, /// priority cookie @@ -17,47 +16,35 @@ pub struct Headline<'a> { /// headline tags, including the sparated colons #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Vec::is_empty"))] pub tags: Vec<&'a str>, - /// headline title - pub title: &'a str, /// headline keyword #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] pub keyword: Option<&'a str>, + pub raw: &'a str, } -impl Headline<'_> { - pub(crate) fn parse<'a>( - text: &'a str, - config: &ParseConfig, - ) -> (&'a str, Headline<'a>, &'a str) { +impl Title<'_> { + #[inline] + pub(crate) fn parse<'a>(text: &'a str, config: &ParseConfig) -> (&'a str, Title<'a>, &'a str) { let level = memchr2(b'\n', b' ', text.as_bytes()).unwrap_or_else(|| text.len()); debug_assert!(level > 0); debug_assert!(text.as_bytes()[0..level].iter().all(|&c| c == b'*')); - let (off, end) = memchr(b'\n', text.as_bytes()) - .map(|i| { - ( - 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())); + let off = memchr(b'\n', text.as_bytes()) + .map(|i| i + 1) + .unwrap_or_else(|| text.len()); if level == off { return ( - &text[end..], - Headline { + "", + Title { level, keyword: None, priority: None, - title: "", tags: Vec::new(), + raw: "", }, - &text[off..end], + "", ); } @@ -103,47 +90,18 @@ impl Headline<'_> { }; ( - &text[end..], - Headline { + &text[off..], + Title { level, keyword, priority, - title, 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" pub fn is_archived(&self) -> bool { self.tags.contains(&"ARCHIVE") @@ -158,123 +116,120 @@ lazy_static::lazy_static! { #[test] fn parse() { assert_eq!( - Headline::parse("**** DONE [#A] COMMENT Title :tag:a2%:", &CONFIG), + Title::parse("**** DONE [#A] COMMENT Title :tag:a2%:", &CONFIG), ( "", - Headline { + Title { level: 4, priority: Some('A'), keyword: Some("DONE"), - title: "COMMENT Title", tags: vec!["tag", "a2%"], + raw: "COMMENT Title" }, - "" + "COMMENT Title" ) ); assert_eq!( - Headline::parse("**** ToDO [#A] COMMENT Title :tag:a2%:", &CONFIG), + Title::parse("**** ToDO [#A] COMMENT Title :tag:a2%:", &CONFIG), ( "", - Headline { + Title { level: 4, priority: None, tags: vec!["tag", "a2%"], - title: "ToDO [#A] COMMENT Title", keyword: None, + raw: "ToDO [#A] COMMENT Title" }, - "" + "ToDO [#A] COMMENT Title" ) ); assert_eq!( - Headline::parse("**** T0DO [#A] COMMENT Title :tag:a2%:", &CONFIG), + Title::parse("**** T0DO [#A] COMMENT Title :tag:a2%:", &CONFIG), ( "", - Headline { + Title { level: 4, priority: None, tags: vec!["tag", "a2%"], - title: "T0DO [#A] COMMENT Title", keyword: None, + raw: "T0DO [#A] COMMENT Title" }, - "" + "T0DO [#A] COMMENT Title" ) ); assert_eq!( - Headline::parse("**** DONE [#1] COMMENT Title :tag:a2%:", &CONFIG), + Title::parse("**** DONE [#1] COMMENT Title :tag:a2%:", &CONFIG), ( "", - Headline { + Title { level: 4, priority: None, tags: vec!["tag", "a2%"], - title: "[#1] COMMENT Title", keyword: Some("DONE"), + raw: "[#1] COMMENT Title" }, - "" + "[#1] COMMENT Title" ) ); assert_eq!( - Headline::parse("**** DONE [#a] COMMENT Title :tag:a2%:", &CONFIG), + Title::parse("**** DONE [#a] COMMENT Title :tag:a2%:", &CONFIG), ( "", - Headline { + Title { level: 4, priority: None, tags: vec!["tag", "a2%"], - title: "[#a] COMMENT Title", keyword: Some("DONE"), + raw: "[#a] COMMENT Title" }, - "" + "[#a] COMMENT Title" ) ); assert_eq!( - Headline::parse("**** DONE [#A] COMMENT Title :tag:a2%", &CONFIG), + Title::parse("**** DONE [#A] COMMENT Title :tag:a2%", &CONFIG), ( "", - Headline { + Title { level: 4, priority: Some('A'), tags: Vec::new(), - title: "COMMENT Title :tag:a2%", keyword: Some("DONE"), + raw: "COMMENT Title :tag:a2%" }, - "" + "COMMENT Title :tag:a2%" ) ); assert_eq!( - Headline::parse("**** DONE [#A] COMMENT Title tag:a2%:", &CONFIG), + Title::parse("**** DONE [#A] COMMENT Title tag:a2%:", &CONFIG), ( "", - Headline { + Title { level: 4, priority: Some('A'), tags: Vec::new(), - title: "COMMENT Title tag:a2%:", keyword: Some("DONE"), + raw: "COMMENT Title tag:a2%:" }, - "" + "COMMENT Title tag:a2%:" ) ); assert_eq!( - Headline::parse("**** COMMENT Title tag:a2%:", &CONFIG), + Title::parse("**** COMMENT Title tag:a2%:", &CONFIG), ( "", - Headline { + Title { level: 4, priority: None, tags: Vec::new(), - title: "COMMENT Title tag:a2%:", keyword: None, + raw: "COMMENT Title tag:a2%:" }, - "" + "COMMENT Title tag:a2%:" ) ); -} -#[test] -fn parse_todo_keywords() { assert_eq!( - Headline::parse( + Title::parse( "**** DONE [#A] COMMENT Title :tag:a2%:", &ParseConfig { done_keywords: vec![], @@ -283,18 +238,18 @@ fn parse_todo_keywords() { ), ( "", - Headline { + Title { level: 4, priority: None, keyword: None, - title: "DONE [#A] COMMENT Title", tags: vec!["tag", "a2%"], + raw: "DONE [#A] COMMENT Title" }, - "" + "DONE [#A] COMMENT Title" ) ); assert_eq!( - Headline::parse( + Title::parse( "**** TASK [#A] COMMENT Title :tag:a2%:", &ParseConfig { todo_keywords: vec!["TASK".to_string()], @@ -303,53 +258,39 @@ fn parse_todo_keywords() { ), ( "", - Headline { + Title { level: 4, priority: Some('A'), keyword: Some("TASK"), - title: "COMMENT Title", tags: vec!["tag", "a2%"], + raw: "COMMENT Title" }, - "" + "COMMENT Title" ) ); } -#[test] -fn is_commented() { - assert!(Headline::parse("* COMMENT Title", &CONFIG).1.is_commented()); - assert!(!Headline::parse("* Title", &CONFIG).1.is_commented()); - assert!(!Headline::parse("* C0MMENT Title", &CONFIG).1.is_commented()); - assert!(!Headline::parse("* comment Title", &CONFIG).1.is_commented()); -} +// #[test] +// fn is_commented() { +// assert!(Title::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] fn is_archived() { - assert!(Headline::parse("* Title :ARCHIVE:", &CONFIG) - .1 - .is_archived()); - assert!(Headline::parse("* Title :t:ARCHIVE:", &CONFIG) - .1 - .is_archived()); - assert!(Headline::parse("* Title :ARCHIVE:t:", &CONFIG) - .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() - ); + assert!(Title::parse("* Title :ARCHIVE:", &CONFIG).1.is_archived()); + assert!(Title::parse("* Title :t:ARCHIVE:", &CONFIG).1.is_archived()); + assert!(Title::parse("* Title :ARCHIVE:t:", &CONFIG).1.is_archived()); + assert!(!Title::parse("* Title", &CONFIG).1.is_archived()); + assert!(!Title::parse("* Title :ARCHIVED:", &CONFIG).1.is_archived()); + assert!(!Title::parse("* Title :ARCHIVES:", &CONFIG).1.is_archived()); + assert!(!Title::parse("* Title :archive:", &CONFIG).1.is_archived()); } diff --git a/src/export/html.rs b/src/export/html.rs index 9aa5c92..6ac4b12 100644 --- a/src/export/html.rs +++ b/src/export/html.rs @@ -35,17 +35,10 @@ pub trait HtmlHandler> { match element { // container elements Block(_block) => write!(w, "
")?, - Bold { .. } => write!(w, "")?, - Document { .. } => write!(w, "
")?, + Bold => write!(w, "")?, + Document => write!(w, "
")?, DynBlock(_dyn_block) => (), - Headline(headline) => { - let level = if headline.level <= 6 { - headline.level - } else { - 6 - }; - write!(w, "{1}", level, Escape(headline.title))?; - } + Headline => (), List(list) => { if list.ordered { write!(w, "
    ")?; @@ -53,12 +46,12 @@ pub trait HtmlHandler> { write!(w, "
      ")?; } } - Italic { .. } => write!(w, "")?, - ListItem { .. } => write!(w, "
    • ")?, - Paragraph { .. } => write!(w, "

      ")?, - Section { .. } => write!(w, "

      ")?, - Strike { .. } => write!(w, "")?, - Underline { .. } => write!(w, "")?, + Italic => write!(w, "")?, + ListItem(_) => write!(w, "
    • ")?, + Paragraph => write!(w, "

      ")?, + Section => write!(w, "

      ")?, + Strike => write!(w, "")?, + Underline => write!(w, "")?, // non-container elements BabelCall(_babel_call) => (), InlineSrc(inline_src) => write!(w, "{}", Escape(inline_src.body))?, @@ -91,6 +84,7 @@ pub trait HtmlHandler> { Drawer(_drawer) => (), Rule => write!(w, "
      ")?, Cookie(_cookie) => (), + Title(title) => write!(w, "", if title.level <= 6 { title.level } else { 6 })?, } Ok(()) @@ -101,10 +95,10 @@ pub trait HtmlHandler> { match element { // container elements Block(_block) => write!(w, "
")?, - Bold { .. } => write!(w, "")?, - Document { .. } => write!(w, "")?, + Bold => write!(w, "")?, + Document => write!(w, "")?, DynBlock(_dyn_block) => (), - Headline(_headline) => (), + Headline => (), List(list) => { if list.ordered { write!(w, "")?; @@ -112,14 +106,15 @@ pub trait HtmlHandler> { write!(w, "")?; } } - Italic { .. } => write!(w, "")?, - ListItem { .. } => write!(w, "")?, - Paragraph { .. } => write!(w, "

")?, - Section { .. } => write!(w, "")?, - Strike { .. } => write!(w, "
")?, - Underline { .. } => write!(w, "")?, + Italic => write!(w, "
")?, + ListItem(_) => write!(w, "")?, + Paragraph => write!(w, "

")?, + Section => write!(w, "")?, + Strike => write!(w, "
")?, + Underline => write!(w, "")?, + Title(title) => write!(w, "", if title.level <= 6 { title.level } else { 6 })?, // non-container elements - _ => (), + _ => debug_assert!(!element.is_container()), } Ok(()) diff --git a/src/export/mod.rs b/src/export/mod.rs index 99260ce..80dfa41 100644 --- a/src/export/mod.rs +++ b/src/export/mod.rs @@ -1,5 +1,5 @@ pub mod html; -pub mod org; +// pub mod org; pub use html::{DefaultHtmlHandler, HtmlHandler}; -pub use org::{DefaultOrgHandler, OrgHandler}; +// pub use org::{DefaultOrgHandler, OrgHandler}; diff --git a/src/lib.rs b/src/lib.rs index 5163d95..c431adb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -117,16 +117,15 @@ //! impl HtmlHandler for MyHtmlHandler { //! fn start(&mut self, mut w: W, element: &Element<'_>) -> Result<(), MyError> { //! match element { -//! Element::Headline(headline) => { -//! if headline.level > 6 { +//! Element::Title(title) => { +//! if title.level > 6 { //! return Err(MyError::Heading); //! } else { //! write!( //! w, -//! "{2}", -//! headline.level, -//! slugify!(headline.title), -//! Escape(headline.title), +//! "", +//! title.level, +//! slugify!(title.raw), //! )?; //! } //! } @@ -135,6 +134,16 @@ //! } //! Ok(()) //! } +//! +//! fn end(&mut self, mut w: W, element: &Element<'_>) -> Result<(), MyError> { +//! match element { +//! Element::Title(title) => { +//! write!(w, "", title.level,)?; +//! } +//! _ => self.0.end(w, element)?, +//! } +//! Ok(()) +//! } //! } //! //! fn main() -> Result<(), MyError> { diff --git a/src/org.rs b/src/org.rs index 143d1e3..ff49de0 100644 --- a/src/org.rs +++ b/src/org.rs @@ -1,12 +1,12 @@ use indextree::{Arena, NodeId}; use jetscii::bytes; -use memchr::{memchr, memchr_iter}; +use memchr::{memchr, memchr2, memchr_iter}; use std::io::{Error, Write}; use crate::config::ParseConfig; use crate::elements::*; use crate::export::*; -use crate::iter::Iter; +use crate::iter::{Event, Iter}; pub struct Org<'a> { pub(crate) arena: Arena>, @@ -30,13 +30,13 @@ enum Container<'a> { content: &'a str, node: NodeId, }, - // Headline, Document + // Headline Headline { content: &'a str, node: NodeId, }, - // Section - Section { + // Document + Document { content: &'a str, node: NodeId, }, @@ -51,54 +51,61 @@ impl<'a> Org<'a> { let mut arena = Arena::new(); let document = arena.new_node(Element::Document); - let mut containers = vec![Container::Headline { + let mut containers = vec![Container::Document { content, node: document, }]; while let Some(container) = containers.pop() { match container { - Container::Headline { - mut content, + Container::Document { + content, node: parent, } => { - if !content.is_empty() { - let off = Headline::find_level(content, std::usize::MAX); - if off != 0 { - let node = arena.new_node(Element::Section); - parent.append(node, &mut arena).unwrap(); - containers.push(Container::Section { - content: &content[0..off], - node, - }); - content = &content[off..]; - } - } - while !content.is_empty() { - let (tail, headline, headline_content) = Headline::parse(content, &config); - let headline = Element::Headline(headline); - let node = arena.new_node(headline); + let mut tail = skip_empty_lines(content); + 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::Headline { - content: headline_content, - node, - }); - content = tail; + 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::Section { content, node } => { - // TODO - if let Some((tail, _planning)) = Planning::parse(content) { - parse_elements_children(&mut arena, tail, node, &mut containers); - } else { - parse_elements_children(&mut arena, content, node, &mut containers); + Container::Headline { + content, + node: parent, + } => { + let mut tail = content; + 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 } => { - parse_elements_children(&mut arena, content, node, &mut containers); + parse_blocks(&mut arena, content, node, &mut containers); } Container::Inline { content, node } => { - parse_objects_children(&mut arena, content, node, &mut containers); + parse_inlines(&mut arena, content, node, &mut containers); } Container::List { content, @@ -130,12 +137,10 @@ impl<'a> Org<'a> { E: From, H: HtmlHandler, { - use crate::iter::Event::*; - for event in self.iter() { match event { - Start(element) => handler.start(&mut writer, element)?, - End(element) => handler.end(&mut writer, element)?, + Event::Start(element) => handler.start(&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 { + 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>, content: &'a str, parent: NodeId, @@ -151,7 +198,7 @@ fn parse_elements_children<'a>( ) { 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(); tail = skip_empty_lines(new_tail); } @@ -173,7 +220,7 @@ fn parse_elements_children<'a>( }); text = tail; 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 { let node = arena.new_node(Element::Paragraph); 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, arena: &mut Arena>, containers: &mut Vec>, @@ -300,7 +347,7 @@ fn parse_element<'a>( } } -fn parse_objects_children<'a>( +fn parse_inlines<'a>( arena: &mut Arena>, content: &'a str, parent: NodeId, @@ -308,8 +355,8 @@ fn parse_objects_children<'a>( ) { let mut tail = content; - if let Some((new_tail, obj)) = parse_object(tail, arena, containers) { - parent.append(obj, arena).unwrap(); + if let Some((new_tail, element)) = parse_inline(tail, arena, containers) { + parent.append(element, arena).unwrap(); tail = new_tail; } @@ -321,7 +368,7 @@ fn parse_objects_children<'a>( while let Some(off) = bs.find(tail.as_bytes()) { match tail.as_bytes()[off] { 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 { let node = arena.new_node(Element::Text { value: &text[0..pos + off], @@ -329,39 +376,40 @@ fn parse_objects_children<'a>( parent.append(node, arena).unwrap(); pos = 0; } - parent.append(obj, arena).unwrap(); + parent.append(element, arena).unwrap(); tail = new_tail; text = new_tail; continue; - } else if let Some((new_tail, obj)) = - parse_object(&tail[off + 1..], arena, containers) + } else if let Some((new_tail, element)) = + parse_inline(&tail[off + 1..], arena, containers) { let node = arena.new_node(Element::Text { value: &text[0..pos + off + 1], }); parent.append(node, arena).unwrap(); pos = 0; - parent.append(obj, arena).unwrap(); + parent.append(element, arena).unwrap(); tail = new_tail; text = new_tail; continue; } } 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 { value: &text[0..pos + off + 1], }); parent.append(node, arena).unwrap(); pos = 0; - parent.append(obj, arena).unwrap(); + parent.append(element, arena).unwrap(); tail = new_tail; text = new_tail; 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 { let node = arena.new_node(Element::Text { value: &text[0..pos + off], @@ -369,7 +417,7 @@ fn parse_objects_children<'a>( parent.append(node, arena).unwrap(); pos = 0; } - parent.append(obj, arena).unwrap(); + parent.append(element, arena).unwrap(); tail = new_tail; text = new_tail; continue; @@ -386,7 +434,7 @@ fn parse_objects_children<'a>( } } -fn parse_object<'a>( +fn parse_inline<'a>( contents: &'a str, arena: &mut Arena>, containers: &mut Vec>, diff --git a/tests/html.rs b/tests/html.rs index 17a49e2..5e4cd71 100644 --- a/tests/html.rs +++ b/tests/html.rs @@ -1,17 +1,15 @@ use orgize::Org; use pretty_assertions::assert_eq; -use std::io::Result; macro_rules! test_suite { ($name:ident, $content:expr, $expected:expr) => { #[test] - fn $name() -> Result<()> { + fn $name() { let mut writer = Vec::new(); let org = Org::parse($content); org.html(&mut writer).unwrap(); let string = String::from_utf8(writer).unwrap(); assert_eq!(string, $expected); - Ok(()) } }; }