diff --git a/src/model.rs b/src/model.rs index 0a38d44..00e842b 100644 --- a/src/model.rs +++ b/src/model.rs @@ -23,6 +23,7 @@ use core::fmt::Display; pub use node::{DeviceTreeNode, DeviceTreeNodeBuilder}; pub use property::DeviceTreeProperty; +use crate::Node; use crate::fdt::Fdt; use crate::memreserve::MemoryReservation; @@ -89,6 +90,40 @@ impl DeviceTree { } } + /// Finds a node by its path and returns a reference to it. + /// + /// # Performance + /// + /// This method traverses the device tree, but since child lookup is a + /// constant-time operation, performance is linear in the number of path + /// segments. + /// + /// # Examples + /// + /// ``` + /// use dtoolkit::Node; + /// use dtoolkit::model::{DeviceTree, DeviceTreeNode}; + /// + /// let mut tree = DeviceTree::new(); + /// tree.root.add_child(DeviceTreeNode::new("child").unwrap()); + /// let child = tree.find_node("/child").unwrap(); + /// assert_eq!(child.name(), "child"); + /// ``` + #[must_use] + pub fn find_node(&self, path: &str) -> Option<&DeviceTreeNode> { + if !path.starts_with('/') { + return None; + } + let mut current_node = &self.root; + if path == "/" { + return Some(current_node); + } + for component in path.split('/').filter(|s| !s.is_empty()) { + current_node = current_node.child(component)?; + } + Some(current_node) + } + /// Finds a node by its path and returns a mutable reference to it. /// /// # Performance diff --git a/tests/model.rs b/tests/model.rs index c55a07b..91af9fa 100644 --- a/tests/model.rs +++ b/tests/model.rs @@ -114,6 +114,33 @@ fn find_node_mut() { assert!(tree.find_node_mut("/child-a/child-c").is_none()); } +#[test] +fn find_node() { + let mut tree = DeviceTree::new(); + tree.root.add_child( + DeviceTreeNode::builder("foo") + .unwrap() + .child(DeviceTreeNode::builder("bar").unwrap().build()) + .build(), + ); + + // Normal paths + assert_eq!(tree.find_node("/").unwrap().name(), ""); + assert_eq!(tree.find_node("/foo").unwrap().name(), "foo"); + assert_eq!(tree.find_node("/foo/bar").unwrap().name(), "bar"); + + // Weird but valid paths (due to removing empty segments) + assert_eq!(tree.find_node("//").unwrap().name(), ""); + assert_eq!(tree.find_node("/foo/").unwrap().name(), "foo"); + assert_eq!(tree.find_node("/foo//bar").unwrap().name(), "bar"); + + // Invalid paths + assert!(tree.find_node("").is_none()); + assert!(tree.find_node("foo/").is_none()); + assert!(tree.find_node("foo//bar").is_none()); + assert!(tree.find_node("foo").is_none()); +} + #[test] fn device_tree_format() { let mut tree = DeviceTree::new();