use crate::{Document, Item, MatrixList, Node, Value};
#[derive(Debug, Clone)]
pub struct VisitorContext<'a> {
pub depth: usize,
pub path: Vec<&'a str>,
pub document: &'a Document,
pub current_schema: Option<&'a [String]>,
}
impl<'a> VisitorContext<'a> {
pub fn new(document: &'a Document) -> Self {
Self {
depth: 0,
path: Vec::new(),
document,
current_schema: None,
}
}
pub fn child(&self, key: &'a str) -> Self {
let mut path = self.path.clone();
path.push(key);
Self {
depth: self.depth + 1,
path,
document: self.document,
current_schema: self.current_schema,
}
}
pub fn with_schema(&self, schema: &'a [String]) -> Self {
Self {
depth: self.depth,
path: self.path.clone(),
document: self.document,
current_schema: Some(schema),
}
}
pub fn path_string(&self) -> String {
if self.path.is_empty() {
"root".to_string()
} else {
self.path.join(".")
}
}
}
pub trait DocumentVisitor {
type Error;
fn begin_document(
&mut self,
_doc: &Document,
_ctx: &VisitorContext<'_>,
) -> Result<(), Self::Error> {
Ok(())
}
fn end_document(
&mut self,
_doc: &Document,
_ctx: &VisitorContext<'_>,
) -> Result<(), Self::Error> {
Ok(())
}
fn visit_scalar(
&mut self,
key: &str,
value: &Value,
ctx: &VisitorContext<'_>,
) -> Result<(), Self::Error>;
fn begin_object(&mut self, _key: &str, _ctx: &VisitorContext<'_>) -> Result<(), Self::Error> {
Ok(())
}
fn end_object(&mut self, _key: &str, _ctx: &VisitorContext<'_>) -> Result<(), Self::Error> {
Ok(())
}
fn begin_list(
&mut self,
_key: &str,
_list: &MatrixList,
_ctx: &VisitorContext<'_>,
) -> Result<(), Self::Error> {
Ok(())
}
fn end_list(
&mut self,
_key: &str,
_list: &MatrixList,
_ctx: &VisitorContext<'_>,
) -> Result<(), Self::Error> {
Ok(())
}
fn visit_node(
&mut self,
node: &Node,
schema: &[String],
ctx: &VisitorContext<'_>,
) -> Result<(), Self::Error>;
fn begin_node_children(
&mut self,
_node: &Node,
_ctx: &VisitorContext<'_>,
) -> Result<(), Self::Error> {
Ok(())
}
fn end_node_children(
&mut self,
_node: &Node,
_ctx: &VisitorContext<'_>,
) -> Result<(), Self::Error> {
Ok(())
}
}
pub fn traverse<V: DocumentVisitor>(doc: &Document, visitor: &mut V) -> Result<(), V::Error> {
let ctx = VisitorContext::new(doc);
visitor.begin_document(doc, &ctx)?;
for (key, item) in &doc.root {
traverse_item(key, item, visitor, &ctx)?;
}
visitor.end_document(doc, &ctx)?;
Ok(())
}
fn traverse_item<V: DocumentVisitor>(
key: &str,
item: &Item,
visitor: &mut V,
ctx: &VisitorContext<'_>,
) -> Result<(), V::Error> {
match item {
Item::Scalar(value) => {
visitor.visit_scalar(key, value, ctx)?;
}
Item::Object(map) => {
visitor.begin_object(key, ctx)?;
let child_ctx = ctx.child(key);
for (child_key, child_item) in map {
traverse_item(child_key, child_item, visitor, &child_ctx)?;
}
visitor.end_object(key, ctx)?;
}
Item::List(list) => {
visitor.begin_list(key, list, ctx)?;
let list_ctx = ctx.child(key).with_schema(&list.schema);
for node in &list.rows {
traverse_node(node, &list.schema, visitor, &list_ctx)?;
}
visitor.end_list(key, list, ctx)?;
}
}
Ok(())
}
fn traverse_node<V: DocumentVisitor>(
node: &Node,
schema: &[String],
visitor: &mut V,
ctx: &VisitorContext<'_>,
) -> Result<(), V::Error> {
visitor.visit_node(node, schema, ctx)?;
if let Some(children) = node.children() {
if !children.is_empty() {
visitor.begin_node_children(node, ctx)?;
let child_ctx = ctx.child(&node.id);
for (child_type, child_nodes) in children {
let child_schema = ctx.document.structs.get(child_type);
let child_schema = child_schema.map(|s| s.as_slice()).unwrap_or(&[]);
for child in child_nodes {
traverse_node(child, child_schema, visitor, &child_ctx)?;
}
}
visitor.end_node_children(node, ctx)?;
}
}
Ok(())
}
#[derive(Debug, Default)]
pub struct StatsCollector {
pub scalar_count: usize,
pub object_count: usize,
pub list_count: usize,
pub node_count: usize,
pub max_depth: usize,
}
impl DocumentVisitor for StatsCollector {
type Error = std::convert::Infallible;
fn visit_scalar(
&mut self,
_key: &str,
_value: &Value,
ctx: &VisitorContext<'_>,
) -> Result<(), Self::Error> {
self.scalar_count += 1;
self.max_depth = self.max_depth.max(ctx.depth);
Ok(())
}
fn begin_object(&mut self, _key: &str, ctx: &VisitorContext<'_>) -> Result<(), Self::Error> {
self.object_count += 1;
self.max_depth = self.max_depth.max(ctx.depth);
Ok(())
}
fn begin_list(
&mut self,
_key: &str,
_list: &MatrixList,
ctx: &VisitorContext<'_>,
) -> Result<(), Self::Error> {
self.list_count += 1;
self.max_depth = self.max_depth.max(ctx.depth);
Ok(())
}
fn visit_node(
&mut self,
_node: &Node,
_schema: &[String],
ctx: &VisitorContext<'_>,
) -> Result<(), Self::Error> {
self.node_count += 1;
self.max_depth = self.max_depth.max(ctx.depth);
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{parse, Value};
#[test]
fn test_traverse_empty_document() {
let hedl = "%V:2.0\n%NULL:~\n%QUOTE:\"\n---\n";
let doc = parse(hedl.as_bytes()).unwrap();
let mut stats = StatsCollector::default();
traverse(&doc, &mut stats).unwrap();
assert_eq!(stats.scalar_count, 0);
assert_eq!(stats.object_count, 0);
assert_eq!(stats.list_count, 0);
assert_eq!(stats.node_count, 0);
}
#[test]
fn test_traverse_scalars() {
let hedl = "%V:2.0\n%NULL:~\n%QUOTE:\"\n---\nname: Test\ncount: 42\n";
let doc = parse(hedl.as_bytes()).unwrap();
let mut stats = StatsCollector::default();
traverse(&doc, &mut stats).unwrap();
assert_eq!(stats.scalar_count, 2);
assert_eq!(stats.max_depth, 0);
}
#[test]
fn test_traverse_nested_objects() {
let hedl = "%V:2.0\n%NULL:~\n%QUOTE:\"\n---\nouter:\n inner:\n value: 42\n";
let doc = parse(hedl.as_bytes()).unwrap();
let mut stats = StatsCollector::default();
traverse(&doc, &mut stats).unwrap();
assert_eq!(stats.object_count, 2);
assert_eq!(stats.scalar_count, 1);
assert_eq!(stats.max_depth, 2);
}
#[test]
fn test_traverse_list() {
let hedl = "%V:2.0\n%NULL:~\n%QUOTE:\"\n%S:User:[id,name]\n---\nusers:@User\n |alice, Alice\n |bob, Bob\n";
let doc = parse(hedl.as_bytes()).unwrap();
let mut stats = StatsCollector::default();
traverse(&doc, &mut stats).unwrap();
assert_eq!(stats.list_count, 1);
assert_eq!(stats.node_count, 2);
}
#[test]
fn test_traverse_nested_nodes() {
let hedl = "%V:2.0\n%NULL:~\n%QUOTE:\"\n%S:User:[id]\n%S:Post:[id]\n%N:User>Post\n---\nusers:@User\n |alice\n |post1\n |post2\n";
let doc = parse(hedl.as_bytes()).unwrap();
let mut stats = StatsCollector::default();
traverse(&doc, &mut stats).unwrap();
assert_eq!(stats.list_count, 1);
assert_eq!(stats.node_count, 3); }
#[test]
fn test_visitor_context_path() {
let hedl = "%V:2.0\n%NULL:~\n%QUOTE:\"\n---\na:\n b:\n c: 42\n";
let doc = parse(hedl.as_bytes()).unwrap();
struct PathCollector {
paths: Vec<String>,
}
impl DocumentVisitor for PathCollector {
type Error = std::convert::Infallible;
fn visit_scalar(
&mut self,
_key: &str,
_value: &Value,
ctx: &VisitorContext<'_>,
) -> Result<(), Self::Error> {
self.paths.push(ctx.path_string());
Ok(())
}
fn begin_object(
&mut self,
_key: &str,
ctx: &VisitorContext<'_>,
) -> Result<(), Self::Error> {
self.paths.push(ctx.path_string());
Ok(())
}
fn visit_node(
&mut self,
_node: &Node,
_schema: &[String],
ctx: &VisitorContext<'_>,
) -> Result<(), Self::Error> {
self.paths.push(ctx.path_string());
Ok(())
}
}
let mut collector = PathCollector { paths: Vec::new() };
traverse(&doc, &mut collector).unwrap();
assert!(collector.paths.contains(&"root".to_string()));
assert!(collector.paths.contains(&"a".to_string()));
assert!(collector.paths.contains(&"a.b".to_string()));
}
}