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
|
//! MIT
|
||||||
|
|
||||||
#![allow(clippy::range_plus_one)]
|
|
||||||
|
|
||||||
mod config;
|
mod config;
|
||||||
pub mod elements;
|
pub mod elements;
|
||||||
mod error;
|
|
||||||
pub mod export;
|
pub mod export;
|
||||||
mod node;
|
mod headline;
|
||||||
mod org;
|
mod org;
|
||||||
mod parsers;
|
mod parsers;
|
||||||
|
mod validate;
|
||||||
|
|
||||||
// Re-export of the indextree crate.
|
// Re-export of the indextree crate.
|
||||||
pub use indextree;
|
pub use indextree;
|
||||||
|
@ -235,6 +233,6 @@ pub use syntect;
|
||||||
|
|
||||||
pub use config::ParseConfig;
|
pub use config::ParseConfig;
|
||||||
pub use elements::Element;
|
pub use elements::Element;
|
||||||
pub use error::OrgizeError;
|
pub use headline::{Document, Headline};
|
||||||
pub use node::{DocumentNode, HeadlineNode};
|
|
||||||
pub use org::{Event, Org};
|
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 indextree::{Arena, NodeEdge, NodeId};
|
||||||
use std::io::{Error, Write};
|
use std::io::{Error, Write};
|
||||||
|
|
||||||
use crate::config::{ParseConfig, DEFAULT_CONFIG};
|
use crate::{
|
||||||
use crate::elements::{Element, Title};
|
config::{ParseConfig, DEFAULT_CONFIG},
|
||||||
use crate::export::*;
|
elements::Element,
|
||||||
use crate::node::{DocumentNode, HeadlineNode};
|
export::{DefaultHtmlHandler, DefaultOrgHandler, HtmlHandler, OrgHandler},
|
||||||
use crate::parsers::{parse_container, Container};
|
headline::{Document, Headline},
|
||||||
|
parsers::{parse_container, Container},
|
||||||
|
};
|
||||||
|
|
||||||
pub struct Org<'a> {
|
pub struct Org<'a> {
|
||||||
pub(crate) arena: Arena<Element<'a>>,
|
pub(crate) arena: Arena<Element<'a>>,
|
||||||
|
@ -19,7 +21,7 @@ pub enum Event<'a, 'b> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Org<'a> {
|
impl<'a> Org<'a> {
|
||||||
/// Create a new empty Org struct
|
/// Create a new empty `Org` struct
|
||||||
pub fn new() -> Org<'static> {
|
pub fn new() -> Org<'static> {
|
||||||
let mut arena = Arena::new();
|
let mut arena = Arena::new();
|
||||||
let root = arena.new_node(Element::Document);
|
let root = arena.new_node(Element::Document);
|
||||||
|
@ -27,7 +29,7 @@ impl<'a> Org<'a> {
|
||||||
Org { arena, root }
|
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> {
|
pub fn parse(text: &'a str) -> Org<'a> {
|
||||||
Org::parse_with_config(text, &DEFAULT_CONFIG)
|
Org::parse_with_config(text, &DEFAULT_CONFIG)
|
||||||
}
|
}
|
||||||
|
@ -50,18 +52,18 @@ impl<'a> Org<'a> {
|
||||||
org
|
org
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return a DocumentNode
|
/// Return a `Document`
|
||||||
pub fn document(&self) -> DocumentNode {
|
pub fn document(&self) -> Document {
|
||||||
DocumentNode::new(self)
|
Document::from_org(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return an iterator of HeadlineNode
|
/// Return an iterator of `Headline`
|
||||||
pub fn headlines<'b>(&'b self) -> impl Iterator<Item = HeadlineNode> + 'b {
|
pub fn headlines<'b>(&'b self) -> impl Iterator<Item = Headline> + 'b {
|
||||||
self.root
|
self.root
|
||||||
.descendants(&self.arena)
|
.descendants(&self.arena)
|
||||||
.skip(1)
|
.skip(1)
|
||||||
.filter_map(move |node| match &self.arena[node].get() {
|
.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,
|
_ => None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -76,23 +78,6 @@ impl<'a> Org<'a> {
|
||||||
&mut self.arena
|
&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
|
/// Return an iterator of Event
|
||||||
pub fn iter<'b>(&'b self) -> impl Iterator<Item = Event<'a, 'b>> + 'b {
|
pub fn iter<'b>(&'b self) -> impl Iterator<Item = Event<'a, 'b>> + 'b {
|
||||||
self.root.traverse(&self.arena).map(move |edge| match edge {
|
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