4use crate::can_be_deleted::DeleteDecision;
8#[derive(Debug, PartialEq)]
9pub struct EmptydirResult {
10 pub count_deleted: u32,
11 pub count_errors: u32,
14/// Recurse through a given root directory, and delete any "empty" directories.
16/// Returns the number of directories deleted.
18pub fn emptydir(root: &Path) -> EmptydirResult {
19 let directories_to_delete = WalkDir::new(root)
22 .filter_map(|e| e.ok())
23 .filter(|e| e.file_type().is_dir())
26 crate::can_be_deleted::can_be_deleted(e.path()),
27 DeleteDecision::CanDelete
31 let mut count_deleted: u32 = 0;
32 let mut count_errors: u32 = 0;
34 for dir in directories_to_delete {
35 match fs::remove_dir_all(dir.path()) {
37 println!("{}", dir.path().display());
41 let message = format!(
42 "Tried to delete {}, but got error: {}",
46 eprintln!("{}", message.red());
52 // Now work our way upward through the parent directories, and
53 // delete any of those which are empty.
54 let mut current_parent = root.parent();
56 while let Some(parent) = current_parent {
58 crate::can_be_deleted::can_be_deleted(parent),
59 DeleteDecision::CanDelete
64 match fs::remove_dir_all(parent) {
66 println!("{}", parent.display());
70 let message = format!("Tried to delete {}, but got error: {}", parent.display(), e);
71 eprintln!("{}", message.red());
76 current_parent = parent.parent();
88 use std::path::{Path, PathBuf};
92 fn test_dir() -> PathBuf {
93 let tmp_dir = tempfile::tempdir().unwrap();
94 let path = tmp_dir.path();
98 fn create_dir(dir: &PathBuf) {
99 fs::create_dir_all(dir).unwrap();
102 fn create_file(path: &PathBuf) {
103 create_dir(&path.parent().unwrap().to_path_buf());
104 fs::write(&path, "this file is for testing").unwrap();
108 fn it_doesnt_delete_a_non_existent_directory() {
109 let dir = Path::new("/does/not/exist");
120 fn it_deletes_an_empty_dir() {
121 let dir = test_dir();
123 // Create the directory, but don't put anything in it
133 assert_eq!(dir.exists(), false);
137 fn it_ignores_a_dir_with_extra_entries() {
138 let dir = test_dir();
140 // Create the directory, then add a text file
143 create_file(&dir.join("greeting.txt"));
152 assert_eq!(dir.exists(), true);
153 assert_eq!(dir.join("greeting.txt").exists(), true);
157 fn it_deletes_a_dir_with_only_safe_to_delete_entries() {
158 let dir = test_dir();
161 // ├─ .ipynb_checkpoints/
162 // │ └─ analysis-checkpoint.ipynb
175 create_dir(&dir.join(".venv"));
176 create_file(&dir.join(".venv/bin/mypython.py"));
178 create_dir(&dir.join(".ipynb_checkpoints"));
179 create_file(&dir.join(".ipynb_checkpoints/analysis-checkpoint.ipynb"));
181 create_dir(&dir.join("__pycache__"));
182 create_file(&dir.join("__pycache__/myfile.pyc"));
184 create_file(&dir.join(".DS_Store"));
193 assert_eq!(dir.exists(), false);
197 fn it_ignores_a_dir_with_a_mix_of_safe_and_unsafe_entries() {
198 let dir = test_dir();
202 create_file(&dir.join(".DS_Store"));
203 create_file(&dir.join("greeting.txt"));
212 assert!(dir.exists());
213 assert!(dir.join("greeting.txt").exists());
217 fn it_deletes_a_subdir_with_only_safe_to_delete_entries() {
218 let dir = test_dir();
219 let subdir = dir.join("subdir");
225 // │ │ └─ mypython.py
236 create_dir(&subdir.join(".venv"));
237 create_file(&subdir.join(".venv/bin/mypython.py"));
239 create_dir(&subdir.join("__pycache__"));
240 create_file(&subdir.join("__pycache__/myfile.pyc"));
242 create_file(&subdir.join(".DS_Store"));
244 create_file(&dir.join("greeting.txt"));
253 assert_eq!(dir.exists(), true);
254 assert_eq!(subdir.exists(), false);
255 assert!(dir.join("greeting.txt").exists());