chore: rename node and error crate

This commit is contained in:
PoiScript 2019-10-27 10:36:41 +08:00
parent 499f0de508
commit 3b646aa7a5
8 changed files with 763 additions and 637 deletions

View file

@ -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<usize>,
min: Option<usize>,
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)
);
}
}
}
}

473
src/headline.rs Normal file
View file

@ -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<NodeId> {
self.0.sec_n
}
pub fn children<'a>(self, org: &'a Org) -> impl Iterator<Item = Headline> + '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<Headline> {
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<Headline> {
self.0.last_child(org)
}
pub fn set_section_content<'a, S: Into<Cow<'a, str>>>(
&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<NodeId>,
}
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<NodeId> {
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<Cow<'a, str>>>(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<Cow<'a, str>>>(
&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<Headline> {
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<Item = Headline> + '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<Headline> {
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<Headline> {
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<Headline> {
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<Headline> {
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<usize>) -> ValidationResult<()> {
if !range.contains(&self.lvl) {
Err(ValidationError::HeadlineLevelMismatch {
range,
at: self.hdl_n,
})
} else {
Ok(())
}
}
}

View file

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

View file

@ -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<NodeId>,
}
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<NodeId> {
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<Cow<'a, str>>>(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<Cow<'a, str>>>(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<HeadlineNode> {
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<Item = HeadlineNode> + '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<HeadlineNode> {
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<HeadlineNode> {
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<usize>) -> 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<NodeId>,
}
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<Item = HeadlineNode> + '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<Cow<'a, str>>>(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(())
}
}

View file

@ -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<Element<'a>>,
@ -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<Item = HeadlineNode> + 'b {
/// Return an iterator of `Headline`
pub fn headlines<'b>(&'b self) -> impl Iterator<Item = Headline> + '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<Item = Event<'a, 'b>> + 'b {
self.root.traverse(&self.arena).map(move |edge| match edge {

195
src/validate.rs Normal file
View file

@ -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<usize>,
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<T> = Result<T, ValidationError>;
impl Org<'_> {
/// Validate an `Org` struct.
pub fn validate(&self) -> Vec<ValidationError> {
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<ValidationError> {
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!();
}
}
}
}

76
tests/headline.rs Normal file
View file

@ -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(),
"<main><h1>a <b>bold</b> title</h1><section><p>and a <u>underline</u> section</p></section>\
<h2>a <b>bold</b> title</h2><section><p>and a <u>underline</u> section</p></section></main>"
);
}
#[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(),
"<main><section><p>section</p></section>\
<h3>title</h3><section><p>section</p></section>\
<h2>title</h2><section><p>section</p></section>\
<h1>title</h1><section><p>section</p></section></main>"
);
}

View file

@ -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(),
"<main><h1>a <b>bold</b> title</h1><section><p>and a <u>underline</u> section</p></section>\
<h2>a <b>bold</b> title</h2><section><p>and a <u>underline</u> section</p></section></main>"
);
}
#[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(),
"<main><h3>title</h3><section><p>section</p></section>\
<h2>title</h2><section><p>section</p></section>\
<h1>title</h1><section><p>section</p></section></main>"
);
}