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}