Skip to main content

chezmoi_files/
tree.rs

1//! Tree structure module for hierarchical file path visualization.
2//!
3//! This module provides the core data structures and algorithms for building and
4//! rendering tree structures. Parts of this code are derived from the `eza` crate,
5//! which is licensed under the MIT License.
6//!
7//! # Examples
8//!
9//! ```
10//! use chezmoi_files::{TreeNode, TreeTrunk, TreeDepth, ColorScheme};
11//!
12//! // Create a tree structure
13//! let mut root = TreeNode::new();
14//! root.add_path(vec!["src", "main.rs"]);
15//! root.add_path(vec!["src", "lib.rs"]);
16//! root.add_path(vec!["tests", "test.rs"]);
17//!
18//! // The tree now contains the hierarchical structure
19//! assert_eq!(root.children.len(), 2); // src and tests
20//! ```
21
22use indexmap::IndexMap;
23
24/// A **tree part** is a single character in the tree structure.
25///
26/// It can be either an edge, a line, a corner, or a blank space.
27#[derive(PartialEq, Eq, Debug, Copy, Clone)]
28pub enum TreePart {
29    /// Rightmost column, *not* the last in the directory.
30    Edge,
31
32    /// Not the rightmost column, and the directory has not finished yet.
33    Line,
34
35    /// Rightmost column, and the last in the directory.
36    Corner,
37
38    /// Not the rightmost column, and the directory *has* finished.
39    Blank,
40}
41
42impl TreePart {
43    /// Turn this tree part into box drawing characters.
44    #[must_use]
45    pub const fn ascii_art(self) -> &'static str {
46        match self {
47            Self::Edge => "├──",
48            Self::Line => "│   ",
49            Self::Corner => "└──",
50            Self::Blank => "    ",
51        }
52    }
53}
54
55/// A **tree trunk** builds up arrays of tree parts over multiple depths.
56#[derive(Debug, Default)]
57pub struct TreeTrunk {
58    /// A stack tracks which tree characters should be printed. It's
59    /// necessary to maintain information about the previously-printed
60    /// lines, as the output will change based on any previous entries.
61    stack: Vec<TreePart>,
62
63    /// A tuple for the last 'depth' and 'last' parameters that are passed in.
64    last_params: Option<TreeParams>,
65}
66
67impl TreeTrunk {
68    /// Calculates the tree parts for an entry at the given depth and last-ness.
69    ///
70    /// The depth is used to determine where in the stack the tree part should be
71    /// inserted, and the last-ness is used to determine which type of tree part
72    /// to insert.
73    ///
74    /// This takes a `&mut self` because the results of each file are stored
75    /// and used in future rows.
76    pub fn new_row(&mut self, params: TreeParams) -> &[TreePart] {
77        // If this isn't our first iteration, then update the tree parts thus
78        // far to account for there being another row after it.
79        if let Some(last) = self.last_params {
80            self.stack[last.depth.0] = if last.last {
81                TreePart::Blank
82            } else {
83                TreePart::Line
84            };
85        }
86
87        // Make sure the stack has enough space, then add or modify another
88        // part into it.
89        self.stack.resize(params.depth.0 + 1, TreePart::Edge);
90        self.stack[params.depth.0] = if params.last {
91            TreePart::Corner
92        } else {
93            TreePart::Edge
94        };
95
96        self.last_params = Some(params);
97
98        // Return the tree parts as a slice of the stack.
99        //
100        // Ignore the first element here to prevent a 'zeroth level' from
101        // appearing before the very first directory. This level would
102        // join unrelated directories without connecting to anything:
103        //
104        //     with [0..]        with [1..]
105        //     ==========        ==========
106        //      ├── folder        folder
107        //      │  └── file       └── file
108        //      └── folder        folder
109        //         └── file       └──file
110        //
111        &self.stack[1..]
112    }
113}
114
115/// A structure representing the parameters of a tree.
116///
117/// # Fields
118///
119/// * `depth` - A `TreeDepth` that represents how many directories deep into the tree
120///   structure this is. Directories on top have depth 0.
121/// * `last` - A boolean flag that indicates whether this is the last entry in the directory.
122#[derive(Debug, Copy, Clone)]
123pub struct TreeParams {
124    /// How many directories deep into the tree structure this is.
125    /// Directories on top have depth 0.
126    depth: TreeDepth,
127
128    /// Whether this is the last entry in the directory.
129    last: bool,
130}
131
132impl TreeParams {
133    /// Create a new set of tree parameters.
134    #[must_use]
135    pub const fn new(depth: TreeDepth, last: bool) -> Self {
136        Self { depth, last }
137    }
138}
139
140/// A structure representing the depth of a node in a tree.
141///
142/// This structure is used to represent the depth of a node in a tree.
143/// The depth of a node is the number of edges from the node to the tree's root node.
144/// A root node will have a depth of 0.
145///
146/// # Fields
147///
148/// * `0` - A `usize` that represents the depth of the node in the tree.
149#[derive(Debug, Copy, Clone)]
150pub struct TreeDepth(pub usize);
151
152impl TreeDepth {
153    /// Create a new tree depth at the root level (depth 0).
154    #[must_use]
155    pub const fn root() -> Self {
156        Self(0)
157    }
158
159    /// Increase the depth by one level.
160    #[must_use]
161    pub const fn deeper(self) -> Self {
162        Self(self.0 + 1)
163    }
164}
165
166/// A structure representing a node in a tree.
167///
168/// This structure is used to represent a node in a tree. Each node has a collection
169/// of children, which are also nodes. The `IndexMap` ensures that the children are
170/// ordered in the order they were inserted.
171///
172/// # Fields
173///
174/// * `children` - An `IndexMap` where the keys are `String` and the values are `TreeNode`.
175///   This represents the children of the node.
176/// * `is_leaf` - A boolean flag that indicates whether the node is a leaf node
177///   (i.e., it has no children).
178pub struct TreeNode {
179    /// The children of this node.
180    pub children: IndexMap<String, Self>,
181    /// Whether this node is a leaf (has no children).
182    pub is_leaf: bool,
183}
184
185impl TreeNode {
186    /// Creates a new `TreeNode`.
187    #[must_use]
188    pub fn new() -> Self {
189        Self {
190            children: IndexMap::new(),
191            is_leaf: true,
192        }
193    }
194
195    /// Adds a path to the tree structure.
196    ///
197    /// The path is split into parts, and each part is added as a node in the tree.
198    /// If a part already exists, it is reused.
199    ///
200    /// # Type Parameters
201    ///
202    /// * `I` - An iterator over string-like items that can be referenced as strings.
203    ///
204    /// # Arguments
205    ///
206    /// * `parts` - An iterable of path components to add to the tree.
207    pub fn add_path<I>(&mut self, parts: I)
208    where
209        I: IntoIterator,
210        I::Item: AsRef<str>,
211    {
212        let mut current = self;
213        for part in parts {
214            current.is_leaf = false;
215            let part_str = part.as_ref().to_string();
216            current = current.children.entry(part_str).or_default();
217        }
218    }
219}
220
221impl Default for TreeNode {
222    fn default() -> Self {
223        Self::new()
224    }
225}
226
227#[cfg(test)]
228mod tests {
229    use super::*;
230
231    #[test]
232    fn test_tree_part_ascii_art() {
233        assert_eq!(TreePart::Edge.ascii_art(), "├──");
234        assert_eq!(TreePart::Line.ascii_art(), "│   ");
235        assert_eq!(TreePart::Corner.ascii_art(), "└──");
236        assert_eq!(TreePart::Blank.ascii_art(), "    ");
237    }
238
239    #[test]
240    fn test_tree_depth_root() {
241        let depth = TreeDepth::root();
242        assert_eq!(depth.0, 0);
243    }
244
245    #[test]
246    fn test_tree_depth_deeper() {
247        let depth = TreeDepth::root().deeper();
248        assert_eq!(depth.0, 1);
249
250        let depth2 = depth.deeper();
251        assert_eq!(depth2.0, 2);
252    }
253
254    #[test]
255    fn test_tree_params_new() {
256        let params = TreeParams::new(TreeDepth::root(), true);
257        assert_eq!(params.depth.0, 0);
258        assert!(params.last);
259
260        let params2 = TreeParams::new(TreeDepth::root().deeper(), false);
261        assert_eq!(params2.depth.0, 1);
262        assert!(!params2.last);
263    }
264
265    #[test]
266    fn test_tree_node_new() {
267        let node = TreeNode::new();
268        assert!(node.is_leaf);
269        assert_eq!(node.children.len(), 0);
270    }
271
272    #[test]
273    fn test_tree_node_default() {
274        let node = TreeNode::default();
275        assert!(node.is_leaf);
276        assert_eq!(node.children.len(), 0);
277    }
278
279    #[test]
280    fn test_tree_node_add_path_simple() {
281        let mut root = TreeNode::new();
282        root.add_path(vec!["src", "main.rs"]);
283
284        assert!(!root.is_leaf);
285        assert_eq!(root.children.len(), 1);
286        assert!(root.children.contains_key("src"));
287
288        let src = &root.children["src"];
289        assert!(!src.is_leaf);
290        assert_eq!(src.children.len(), 1);
291        assert!(src.children.contains_key("main.rs"));
292
293        let main_rs = &src.children["main.rs"];
294        assert!(main_rs.is_leaf);
295    }
296
297    #[test]
298    fn test_tree_node_add_path_multiple() {
299        let mut root = TreeNode::new();
300        root.add_path(vec!["src", "main.rs"]);
301        root.add_path(vec!["src", "lib.rs"]);
302        root.add_path(vec!["tests", "test.rs"]);
303
304        assert_eq!(root.children.len(), 2);
305        assert!(root.children.contains_key("src"));
306        assert!(root.children.contains_key("tests"));
307
308        let src = &root.children["src"];
309        assert_eq!(src.children.len(), 2);
310        assert!(src.children.contains_key("main.rs"));
311        assert!(src.children.contains_key("lib.rs"));
312    }
313
314    #[test]
315    fn test_tree_trunk_new_row_first() {
316        let mut trunk = TreeTrunk::default();
317        let params = TreeParams::new(TreeDepth::root().deeper(), false);
318        let parts = trunk.new_row(params);
319
320        assert_eq!(parts.len(), 1);
321        assert_eq!(parts[0], TreePart::Edge);
322    }
323
324    #[test]
325    fn test_tree_trunk_new_row_last() {
326        let mut trunk = TreeTrunk::default();
327        let params = TreeParams::new(TreeDepth::root().deeper(), true);
328        let parts = trunk.new_row(params);
329
330        assert_eq!(parts.len(), 1);
331        assert_eq!(parts[0], TreePart::Corner);
332    }
333
334    #[test]
335    fn test_tree_trunk_new_row_multiple() {
336        let mut trunk = TreeTrunk::default();
337
338        // First row - not last
339        let params1 = TreeParams::new(TreeDepth::root().deeper(), false);
340        trunk.new_row(params1);
341
342        // Second row - last
343        let params2 = TreeParams::new(TreeDepth::root().deeper(), true);
344        let parts = trunk.new_row(params2);
345
346        assert_eq!(parts[0], TreePart::Corner);
347    }
348
349    #[test]
350    fn test_tree_trunk_new_row_deeper() {
351        let mut trunk = TreeTrunk::default();
352
353        // First level
354        let params1 = TreeParams::new(TreeDepth::root().deeper(), false);
355        trunk.new_row(params1);
356
357        // Second level
358        let params2 = TreeParams::new(TreeDepth::root().deeper().deeper(), false);
359        let parts = trunk.new_row(params2);
360
361        assert_eq!(parts.len(), 2);
362        assert_eq!(parts[0], TreePart::Line);
363        assert_eq!(parts[1], TreePart::Edge);
364    }
365
366    #[test]
367    fn test_tree_trunk_new_row_blank() {
368        let mut trunk = TreeTrunk::default();
369
370        // First row - last
371        let params1 = TreeParams::new(TreeDepth::root().deeper(), true);
372        trunk.new_row(params1);
373
374        // Second row at same level
375        let params2 = TreeParams::new(TreeDepth::root().deeper(), false);
376        let parts = trunk.new_row(params2);
377
378        assert_eq!(parts[0], TreePart::Edge);
379    }
380}