use crate::{RawRdNode, RdTag};
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct RdDocument {
nodes: Vec<RdNode>,
}
impl RdDocument {
pub fn new(nodes: Vec<RdNode>) -> Self {
Self { nodes }
}
pub fn nodes(&self) -> &[RdNode] {
&self.nodes
}
pub fn into_nodes(self) -> Vec<RdNode> {
self.nodes
}
}
impl From<Vec<RdNode>> for RdDocument {
fn from(nodes: Vec<RdNode>) -> Self {
Self::new(nodes)
}
}
impl IntoIterator for RdDocument {
type Item = RdNode;
type IntoIter = std::vec::IntoIter<RdNode>;
fn into_iter(self) -> Self::IntoIter {
self.nodes.into_iter()
}
}
impl<'a> IntoIterator for &'a RdDocument {
type Item = &'a RdNode;
type IntoIter = std::slice::Iter<'a, RdNode>;
fn into_iter(self) -> Self::IntoIter {
self.nodes.iter()
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct RdTagged {
tag: RdTag,
option: Option<Vec<RdNode>>,
children: Vec<RdNode>,
}
impl RdTagged {
pub fn new(tag: RdTag, option: Option<Vec<RdNode>>, children: Vec<RdNode>) -> Self {
Self {
tag,
option,
children,
}
}
pub fn tag(&self) -> &RdTag {
&self.tag
}
pub fn option(&self) -> Option<&[RdNode]> {
self.option.as_deref()
}
pub fn children(&self) -> &[RdNode] {
&self.children
}
pub fn into_parts(self) -> (RdTag, Option<Vec<RdNode>>, Vec<RdNode>) {
(self.tag, self.option, self.children)
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct RdGroup {
children: Vec<RdNode>,
}
impl RdGroup {
pub fn new(children: Vec<RdNode>) -> Self {
Self { children }
}
pub fn children(&self) -> &[RdNode] {
&self.children
}
pub fn into_children(self) -> Vec<RdNode> {
self.children
}
}
impl From<Vec<RdNode>> for RdGroup {
fn from(children: Vec<RdNode>) -> Self {
Self::new(children)
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub enum RdNode {
Text(String),
RCode(String),
Verb(String),
Comment(String),
Tagged(RdTagged),
Group(RdGroup),
Raw(RawRdNode),
}
impl RdNode {
pub fn tagged(tag: RdTag, option: Option<Vec<RdNode>>, children: Vec<RdNode>) -> Self {
Self::Tagged(RdTagged::new(tag, option, children))
}
pub fn group(children: Vec<RdNode>) -> Self {
Self::Group(RdGroup::new(children))
}
pub fn as_tagged(&self) -> Option<&RdTagged> {
match self {
Self::Tagged(node) => Some(node),
_ => None,
}
}
pub fn as_group(&self) -> Option<&RdGroup> {
match self {
Self::Group(node) => Some(node),
_ => None,
}
}
pub fn as_raw(&self) -> Option<&RawRdNode> {
match self {
Self::Raw(node) => Some(node),
_ => None,
}
}
pub fn into_tagged(self) -> Result<RdTagged, Self> {
match self {
Self::Tagged(node) => Ok(node),
other => Err(other),
}
}
pub fn into_group(self) -> Result<RdGroup, Self> {
match self {
Self::Group(node) => Ok(node),
other => Err(other),
}
}
pub fn into_raw(self) -> Result<RawRdNode, Self> {
match self {
Self::Raw(node) => Ok(node),
other => Err(other),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{RawRdValue, producer};
fn sample_document() -> RdDocument {
RdDocument::new(vec![
RdNode::tagged(RdTag::Name, None, vec![RdNode::Text("example".to_string())]),
RdNode::Text("\n".to_string()),
RdNode::tagged(
RdTag::Title,
None,
vec![RdNode::Text("An example topic".to_string())],
),
RdNode::Text("\n".to_string()),
RdNode::tagged(
RdTag::Description,
None,
vec![
RdNode::Text("See ".to_string()),
RdNode::tagged(
RdTag::Link,
Some(vec![RdNode::Text("base".to_string())]),
vec![RdNode::Text("print".to_string())],
),
RdNode::Text(" for details.".to_string()),
],
),
])
}
#[test]
fn builds_and_matches_expected_shape() {
let doc = sample_document();
assert_eq!(doc.nodes().len(), 5);
let Some(tagged) = doc.nodes()[0].as_tagged() else {
panic!("expected a Tagged node");
};
assert_eq!(tagged.tag(), &RdTag::Name);
assert_eq!(tagged.children(), &[RdNode::Text("example".to_string())]);
}
#[test]
fn tagged_option_carries_markup() {
let doc = sample_document();
let Some(description) = doc.nodes()[4].as_tagged() else {
panic!("expected the description's Tagged node");
};
let Some(link) = description.children()[1].as_tagged() else {
panic!(r"expected the \link node");
};
assert_eq!(link.tag(), &RdTag::Link);
assert_eq!(link.option(), Some(&[RdNode::Text("base".to_string())][..]));
}
#[test]
fn raw_node_preserves_untagged_group_and_attributes() {
let raw = producer::raw_node(
None,
None,
vec![RdNode::Text("print".to_string())],
None,
vec![producer::raw_attribute(
"class".to_string(),
producer::raw_object(
RawRdValue::Character(vec![Some("weird".to_string())]),
Vec::new(),
),
)],
);
let node = RdNode::Raw(raw.clone());
let RdNode::Raw(inner) = &node else {
panic!("expected Raw");
};
assert_eq!(inner, &raw);
assert_eq!(inner.tag(), None);
assert_eq!(inner.attributes().len(), 1);
}
#[test]
fn equal_documents_compare_equal() {
assert_eq!(sample_document(), sample_document());
}
}