chore: rename node and error crate
This commit is contained in:
parent
499f0de508
commit
3b646aa7a5
159
src/error.rs
159
src/error.rs
|
@ -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
473
src/headline.rs
Normal 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(())
|
||||
}
|
||||
}
|
||||
}
|
10
src/lib.rs
10
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;
|
||||
|
|
374
src/node.rs
374
src/node.rs
|
@ -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(())
|
||||
}
|
||||
}
|
45
src/org.rs
45
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<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
195
src/validate.rs
Normal 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
76
tests/headline.rs
Normal 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>"
|
||||
);
|
||||
}
|
|
@ -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>"
|
||||
);
|
||||
}
|
Loading…
Reference in a new issue