From 3b646aa7a529b485d604e48bc121f09ed781b2d2 Mon Sep 17 00:00:00 2001 From: PoiScript Date: Sun, 27 Oct 2019 10:36:41 +0800 Subject: [PATCH] chore: rename node and error crate --- src/error.rs | 159 ---------------- src/headline.rs | 473 ++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 10 +- src/node.rs | 374 ------------------------------------ src/org.rs | 45 ++--- src/validate.rs | 195 +++++++++++++++++++ tests/headline.rs | 76 ++++++++ tests/node.rs | 68 ------- 8 files changed, 763 insertions(+), 637 deletions(-) delete mode 100644 src/error.rs create mode 100644 src/headline.rs delete mode 100644 src/node.rs create mode 100644 src/validate.rs create mode 100644 tests/headline.rs delete mode 100644 tests/node.rs diff --git a/src/error.rs b/src/error.rs deleted file mode 100644 index c3c89bd..0000000 --- a/src/error.rs +++ /dev/null @@ -1,159 +0,0 @@ -use indextree::NodeId; - -use crate::elements::*; -use crate::Org; - -/// Orgize Validation Error -#[derive(Debug)] -pub enum OrgizeError { - /// Expect this node has children - Children { at: NodeId }, - /// Expect this node has no children - NoChildren { at: NodeId }, - /// Expect this node contains a headline or section element - HeadlineOrSection { at: NodeId }, - /// Expect this node contains a title element - Title { at: NodeId }, - /// Expect this node contains a headline element - Headline { at: NodeId }, - /// Expect a detached headline - Detached { at: NodeId }, - /// Expect a headline where its level >= max and <= min - HeadlineLevel { - max: Option, - min: Option, - at: NodeId, - }, -} - -impl OrgizeError { - pub fn element<'a, 'b>(&self, org: &'a Org<'b>) -> &'a Element<'b> { - match &self { - OrgizeError::Children { at } - | OrgizeError::NoChildren { at } - | OrgizeError::HeadlineOrSection { at } - | OrgizeError::Title { at } - | OrgizeError::Headline { at } - | OrgizeError::Detached { at } - | OrgizeError::HeadlineLevel { at, .. } => org.arena[*at].get(), - } - } -} - -impl Org<'_> { - /// Validate an `Org` struct. - pub fn validate(&self) -> Result<(), OrgizeError> { - for node_id in self.root.descendants(&self.arena) { - let node = &self.arena[node_id]; - match node.get() { - Element::Document => { - for child_id in node_id.children(&self.arena) { - match self.arena[child_id].get() { - Element::Headline { .. } | Element::Section => (), - _ => return Err(OrgizeError::HeadlineOrSection { at: child_id }), - } - } - } - Element::Headline { .. } => { - if node.first_child().is_none() { - return Err(OrgizeError::Children { at: node_id }); - } - let title = node.first_child().unwrap(); - match self.arena[title].get() { - Element::Title(Title { .. }) => (), - _ => return Err(OrgizeError::Title { at: title }), - } - if let Some(next) = self.arena[title].next_sibling() { - match self.arena[next].get() { - Element::Headline { .. } | Element::Section => (), - _ => return Err(OrgizeError::HeadlineOrSection { at: next }), - } - - for sibling in next.following_siblings(&self.arena).skip(1) { - match self.arena[sibling].get() { - Element::Headline { .. } => (), - _ => return Err(OrgizeError::Headline { at: sibling }), - } - } - } - } - Element::Title(Title { raw, .. }) => { - if !raw.is_empty() && node.first_child().is_none() { - return Err(OrgizeError::Children { at: node_id }); - } - } - Element::CommentBlock(_) - | Element::ExampleBlock(_) - | Element::ExportBlock(_) - | Element::SourceBlock(_) - | Element::BabelCall(_) - | Element::InlineSrc(_) - | Element::Code { .. } - | Element::FnRef(_) - | Element::InlineCall(_) - | Element::Link(_) - | Element::Macros(_) - | Element::RadioTarget - | Element::Snippet(_) - | Element::Target(_) - | Element::Text { .. } - | Element::Timestamp(_) - | Element::Verbatim { .. } - | Element::FnDef(_) - | Element::Clock(_) - | Element::Comment { .. } - | Element::FixedWidth { .. } - | Element::Keyword(_) - | Element::Rule - | Element::Cookie(_) - | Element::Table(Table::TableEl { .. }) - | Element::TableRow(TableRow::Rule) => { - if node.first_child().is_some() { - return Err(OrgizeError::NoChildren { at: node_id }); - } - } - Element::SpecialBlock(_) - | Element::QuoteBlock(_) - | Element::CenterBlock(_) - | Element::VerseBlock(_) - | Element::Paragraph - | Element::Section - | Element::Table(Table::Org { .. }) - | Element::TableRow(TableRow::Standard) - | Element::Bold - | Element::Italic - | Element::Underline - | Element::Strike - | Element::DynBlock(_) - | Element::List(_) - | Element::ListItem(_) => { - if node.first_child().is_none() { - return Err(OrgizeError::Children { at: node_id }); - } - } - // TableCell is a container but it might - // not contains anything, e.g. `||||||` - Element::Drawer(_) | Element::TableCell => (), - } - } - Ok(()) - } - - #[deprecated(since = "0.3.1", note = "rename to validate")] - /// Validate an `Org` struct. - pub fn check(&self) -> Result<(), OrgizeError> { - self.validate() - } - - pub(crate) fn debug_validate(&self) { - if cfg!(debug_assertions) { - if let Err(err) = self.validate() { - panic!( - "Validation error: {:?} at element: {:?}", - err, - err.element(self) - ); - } - } - } -} diff --git a/src/headline.rs b/src/headline.rs new file mode 100644 index 0000000..8ee7333 --- /dev/null +++ b/src/headline.rs @@ -0,0 +1,473 @@ +use indextree::NodeId; +use std::borrow::Cow; +use std::ops::RangeInclusive; +use std::usize; + +use crate::{ + config::ParseConfig, + elements::{Element, Title}, + parsers::{parse_container, Container, OwnedArena}, + validate::{ValidationError, ValidationResult}, + Org, +}; + +#[derive(Copy, Clone, Debug)] +pub struct Document(Headline); + +impl Document { + pub(crate) fn from_org(org: &Org) -> Document { + let sec_n = org.arena[org.root] + .first_child() + .and_then(|n| match org.arena[n].get() { + Element::Section => Some(n), + _ => None, + }); + // document can be treated as zero-level headline without title + Document(Headline { + lvl: 0, + hdl_n: org.root, + ttl_n: org.root, + sec_n, + }) + } + + pub fn section_node(self) -> Option { + self.0.sec_n + } + + pub fn children<'a>(self, org: &'a Org) -> impl Iterator + 'a { + self.0 + .hdl_n + .children(&org.arena) + // skip sec_n if exists + .skip(if self.0.sec_n.is_some() { 1 } else { 0 }) + .map(move |n| match *org.arena[n].get() { + Element::Headline { level } => Headline::from_node(n, level, org), + _ => unreachable!(), + }) + } + + pub fn first_child(self, org: &Org) -> Option { + self.0 + .hdl_n + .children(&org.arena) + // skip sec_n if exists + .nth(if self.0.sec_n.is_some() { 1 } else { 0 }) + .map(move |n| match *org.arena[n].get() { + Element::Headline { level } => Headline::from_node(n, level, org), + _ => unreachable!(), + }) + } + + pub fn last_child(self, org: &Org) -> Option { + self.0.last_child(org) + } + + pub fn set_section_content<'a, S: Into>>( + &mut self, + content: S, + org: &mut Org<'a>, + ) { + let sec_n = if let Some(sec_n) = self.0.sec_n { + let children: Vec<_> = sec_n.children(&org.arena).collect(); + for child in children { + child.detach(&mut org.arena); + } + sec_n + } else { + let sec_n = org.arena.new_node(Element::Section); + self.0.sec_n = Some(sec_n); + self.0.hdl_n.prepend(sec_n, &mut org.arena); + sec_n + }; + + match content.into() { + Cow::Borrowed(content) => parse_container( + &mut org.arena, + Container::Block { + node: sec_n, + content, + }, + &ParseConfig::default(), + ), + Cow::Owned(ref content) => parse_container( + &mut OwnedArena::new(&mut org.arena), + Container::Block { + node: sec_n, + content, + }, + &ParseConfig::default(), + ), + } + + org.debug_validate(); + } + + pub fn append(self, hdl: Headline, org: &mut Org) -> ValidationResult<()> { + self.0.append(hdl, org) + } + + pub fn append_title<'a>(self, ttl: Title<'a>, org: &mut Org<'a>) -> ValidationResult<()> { + self.0.append(Headline::new(ttl, org), org) + } + + pub fn prepend(self, hdl: Headline, org: &mut Org) -> ValidationResult<()> { + hdl.check_detached(org)?; + + if let Some(first) = self.first_child(org) { + hdl.check_level(first.lvl..=usize::MAX)?; + } else { + hdl.check_level(self.0.lvl + 1..=usize::MAX)?; + } + + if let Some(sec_n) = self.0.sec_n { + sec_n.insert_after(hdl.hdl_n, &mut org.arena); + } else { + self.0.hdl_n.prepend(hdl.hdl_n, &mut org.arena); + } + + org.debug_validate(); + + Ok(()) + } + + pub fn prepend_title<'a>(self, ttl: Title<'a>, org: &mut Org<'a>) -> ValidationResult<()> { + self.prepend(Headline::new(ttl, org), org) + } +} + +#[derive(Copy, Clone, Debug)] +pub struct Headline { + lvl: usize, + hdl_n: NodeId, + ttl_n: NodeId, + sec_n: Option, +} + +impl Headline { + pub fn new<'a>(ttl: Title<'a>, org: &mut Org<'a>) -> Headline { + let lvl = ttl.level; + let hdl_n = org.arena.new_node(Element::Headline { level: ttl.level }); + let ttl_n = org.arena.new_node(Element::Document); // placeholder + hdl_n.append(ttl_n, &mut org.arena); + + match ttl.raw { + Cow::Borrowed(content) => parse_container( + &mut org.arena, + Container::Inline { + node: ttl_n, + content, + }, + &ParseConfig::default(), + ), + Cow::Owned(ref content) => parse_container( + &mut OwnedArena::new(&mut org.arena), + Container::Inline { + node: ttl_n, + content, + }, + &ParseConfig::default(), + ), + } + + *org.arena[ttl_n].get_mut() = Element::Title(ttl); + + Headline { + lvl, + hdl_n, + ttl_n, + sec_n: None, + } + } + + pub(crate) fn from_node(hdl_n: NodeId, lvl: usize, org: &Org) -> Headline { + let ttl_n = org.arena[hdl_n].first_child().unwrap(); + let sec_n = org.arena[ttl_n] + .next_sibling() + .and_then(|n| match org.arena[n].get() { + Element::Section => Some(n), + _ => None, + }); + Headline { + lvl, + hdl_n, + ttl_n, + sec_n, + } + } + + pub fn level(self) -> usize { + self.lvl + } + + pub fn headline_node(self) -> NodeId { + self.hdl_n + } + + pub fn title_node(self) -> NodeId { + self.ttl_n + } + + pub fn section_node(self) -> Option { + self.sec_n + } + + pub fn title<'a: 'b, 'b>(self, org: &'b Org<'a>) -> &'b Title<'a> { + match org.arena[self.ttl_n].get() { + Element::Title(title) => title, + _ => unreachable!(), + } + } + + pub fn title_mut<'a: 'b, 'b>(self, org: &'b mut Org<'a>) -> &'b mut Title<'a> { + match org.arena[self.ttl_n].get_mut() { + Element::Title(title) => title, + _ => unreachable!(), + } + } + + pub fn set_title_content<'a, S: Into>>(self, content: S, org: &mut Org<'a>) { + let content = content.into(); + + let children: Vec<_> = self.ttl_n.children(&org.arena).collect(); + for child in children { + child.detach(&mut org.arena); + } + + match &content { + Cow::Borrowed(content) => parse_container( + &mut org.arena, + Container::Inline { + node: self.ttl_n, + content, + }, + &ParseConfig::default(), + ), + Cow::Owned(ref content) => parse_container( + &mut OwnedArena::new(&mut org.arena), + Container::Inline { + node: self.ttl_n, + content, + }, + &ParseConfig::default(), + ), + } + + self.title_mut(org).raw = content; + + org.debug_validate(); + } + + pub fn set_section_content<'a, S: Into>>( + &mut self, + content: S, + org: &mut Org<'a>, + ) { + let sec_n = if let Some(sec_n) = self.sec_n { + let children: Vec<_> = sec_n.children(&org.arena).collect(); + for child in children { + child.detach(&mut org.arena); + } + sec_n + } else { + let sec_n = org.arena.new_node(Element::Section); + self.sec_n = Some(sec_n); + self.ttl_n.insert_after(sec_n, &mut org.arena); + sec_n + }; + + match content.into() { + Cow::Borrowed(content) => parse_container( + &mut org.arena, + Container::Block { + node: sec_n, + content, + }, + &ParseConfig::default(), + ), + Cow::Owned(ref content) => parse_container( + &mut OwnedArena::new(&mut org.arena), + Container::Block { + node: sec_n, + content, + }, + &ParseConfig::default(), + ), + } + + org.debug_validate(); + } + + pub fn parent(self, org: &Org) -> Option { + org.arena[self.hdl_n] + .parent() + .and_then(|n| match *org.arena[n].get() { + Element::Headline { level } => Some(Headline::from_node(n, level, org)), + Element::Document => None, + _ => unreachable!(), + }) + } + + pub fn children<'a>(self, org: &'a Org) -> impl Iterator + 'a { + self.hdl_n + .children(&org.arena) + .skip(if self.sec_n.is_some() { 2 } else { 1 }) + .filter_map(move |n| match *org.arena[n].get() { + Element::Headline { level } => Some(Headline::from_node(n, level, org)), + _ => unreachable!(), + }) + } + + pub fn first_child(self, org: &Org) -> Option { + self.hdl_n + .children(&org.arena) + .nth(if self.sec_n.is_some() { 2 } else { 1 }) + .map(|n| match *org.arena[n].get() { + Element::Headline { level } => Headline::from_node(n, level, org), + _ => unreachable!(), + }) + } + + pub fn last_child(self, org: &Org) -> Option { + org.arena[self.hdl_n] + .last_child() + .and_then(|n| match *org.arena[n].get() { + Element::Headline { level } => Some(Headline::from_node(n, level, org)), + Element::Section | Element::Title(_) => None, + _ => unreachable!(), + }) + } + + pub fn previous(self, org: &Org) -> Option { + org.arena[self.hdl_n] + .previous_sibling() + .map(|n| match *org.arena[n].get() { + Element::Headline { level } => Headline::from_node(n, level, org), + _ => unreachable!(), + }) + } + + pub fn next(self, org: &Org) -> Option { + org.arena[self.hdl_n] + .next_sibling() + .map(|n| match *org.arena[n].get() { + Element::Headline { level } => Headline::from_node(n, level, org), + _ => unreachable!(), + }) + } + + pub fn detach(self, org: &mut Org) { + self.hdl_n.detach(&mut org.arena); + } + + pub fn is_detached(self, org: &Org) -> bool { + self.parent(&org).is_none() + } + + pub fn append(self, hdl: Headline, org: &mut Org) -> ValidationResult<()> { + hdl.check_detached(org)?; + + if let Some(last) = self.last_child(org) { + hdl.check_level(self.lvl + 1..=last.lvl)?; + } else { + hdl.check_level(self.lvl + 1..=usize::MAX)?; + } + + self.hdl_n.append(hdl.hdl_n, &mut org.arena); + + org.debug_validate(); + + Ok(()) + } + + pub fn append_title<'a>(self, ttl: Title<'a>, org: &mut Org<'a>) -> ValidationResult<()> { + self.append(Headline::new(ttl, org), org) + } + + pub fn prepend(self, hdl: Headline, org: &mut Org) -> ValidationResult<()> { + hdl.check_detached(org)?; + + if let Some(first) = self.first_child(org) { + hdl.check_level(first.lvl..=usize::MAX)?; + } else { + hdl.check_level(self.lvl + 1..=usize::MAX)?; + } + + self.sec_n + .unwrap_or(self.ttl_n) + .insert_after(hdl.hdl_n, &mut org.arena); + + org.debug_validate(); + + Ok(()) + } + + pub fn prepend_title<'a>(self, ttl: Title<'a>, org: &mut Org<'a>) -> ValidationResult<()> { + self.prepend(Headline::new(ttl, org), org) + } + + pub fn insert_before(self, hdl: Headline, org: &mut Org) -> ValidationResult<()> { + hdl.check_detached(org)?; + + if let Some(previous) = self.previous(org) { + hdl.check_level(self.lvl..=previous.lvl)?; + } else { + hdl.check_level(self.lvl..=usize::MAX)?; + } + + self.hdl_n.insert_before(hdl.hdl_n, &mut org.arena); + + org.debug_validate(); + + Ok(()) + } + + pub fn insert_title_before<'a>( + self, + ttl: Title<'a>, + org: &mut Org<'a>, + ) -> ValidationResult<()> { + self.insert_before(Headline::new(ttl, org), org) + } + + pub fn insert_after(self, hdl: Headline, org: &mut Org) -> ValidationResult<()> { + hdl.check_detached(org)?; + + if let Some(next) = self.next(org) { + hdl.check_level(next.lvl..=self.lvl)?; + } else if let Some(parent) = self.parent(org) { + hdl.check_level(parent.lvl + 1..=self.lvl)?; + } else { + hdl.check_level(1..=self.lvl)?; + } + + self.hdl_n.insert_after(hdl.hdl_n, &mut org.arena); + + org.debug_validate(); + + Ok(()) + } + + pub fn insert_title_after<'a>(self, ttl: Title<'a>, org: &mut Org<'a>) -> ValidationResult<()> { + self.insert_after(Headline::new(ttl, org), org) + } + + fn check_detached(self, org: &Org) -> ValidationResult<()> { + if !self.is_detached(org) { + Err(ValidationError::ExpectedDetached { at: self.hdl_n }) + } else { + Ok(()) + } + } + + fn check_level(self, range: RangeInclusive) -> ValidationResult<()> { + if !range.contains(&self.lvl) { + Err(ValidationError::HeadlineLevelMismatch { + range, + at: self.hdl_n, + }) + } else { + Ok(()) + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 5ac093a..d7148b1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -218,15 +218,13 @@ //! //! MIT -#![allow(clippy::range_plus_one)] - mod config; pub mod elements; -mod error; pub mod export; -mod node; +mod headline; mod org; mod parsers; +mod validate; // Re-export of the indextree crate. pub use indextree; @@ -235,6 +233,6 @@ pub use syntect; pub use config::ParseConfig; pub use elements::Element; -pub use error::OrgizeError; -pub use node::{DocumentNode, HeadlineNode}; +pub use headline::{Document, Headline}; pub use org::{Event, Org}; +pub use validate::ValidationError; diff --git a/src/node.rs b/src/node.rs deleted file mode 100644 index b817b35..0000000 --- a/src/node.rs +++ /dev/null @@ -1,374 +0,0 @@ -use indextree::NodeId; -use std::borrow::Cow; - -use crate::config::ParseConfig; -use crate::elements::{Element, Title}; -use crate::parsers::{parse_container, Container, OwnedArena}; -use crate::{Org, OrgizeError}; - -#[derive(Copy, Clone, Debug)] -pub struct HeadlineNode { - pub(crate) node: NodeId, - pub(crate) level: usize, - pub(crate) title_node: NodeId, - pub(crate) section_node: Option, -} - -impl HeadlineNode { - pub(crate) fn new(node: NodeId, level: usize, org: &Org) -> HeadlineNode { - let title_node = org.arena[node].first_child().unwrap(); - let section_node = if let Some(node) = org.arena[title_node].next_sibling() { - if let Element::Section = org.arena[node].get() { - Some(node) - } else { - None - } - } else { - None - }; - HeadlineNode { - node, - level, - title_node, - section_node, - } - } - - pub fn node(self) -> NodeId { - self.node - } - - pub fn title_node(self) -> NodeId { - self.title_node - } - - pub fn section_node(self) -> Option { - self.section_node - } - - pub fn level(self) -> usize { - self.level - } - - pub fn title<'a: 'b, 'b>(self, org: &'b Org<'a>) -> &'b Title<'a> { - if let Element::Title(title) = org.arena[self.title_node].get() { - title - } else { - unreachable!() - } - } - - pub fn title_mut<'a: 'b, 'b>(self, org: &'b mut Org<'a>) -> &'b mut Title<'a> { - if let Element::Title(title) = org.arena[self.title_node].get_mut() { - title - } else { - unreachable!() - } - } - - pub fn set_title_content<'a, S: Into>>(self, content: S, org: &mut Org<'a>) { - let content = content.into(); - - let children: Vec<_> = self.title_node.children(&org.arena).collect(); - for child in children { - child.detach(&mut org.arena); - } - - match &content { - Cow::Borrowed(content) => parse_container( - &mut org.arena, - Container::Inline { - node: self.title_node, - content, - }, - &ParseConfig::default(), - ), - Cow::Owned(ref content) => parse_container( - &mut OwnedArena::new(&mut org.arena), - Container::Inline { - node: self.title_node, - content, - }, - &ParseConfig::default(), - ), - } - - self.title_mut(org).raw = content; - - org.debug_validate(); - } - - pub fn set_section_content<'a, S: Into>>(self, content: S, org: &mut Org<'a>) { - let node = if let Some(node) = self.section_node { - let children: Vec<_> = node.children(&org.arena).collect(); - for child in children { - child.detach(&mut org.arena); - } - node - } else { - let node = org.arena.new_node(Element::Section); - self.node.append(node, &mut org.arena); - node - }; - - match content.into() { - Cow::Borrowed(content) => parse_container( - &mut org.arena, - Container::Block { node, content }, - &ParseConfig::default(), - ), - Cow::Owned(ref content) => parse_container( - &mut OwnedArena::new(&mut org.arena), - Container::Block { node, content }, - &ParseConfig::default(), - ), - } - - org.debug_validate(); - } - - pub fn parent(self, org: &Org) -> Option { - org.arena[self.node].parent().map(|node| { - if let Element::Headline { level } = *org.arena[node].get() { - HeadlineNode::new(node, level, org) - } else { - unreachable!() - } - }) - } - - pub fn children<'a>(self, org: &'a Org) -> impl Iterator + 'a { - self.node.children(&org.arena).filter_map(move |node| { - if let Element::Headline { level } = *org.arena[node].get() { - Some(HeadlineNode::new(node, level, org)) - } else { - None - } - }) - } - - pub fn previous_headline(self, org: &Org) -> Option { - if let Some(node) = org.arena[self.node].previous_sibling() { - if let Element::Headline { level } = *org.arena[node].get() { - Some(HeadlineNode::new(node, level, org)) - } else { - debug_assert_eq!(node, self.section_node.unwrap()); - None - } - } else { - None - } - } - - pub fn next_headline(self, org: &Org) -> Option { - if let Some(node) = org.arena[self.node].next_sibling() { - if let Element::Headline { level } = *org.arena[node].get() { - Some(HeadlineNode::new(node, level, org)) - } else { - unreachable!() - } - } else { - None - } - } - - pub fn detach(self, org: &mut Org) { - self.node.detach(&mut org.arena); - - org.debug_validate(); - } - - pub fn is_detached(self, org: &Org) -> bool { - self.parent(&org).is_none() - } - - fn check_level(self, min: usize, max: Option) -> Result<(), OrgizeError> { - match max { - Some(max) if self.level > max || self.level < min => Err(OrgizeError::HeadlineLevel { - min: Some(min), - max: Some(max), - at: self.node, - }), - None if self.level < min => Err(OrgizeError::HeadlineLevel { - min: Some(min), - max: None, - at: self.node, - }), - _ => Ok(()), - } - } - - pub fn append(self, headline: HeadlineNode, org: &mut Org) -> Result<(), OrgizeError> { - if !headline.is_detached(org) { - return Err(OrgizeError::Detached { at: headline.node }); - } - - if let Some(last_headline) = org.headlines().last() { - headline.check_level(self.level + 1, Some(last_headline.level))?; - } else { - headline.check_level(self.level + 1, None)?; - } - - self.node.append(headline.node, &mut org.arena); - - org.debug_validate(); - - Ok(()) - } - - pub fn prepend(self, headline: HeadlineNode, org: &mut Org) -> Result<(), OrgizeError> { - if !headline.is_detached(org) { - return Err(OrgizeError::Detached { at: headline.node }); - } - - if let Some(first_headline) = self.children(org).next() { - headline.check_level(first_headline.level, None)?; - } else { - headline.check_level(self.level + 1, None)?; - } - - if let Some(node) = self.section_node { - node.insert_after(headline.node, &mut org.arena); - } else { - self.title_node.insert_after(headline.node, &mut org.arena); - } - - org.debug_validate(); - - Ok(()) - } - - pub fn insert_before(self, headline: HeadlineNode, org: &mut Org) -> Result<(), OrgizeError> { - if !headline.is_detached(org) { - return Err(OrgizeError::Detached { at: headline.node }); - } - - if let Some(previous) = self.previous_headline(org) { - headline.check_level(self.level, Some(previous.level))?; - } else { - headline.check_level(self.level, None)?; - } - - self.node.insert_before(headline.node, &mut org.arena); - - org.debug_validate(); - - Ok(()) - } - - pub fn insert_after(self, headline: HeadlineNode, org: &mut Org) -> Result<(), OrgizeError> { - if !headline.is_detached(org) { - return Err(OrgizeError::Detached { at: headline.node }); - } - - if let Some(next) = self.next_headline(org) { - headline.check_level(next.level, Some(self.level))?; - } else if let Some(parent) = self.parent(org) { - headline.check_level(parent.level + 1, Some(self.level))?; - } else { - headline.check_level(1, Some(self.level))?; - } - - self.node.insert_after(headline.node, &mut org.arena); - - org.debug_validate(); - - Ok(()) - } -} - -#[derive(Copy, Clone, Debug)] -pub struct DocumentNode { - section_node: Option, -} - -impl DocumentNode { - pub(crate) fn new(org: &Org) -> DocumentNode { - if let Some(node) = org.arena[org.root].first_child() { - if let Element::Section = org.arena[node].get() { - DocumentNode { - section_node: Some(node), - } - } else { - DocumentNode { section_node: None } - } - } else { - DocumentNode { section_node: None } - } - } - - pub fn children<'a>(self, org: &'a Org) -> impl Iterator + 'a { - org.root.children(&org.arena).filter_map(move |node| { - if let Element::Headline { level } = *org.arena[node].get() { - Some(HeadlineNode::new(node, level, org)) - } else { - None - } - }) - } - - pub fn set_section_content<'a, S: Into>>(self, content: S, org: &mut Org<'a>) { - let node = if let Some(node) = self.section_node { - let children: Vec<_> = node.children(&org.arena).collect(); - for child in children { - child.detach(&mut org.arena); - } - node - } else { - let node = org.arena.new_node(Element::Section); - org.root.append(node, &mut org.arena); - node - }; - - match content.into() { - Cow::Borrowed(content) => parse_container( - &mut org.arena, - Container::Block { node, content }, - &ParseConfig::default(), - ), - Cow::Owned(ref content) => parse_container( - &mut OwnedArena::new(&mut org.arena), - Container::Block { node, content }, - &ParseConfig::default(), - ), - } - - org.debug_validate(); - } - - pub fn append(self, headline: HeadlineNode, org: &mut Org) -> Result<(), OrgizeError> { - if !headline.is_detached(org) { - return Err(OrgizeError::Detached { at: headline.node }); - } - - if let Some(last_headline) = org.headlines().last() { - headline.check_level(1, Some(last_headline.level))?; - } - - org.root.append(headline.node, &mut org.arena); - - org.debug_validate(); - - Ok(()) - } - - pub fn prepend(self, headline: HeadlineNode, org: &mut Org) -> Result<(), OrgizeError> { - if !headline.is_detached(org) { - return Err(OrgizeError::Detached { at: headline.node }); - } - - if let Some(first_headline) = self.children(org).next() { - headline.check_level(first_headline.level, None)?; - } - - if let Some(node) = self.section_node { - node.insert_after(headline.node, &mut org.arena); - } else { - org.root.prepend(headline.node, &mut org.arena); - } - - org.debug_validate(); - - Ok(()) - } -} diff --git a/src/org.rs b/src/org.rs index 95888a9..5cdc33d 100644 --- a/src/org.rs +++ b/src/org.rs @@ -1,11 +1,13 @@ use indextree::{Arena, NodeEdge, NodeId}; use std::io::{Error, Write}; -use crate::config::{ParseConfig, DEFAULT_CONFIG}; -use crate::elements::{Element, Title}; -use crate::export::*; -use crate::node::{DocumentNode, HeadlineNode}; -use crate::parsers::{parse_container, Container}; +use crate::{ + config::{ParseConfig, DEFAULT_CONFIG}, + elements::Element, + export::{DefaultHtmlHandler, DefaultOrgHandler, HtmlHandler, OrgHandler}, + headline::{Document, Headline}, + parsers::{parse_container, Container}, +}; pub struct Org<'a> { pub(crate) arena: Arena>, @@ -19,7 +21,7 @@ pub enum Event<'a, 'b> { } impl<'a> Org<'a> { - /// Create a new empty Org struct + /// Create a new empty `Org` struct pub fn new() -> Org<'static> { let mut arena = Arena::new(); let root = arena.new_node(Element::Document); @@ -27,7 +29,7 @@ impl<'a> Org<'a> { Org { arena, root } } - /// Create a new Org struct from parsing `text`, using the default ParseConfig + /// Create a new `Org` struct from parsing `text`, using the default ParseConfig pub fn parse(text: &'a str) -> Org<'a> { Org::parse_with_config(text, &DEFAULT_CONFIG) } @@ -50,18 +52,18 @@ impl<'a> Org<'a> { org } - /// Return a DocumentNode - pub fn document(&self) -> DocumentNode { - DocumentNode::new(self) + /// Return a `Document` + pub fn document(&self) -> Document { + Document::from_org(self) } - /// Return an iterator of HeadlineNode - pub fn headlines<'b>(&'b self) -> impl Iterator + 'b { + /// Return an iterator of `Headline` + pub fn headlines<'b>(&'b self) -> impl Iterator + 'b { self.root .descendants(&self.arena) .skip(1) .filter_map(move |node| match &self.arena[node].get() { - Element::Headline { level } => Some(HeadlineNode::new(node, *level, self)), + Element::Headline { level } => Some(Headline::from_node(node, *level, self)), _ => None, }) } @@ -76,23 +78,6 @@ impl<'a> Org<'a> { &mut self.arena } - /// Create a new headline and return it's HeadlineNode - pub fn new_headline(&mut self, title: Title<'a>) -> HeadlineNode { - let level = title.level; - let title_raw = title.raw.clone(); - let headline_node = self.arena.new_node(Element::Headline { level }); - let title_node = self.arena.new_node(Element::Title(title)); - headline_node.append(title_node, &mut self.arena); - let headline_node = HeadlineNode { - node: headline_node, - level, - title_node, - section_node: None, - }; - headline_node.set_title_content(title_raw, self); - headline_node - } - /// Return an iterator of Event pub fn iter<'b>(&'b self) -> impl Iterator> + 'b { self.root.traverse(&self.arena).map(move |edge| match edge { diff --git a/src/validate.rs b/src/validate.rs new file mode 100644 index 0000000..559a536 --- /dev/null +++ b/src/validate.rs @@ -0,0 +1,195 @@ +use indextree::NodeId; +use std::ops::RangeInclusive; + +use crate::elements::{Element, Table, TableRow, Title}; +use crate::Org; + +/// Validation Error +#[derive(Debug)] +pub enum ValidationError { + /// Expected at least one child + ExpectedChildren { + at: NodeId, + }, + /// Expected no children + UnexpectedChildren { + at: NodeId, + }, + UnexpectedElement { + expected: &'static str, + at: NodeId, + }, + /// Expect a detached element + ExpectedDetached { + at: NodeId, + }, + HeadlineLevelMismatch { + range: RangeInclusive, + at: NodeId, + }, +} + +impl ValidationError { + pub fn element<'a, 'b>(&self, org: &'a Org<'b>) -> &'a Element<'b> { + match &self { + ValidationError::ExpectedChildren { at } + | ValidationError::UnexpectedChildren { at } + | ValidationError::UnexpectedElement { at, .. } + | ValidationError::ExpectedDetached { at } + | ValidationError::HeadlineLevelMismatch { at, .. } => org.arena[*at].get(), + } + } +} + +pub type ValidationResult = Result; + +impl Org<'_> { + /// Validate an `Org` struct. + pub fn validate(&self) -> Vec { + let mut errors = Vec::new(); + + macro_rules! expect { + ($node:ident, $expect:expr, $($pattern:pat)|+) => { + match self.arena[$node].get() { + $($pattern)|+ => (), + _ => errors.push(ValidationError::UnexpectedElement { + expected: $expect, + at: $node + }), + } + }; + } + + for node_id in self.root.descendants(&self.arena) { + let node = &self.arena[node_id]; + match node.get() { + Element::Document => { + let mut children = node_id.children(&self.arena); + if let Some(node) = children.next() { + expect!( + node, + "Headline,Section", + Element::Headline { .. } | Element::Section + ); + } + for node in children { + expect!( + node, + "Headline", + Element::Headline { .. } + ); + } + } + Element::Headline { .. } => { + if node.first_child().is_some() { + let mut children = node_id.children(&self.arena); + if let Some(node) = children.next() { + expect!(node, "Title", Element::Title(_)); + } + if let Some(node) = children.next() { + expect!( + node, + "Headline,Section", + Element::Headline { .. } | Element::Section + ); + } + for node in children { + expect!( + node, + "Headline", + Element::Headline { .. } + ); + } + } else { + errors.push(ValidationError::ExpectedChildren { at: node_id }); + } + } + Element::Title(Title { raw, .. }) => { + if !raw.is_empty() && node.first_child().is_none() { + errors.push(ValidationError::ExpectedChildren { at: node_id }); + } + } + Element::CommentBlock(_) + | Element::ExampleBlock(_) + | Element::ExportBlock(_) + | Element::SourceBlock(_) + | Element::BabelCall(_) + | Element::InlineSrc(_) + | Element::Code { .. } + | Element::FnRef(_) + | Element::InlineCall(_) + | Element::Link(_) + | Element::Macros(_) + | Element::RadioTarget + | Element::Snippet(_) + | Element::Target(_) + | Element::Text { .. } + | Element::Timestamp(_) + | Element::Verbatim { .. } + | Element::FnDef(_) + | Element::Clock(_) + | Element::Comment { .. } + | Element::FixedWidth { .. } + | Element::Keyword(_) + | Element::Rule + | Element::Cookie(_) + | Element::Table(Table::TableEl { .. }) + | Element::TableRow(TableRow::Rule) => { + if node.first_child().is_some() { + errors.push(ValidationError::UnexpectedChildren { at: node_id }); + } + } + Element::List(_) => { + if node.first_child().is_some() { + for node in node_id.children(&self.arena) { + expect!(node, "ListItem", Element::ListItem(_)); + } + } else { + errors.push(ValidationError::ExpectedChildren { at: node_id }); + } + } + Element::SpecialBlock(_) + | Element::QuoteBlock(_) + | Element::CenterBlock(_) + | Element::VerseBlock(_) + | Element::Paragraph + | Element::Section + | Element::Table(Table::Org { .. }) + | Element::TableRow(TableRow::Standard) + | Element::Bold + | Element::Italic + | Element::Underline + | Element::Strike + | Element::DynBlock(_) + | Element::ListItem(_) => { + if node.first_child().is_none() { + errors.push(ValidationError::ExpectedChildren { at: node_id }); + } + } + // TableCell is a container but it might + // not contains anything, e.g. `||||||` + Element::Drawer(_) | Element::TableCell => (), + } + } + errors + } + + #[deprecated(since = "0.3.1", note = "rename to validate")] + /// Validate an `Org` struct. + pub fn check(&self) -> Vec { + self.validate() + } + + pub(crate) fn debug_validate(&self) { + if cfg!(debug_assertions) { + let errors = self.validate(); + if !errors.is_empty() { + eprintln!("Validation failed. {} error(s) found:", errors.len()); + for err in &errors { + eprintln!("{:?} at {:?}", err, err.element(self)); + } + panic!(); + } + } + } +} diff --git a/tests/headline.rs b/tests/headline.rs new file mode 100644 index 0000000..c94f7b9 --- /dev/null +++ b/tests/headline.rs @@ -0,0 +1,76 @@ +use orgize::{elements::Title, Headline, Org}; + +use pretty_assertions::assert_eq; + +#[test] +fn set_content() { + let mut org = Org::parse( + r#"* title 1 +section 1 +** title 2 +"#, + ); + let headlines: Vec<_> = org.headlines().collect(); + for mut headline in headlines { + headline.set_title_content(String::from("a *bold* title"), &mut org); + headline.set_section_content("and a _underline_ section", &mut org); + } + let mut writer = Vec::new(); + org.html(&mut writer).unwrap(); + assert_eq!( + String::from_utf8(writer).unwrap(), + "

a bold title

and a underline section

\ +

a bold title

and a underline section

" + ); +} + +#[test] +fn insert() { + let org = &mut Org::new(); + let mut document = org.document(); + + let mut h1 = Headline::new( + Title { + level: 1, + raw: "title".into(), + ..Default::default() + }, + org, + ); + h1.set_section_content("section", org); + document.prepend(h1, org).unwrap(); + + let mut h3 = Headline::new( + Title { + level: 3, + raw: "title".into(), + ..Default::default() + }, + org, + ); + h3.set_section_content("section", org); + document.prepend(h3, org).unwrap(); + + let mut h2 = Headline::new( + Title { + level: 2, + raw: "title".into(), + ..Default::default() + }, + org, + ); + h2.set_section_content("section", org); + h1.insert_before(h2, org).unwrap(); + + document.set_section_content("section", org); + + let mut writer = Vec::new(); + org.html(&mut writer).unwrap(); + assert_eq!( + String::from_utf8(writer).unwrap(), + "

section

\ +

title

section

\ +

title

section

\ +

title

section

" + ); +} diff --git a/tests/node.rs b/tests/node.rs deleted file mode 100644 index f4aa094..0000000 --- a/tests/node.rs +++ /dev/null @@ -1,68 +0,0 @@ -use orgize::elements::Title; -use orgize::Org; -use pretty_assertions::assert_eq; -use serde_json::to_string; - -#[test] -fn set_content() { - let mut org = Org::parse( - r#"* title 1 -section 1 -** title 2 -"#, - ); - let headlines: Vec<_> = org.headlines().collect(); - for headline in headlines { - headline.set_title_content(String::from("a *bold* title"), &mut org); - headline.set_section_content("and a _underline_ section", &mut org); - } - let mut writer = Vec::new(); - org.html(&mut writer).unwrap(); - assert_eq!( - String::from_utf8(writer).unwrap(), - "

a bold title

and a underline section

\ -

a bold title

and a underline section

" - ); -} - -#[test] -fn insert() { - let mut org = Org::new(); - let document = org.document(); - - let h1 = org.new_headline(Title { - level: 1, - raw: "title".into(), - ..Default::default() - }); - h1.set_section_content("section", &mut org); - document.prepend(h1, &mut org).unwrap(); - dbg!(to_string(&org).unwrap()); - - let h3 = org.new_headline(Title { - level: 3, - raw: "title".into(), - ..Default::default() - }); - h3.set_section_content("section", &mut org); - document.prepend(h3, &mut org).unwrap(); - dbg!(to_string(&org).unwrap()); - - let h2 = org.new_headline(Title { - level: 2, - raw: "title".into(), - ..Default::default() - }); - h2.set_section_content("section", &mut org); - h1.insert_before(h2, &mut org).unwrap(); - dbg!(to_string(&org).unwrap()); - - let mut writer = Vec::new(); - org.html(&mut writer).unwrap(); - assert_eq!( - String::from_utf8(writer).unwrap(), - "

title

section

\ -

title

section

\ -

title

section

" - ); -}