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}