use super::{TextNode, TextTree};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Control {
Continue,
Prune,
}
pub fn visit(tree: &TextTree, f: &mut dyn FnMut(&TextNode) -> Control) {
visit_depth(tree, &mut |node, _| f(node));
}
pub fn visit_depth(tree: &TextTree, f: &mut dyn FnMut(&TextNode, usize) -> Control) {
visit_depth_at(tree, 0, f);
}
fn visit_depth_at(tree: &TextTree, depth: usize, f: &mut dyn FnMut(&TextNode, usize) -> Control) {
for node in tree {
match f(node, depth) {
Control::Continue => match node {
TextNode::BeginEnd { txt, .. } | TextNode::Encrypted { txt, .. } => {
visit_depth_at(txt, depth + 1, f);
}
_ => {}
},
Control::Prune => {}
}
}
}
pub fn visit_mut(tree: &mut TextTree, f: &mut dyn FnMut(&mut TextNode) -> Control) {
for node in tree.iter_mut() {
match f(node) {
Control::Continue => match node {
TextNode::BeginEnd { txt, .. } | TextNode::Encrypted { txt, .. } => {
visit_mut(txt, f);
}
_ => {}
},
Control::Prune => {}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn parse(s: &str) -> TextTree {
let policy = crate::crypto::default_policy();
let mut paops = crate::etree::ParseOps::new(policy).unwrap();
crate::etree::parse(std::io::Cursor::new(s), &mut paops).unwrap()
}
fn flat() -> TextTree {
parse("plain\n// <( BEGIN W )>\ninner\n// <( END W )>\nplain2\n")
}
#[test]
fn pre_order_sees_parents_before_children() {
let tree = flat();
let mut order: Vec<&str> = Vec::new();
visit(&tree, &mut |node| {
match node {
TextNode::Plain(_) => order.push("plain"),
TextNode::BeginEnd { .. } => order.push("begin-end"),
_ => order.push("other"),
}
Control::Continue
});
assert_eq!(order, vec!["plain", "begin-end", "plain", "plain"]);
}
#[test]
fn prune_skips_children_but_not_siblings() {
let tree = flat();
let mut seen: Vec<&str> = Vec::new();
visit(&tree, &mut |node| {
match node {
TextNode::Plain(_) => seen.push("plain"),
TextNode::BeginEnd { .. } => {
seen.push("begin-end");
return Control::Prune; }
_ => seen.push("other"),
}
Control::Continue
});
assert_eq!(seen, vec!["plain", "begin-end", "plain"]);
}
#[test]
fn descends_into_encrypted_children() {
let tree = parse(
"// <( ENCRYPTED W pbkdf:$argon2$m=1,p=1,t=1$AAAA )>\n// <( DATA AAAA )>\n// <( END W )>\n",
);
let mut saw_encrypted = false;
let mut saw_data = false;
visit(&tree, &mut |node| {
match node {
TextNode::Encrypted { .. } => saw_encrypted = true,
TextNode::Data(_) => saw_data = true,
_ => {}
}
Control::Continue
});
assert!(
saw_encrypted && saw_data,
"Data child of Encrypted must be visited"
);
}
#[test]
fn visit_mut_rewrites_in_place() {
let mut tree = flat();
visit_mut(&mut tree, &mut |node| {
if let TextNode::Plain(p) = node {
p.push('!');
}
Control::Continue
});
let mut out = Vec::new();
let policy = crate::crypto::default_policy();
let mut paops = crate::etree::ParseOps::new(policy).unwrap();
crate::etree::tree_write(&mut out, &tree, &mut paops).unwrap();
let s = String::from_utf8(out).unwrap();
assert!(s.contains("plain!\n") && s.contains("inner!"), "{s}");
}
#[test]
fn visit_mut_prune_preserves_subtree() {
let mut tree = flat();
visit_mut(&mut tree, &mut |node| {
if let TextNode::BeginEnd { txt, .. } = node {
txt.clear(); return Control::Prune;
}
Control::Continue
});
let mut saw_inner = false;
visit(&tree, &mut |n| {
if let TextNode::Plain(p) = n
&& p.contains("inner")
{
saw_inner = true;
}
Control::Continue
});
assert!(!saw_inner, "cleared children must not reappear");
}
#[test]
fn visit_depth_numbers_nesting() {
let tree = flat();
let mut seen: Vec<(&str, usize)> = Vec::new();
visit_depth(&tree, &mut |node, d| {
match node {
TextNode::Plain(_) => seen.push(("plain", d)),
TextNode::BeginEnd { .. } => seen.push(("begin-end", d)),
_ => seen.push(("other", d)),
}
Control::Continue
});
assert_eq!(
seen,
vec![("plain", 0), ("begin-end", 0), ("plain", 1), ("plain", 0)]
);
}
#[test]
fn visit_depth_prune_hides_child_depths() {
let tree = flat();
let mut depths = Vec::new();
visit_depth(&tree, &mut |node, d| {
depths.push(d);
match node {
TextNode::BeginEnd { .. } => Control::Prune,
_ => Control::Continue,
}
});
assert_eq!(depths, vec![0, 0, 0]);
}
#[test]
fn visit_depth_descends_into_encrypted() {
let tree = parse(
"// <( ENCRYPTED W pbkdf:$argon2$m=1,p=1,t=1$AAAA )>\n// <( DATA AAAA )>\n// <( END W )>\n",
);
let mut saw = Vec::new();
visit_depth(&tree, &mut |node, d| {
if let TextNode::Data(_) = node {
saw.push(d);
}
Control::Continue
});
assert_eq!(saw, vec![1], "Data child must arrive at depth 1");
}
#[test]
fn deeply_nested_trees_descend_fully() {
let input = "// <( BEGIN A )>\n// <( BEGIN B )>\n// <( BEGIN C )>\ndeep\n// <( END C )>\n// <( END B )>\n// <( END A )>\n";
let tree = parse(input);
let mut depth = 0usize;
let mut max_depth = 0usize;
visit(&tree, &mut |node| {
if let TextNode::BeginEnd { .. } = node {
depth += 1;
max_depth = max_depth.max(depth);
}
Control::Continue
});
assert_eq!(max_depth, 3);
}
}