file_io/print.rs
1use crate::list::list_folder_contents;
2use crate::path::get_last_path_component;
3use std::path::Path;
4
5/// Helper function to recursively print the folder tree.
6///
7/// # Arguments
8///
9/// * `path` - The current path to print.
10/// * `prefix` - The prefix string to use for the current level of indentation.
11/// * `is_last` - A boolean indicating if this is the last entry at the current level.
12/// * `output` - The output stream to write the tree structure to.
13fn helper<W: std::io::Write>(path: &Path, prefix: String, is_last: bool, output: &mut W) {
14 // Get the name of the file or folder (i.e. the last component of the path).
15 let name = get_last_path_component(path);
16
17 // Print the current file or folder with the appropriate prefix.
18 let connector = if is_last { "└── " } else { "├── " };
19 writeln!(output, "{prefix}{connector}{name}").unwrap();
20
21 // Special handling for folders (we need to recurse into them and update the prefix).
22 if path.is_dir() {
23 // Create a new prefix for the children. If this is the last entry, we use spaces to avoid
24 // drawing the vertical line.
25 let new_prefix = format!("{}{}", prefix, if is_last { " " } else { "│ " });
26
27 // Read the directory entries into a vector and sort them.
28 let entries = list_folder_contents(path);
29
30 // Call the helper function recursively for each entry.
31 for (i, entry) in entries.iter().enumerate() {
32 let is_last = i == entries.len() - 1;
33 helper(entry, new_prefix.clone(), is_last, output);
34 }
35 }
36}
37
38/// Write the folder tree structure starting from the specified path.
39///
40/// # Arguments
41///
42/// * `path` - The path to the folder to print (can be a `&str`, [`String`], [`Path`], or
43/// [`std::path::PathBuf`]).
44/// * `output` - The output stream to write the tree structure to.
45fn write_folder_tree<P: AsRef<Path>, W: std::io::Write>(path: P, output: &mut W) {
46 // Convert the input path to a Path reference.
47 let path = path.as_ref();
48
49 // Print the full top-level path once.
50 writeln!(output, "{}", path.display()).unwrap();
51
52 // List and sort children.
53 let entries = list_folder_contents(path);
54
55 // Recurse only into children.
56 // --> The first entry is the top-level path, so we don't need to print it again.
57 for (i, entry) in entries.iter().enumerate() {
58 let is_last = i == entries.len() - 1;
59 helper(entry, "".to_string(), is_last, output);
60 }
61}
62
63/// Print the folder tree structure starting from the specified path.
64///
65/// # Arguments
66///
67/// * `path` - The path to the folder to print (can be a `&str`, [`String`], [`Path`], or
68/// [`std::path::PathBuf`]).
69///
70/// # Examples
71///
72/// ## Using a string literal
73///
74/// ```
75/// use file_io::print_folder_tree;
76///
77/// print_folder_tree("src");
78/// ```
79///
80/// ## Using a `Path` reference
81///
82/// ```
83/// use file_io::print_folder_tree;
84/// use std::path::Path;
85///
86/// print_folder_tree(Path::new("src"));
87/// ```
88pub fn print_folder_tree<P: AsRef<Path>>(path: P) {
89 write_folder_tree(path, &mut std::io::stdout());
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95 use crate::save_string_to_file;
96 use crate::test_utils::get_temp_dir_path;
97 use tempfile::tempdir;
98
99 #[test]
100 fn test_write_folder_tree() {
101 // Create a temporary directory to work in.
102 let temp_dir = tempdir().unwrap();
103
104 // Get the path to the temporary directory.
105 let temp_dir_path = get_temp_dir_path(&temp_dir);
106
107 // Create some test files and folders.
108 save_string_to_file("Content 1", temp_dir_path.join("file1.txt"));
109 save_string_to_file("Content 2", temp_dir_path.join("file2.txt"));
110 save_string_to_file("Content 3", temp_dir_path.join("subfolder/file3.txt"));
111
112 // Vector of bytes to capture the output (would be redirected to stdout using
113 // `print_folder_tree`).
114 let mut stdout: Vec<u8> = Vec::new();
115
116 // Call the function to print the folder tree.
117 write_folder_tree(&temp_dir_path, &mut stdout);
118
119 // Check the output.
120 let output = String::from_utf8(stdout).unwrap();
121 assert_eq!(
122 output,
123 format!(
124 "{}\n├── file1.txt\n├── file2.txt\n└── subfolder\n └── file3.txt\n",
125 temp_dir_path.display()
126 )
127 );
128 }
129}