Skip to main content

vortex_utils/
tree.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4//! Shared traversal and rendering utilities for named trees.
5
6use std::fmt;
7
8/// Traversal state updated as a tree renderer enters and leaves a node's children.
9///
10/// The context visible while a node is rendered describes its ancestors. The renderer calls
11/// [`Self::push_parent`] before visiting the node's children and [`Self::pop_parent`] afterwards.
12pub trait TreeDisplayContext<N: ?Sized> {
13    /// Record `parent` while its children are visited.
14    fn push_parent(&mut self, parent: &N) {
15        _ = parent;
16    }
17
18    /// Remove `parent` after all of its children have been visited.
19    fn pop_parent(&mut self, parent: &N) {
20        _ = parent;
21    }
22}
23
24impl<N: ?Sized> TreeDisplayContext<N> for () {}
25
26/// Tree traversal context that records only the current depth.
27#[derive(Debug, Default)]
28pub struct DepthContext {
29    depth: usize,
30}
31
32impl DepthContext {
33    /// Return the current node's depth, where the root has depth zero.
34    pub fn depth(&self) -> usize {
35        self.depth
36    }
37}
38
39impl<N: ?Sized> TreeDisplayContext<N> for DepthContext {
40    fn push_parent(&mut self, _parent: &N) {
41        self.depth += 1;
42    }
43
44    fn pop_parent(&mut self, _parent: &N) {
45        debug_assert!(self.depth > 0, "tree depth push/pop mismatch");
46        self.depth -= 1;
47    }
48}
49
50/// Access to a formatter together with the indentation for detail lines.
51pub struct IndentedFormatter<'a, 'b> {
52    inner: &'a mut fmt::Formatter<'b>,
53    indent: &'a str,
54}
55
56impl<'a, 'b> IndentedFormatter<'a, 'b> {
57    fn new(inner: &'a mut fmt::Formatter<'b>, indent: &'a str) -> Self {
58        Self { inner, indent }
59    }
60
61    /// Return the indentation string and underlying formatter together.
62    pub fn parts(&mut self) -> (&str, &mut fmt::Formatter<'b>) {
63        (self.indent, self.inner)
64    }
65
66    /// Return the current indentation string.
67    pub fn indent(&self) -> &str {
68        self.indent
69    }
70
71    /// Return the underlying formatter.
72    pub fn formatter(&mut self) -> &mut fmt::Formatter<'b> {
73        self.inner
74    }
75}
76
77/// Contributes one composable dimension of information to tree nodes.
78pub trait TreeDisplayExtractor<N: ?Sized, C: TreeDisplayContext<N>>: Send + Sync {
79    /// Write space-prefixed annotations on the node's header line.
80    fn write_header(
81        &self,
82        node: &N,
83        context: &C,
84        formatter: &mut fmt::Formatter<'_>,
85    ) -> fmt::Result {
86        _ = (node, context, formatter);
87        Ok(())
88    }
89
90    /// Write detail lines beneath the node's header.
91    fn write_details(
92        &self,
93        node: &N,
94        context: &C,
95        formatter: &mut IndentedFormatter<'_, '_>,
96    ) -> fmt::Result {
97        _ = (node, context, formatter);
98        Ok(())
99    }
100}
101
102/// Adapts a domain-specific node and traversal context to the shared tree renderers.
103pub trait TreeDisplayAdapter {
104    /// Node type traversed by this adapter.
105    type Node: ?Sized;
106
107    /// State made available while each node is rendered.
108    type Context: TreeDisplayContext<Self::Node>;
109
110    /// Write the node's display content.
111    ///
112    /// The indented renderer writes `name:` first, so implementations normally write
113    /// space-prefixed annotations. The branch renderer uses this as the complete node label.
114    fn write_node(
115        &self,
116        node: &Self::Node,
117        context: &Self::Context,
118        formatter: &mut fmt::Formatter<'_>,
119    ) -> fmt::Result;
120
121    /// Write detail lines beneath an indented node header.
122    fn write_details(
123        &self,
124        node: &Self::Node,
125        context: &Self::Context,
126        formatter: &mut IndentedFormatter<'_, '_>,
127    ) -> fmt::Result {
128        _ = (node, context, formatter);
129        Ok(())
130    }
131
132    /// Visit each named child in display order.
133    ///
134    /// The final argument to `visit` must be `true` only for the last child. Renderers call the
135    /// visitor synchronously, so adapters may pass either stored or temporarily owned nodes.
136    fn visit_children(
137        &self,
138        node: &Self::Node,
139        visit: &mut dyn FnMut(&str, &Self::Node, bool) -> fmt::Result,
140    ) -> fmt::Result;
141}
142
143/// Render a named tree using two-space indentation.
144///
145/// Each node is written as `name:` followed by [`TreeDisplayAdapter::write_node`]. Detail lines
146/// and child nodes are indented beneath it.
147pub fn write_indented_tree<A: TreeDisplayAdapter>(
148    adapter: &A,
149    root_name: &str,
150    root: &A::Node,
151    context: &mut A::Context,
152    formatter: &mut fmt::Formatter<'_>,
153) -> fmt::Result {
154    write_indented_node(adapter, root_name, root, context, "", formatter)
155}
156
157fn write_indented_node<A: TreeDisplayAdapter>(
158    adapter: &A,
159    name: &str,
160    node: &A::Node,
161    context: &mut A::Context,
162    indent: &str,
163    formatter: &mut fmt::Formatter<'_>,
164) -> fmt::Result {
165    write!(formatter, "{indent}{name}:")?;
166    adapter.write_node(node, context, formatter)?;
167    writeln!(formatter)?;
168
169    let child_indent = format!("{indent}  ");
170    {
171        let mut indented = IndentedFormatter::new(formatter, &child_indent);
172        adapter.write_details(node, context, &mut indented)?;
173    }
174
175    context.push_parent(node);
176    let result = adapter.visit_children(node, &mut |child_name, child, _is_last| {
177        write_indented_node(
178            adapter,
179            child_name,
180            child,
181            context,
182            &child_indent,
183            formatter,
184        )
185    });
186    context.pop_parent(node);
187    result
188}
189
190/// Render a tree using Unicode branch connectors.
191///
192/// The root contains only [`TreeDisplayAdapter::write_node`]. Descendants are prefixed with their
193/// child name and connectors such as `├──` and `└──`. Detail lines are not rendered in this style.
194pub fn write_branch_tree<A: TreeDisplayAdapter>(
195    adapter: &A,
196    root: &A::Node,
197    context: &mut A::Context,
198    formatter: &mut fmt::Formatter<'_>,
199) -> fmt::Result {
200    write_branch_node(adapter, root, context, "", formatter)
201}
202
203fn write_branch_node<A: TreeDisplayAdapter>(
204    adapter: &A,
205    node: &A::Node,
206    context: &mut A::Context,
207    prefix: &str,
208    formatter: &mut fmt::Formatter<'_>,
209) -> fmt::Result {
210    adapter.write_node(node, context, formatter)?;
211
212    context.push_parent(node);
213    let result = adapter.visit_children(node, &mut |child_name, child, is_last| {
214        writeln!(formatter)?;
215        let connector = if is_last { "└── " } else { "├── " };
216        write!(formatter, "{prefix}{connector}{child_name}: ")?;
217        let child_prefix = format!("{prefix}{}", if is_last { "    " } else { "│   " });
218        write_branch_node(adapter, child, context, &child_prefix, formatter)
219    });
220    context.pop_parent(node);
221    result
222}
223
224#[cfg(test)]
225mod tests {
226    use std::fmt;
227
228    use super::DepthContext;
229    use super::TreeDisplayAdapter;
230    use super::write_branch_tree;
231    use super::write_indented_tree;
232
233    struct TestNode {
234        label: &'static str,
235        children: Vec<(&'static str, TestNode)>,
236    }
237
238    struct TestAdapter;
239
240    impl TreeDisplayAdapter for TestAdapter {
241        type Context = DepthContext;
242        type Node = TestNode;
243
244        fn write_node(
245            &self,
246            node: &Self::Node,
247            context: &Self::Context,
248            formatter: &mut fmt::Formatter<'_>,
249        ) -> fmt::Result {
250            write!(formatter, "{}@{}", node.label, context.depth())
251        }
252
253        fn visit_children(
254            &self,
255            node: &Self::Node,
256            visit: &mut dyn FnMut(&str, &Self::Node, bool) -> fmt::Result,
257        ) -> fmt::Result {
258            for (index, (name, child)) in node.children.iter().enumerate() {
259                visit(name, child, index + 1 == node.children.len())?;
260            }
261            Ok(())
262        }
263    }
264
265    struct IndentedDisplay<'a>(&'a TestNode);
266
267    impl fmt::Display for IndentedDisplay<'_> {
268        fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
269            write_indented_tree(
270                &TestAdapter,
271                "root",
272                self.0,
273                &mut DepthContext::default(),
274                formatter,
275            )
276        }
277    }
278
279    struct BranchDisplay<'a>(&'a TestNode);
280
281    impl fmt::Display for BranchDisplay<'_> {
282        fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
283            write_branch_tree(
284                &TestAdapter,
285                self.0,
286                &mut DepthContext::default(),
287                formatter,
288            )
289        }
290    }
291
292    fn tree() -> TestNode {
293        TestNode {
294            label: "parent",
295            children: vec![
296                (
297                    "left",
298                    TestNode {
299                        label: "branch",
300                        children: vec![(
301                            "leaf",
302                            TestNode {
303                                label: "first",
304                                children: Vec::new(),
305                            },
306                        )],
307                    },
308                ),
309                (
310                    "right",
311                    TestNode {
312                        label: "second",
313                        children: Vec::new(),
314                    },
315                ),
316            ],
317        }
318    }
319
320    #[test]
321    fn renders_indented_tree() {
322        assert_eq!(
323            IndentedDisplay(&tree()).to_string(),
324            "root:parent@0\n  left:branch@1\n    leaf:first@2\n  right:second@1\n"
325        );
326    }
327
328    #[test]
329    fn renders_branch_tree() {
330        assert_eq!(
331            BranchDisplay(&tree()).to_string(),
332            "parent@0\n├── left: branch@1\n│   └── leaf: first@2\n└── right: second@1"
333        );
334    }
335}