1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
use std::fs;
use std::io;
use std::path::Path;

/// Remove empty directories under a directory.
/// ```rust,ignore
/// extern crate remove_empty_subdirs;
///
/// use std::path::Path;
///
/// use remove_empty_subdirs::remove_empty_subdirs;
///
/// fn main() {
///     let path = Path::new("test_dir");
///     remove_empty_subdirs(path).unwrap();
/// }
/// ```
pub fn remove_empty_subdirs(dir: &Path) -> io::Result<()> {
    _remove_empty_subdirs(dir, dir.clone())
}

/// Remove empty directories under current directory.
fn _remove_empty_subdirs(dir: &Path, top_dir: &Path) -> io::Result<()> {
    let entries = match fs::read_dir(dir) {
        Ok(dirs) => dirs,
        Err(err) => {
            println!(
                "Failed to read directory `{}` due to `{}`.",
                dir.display(),
                err.to_string()
            );
            return Ok(());
        }
    };
    for entry in entries {
        let path = entry.unwrap().path();
        if path.is_dir() {
            // Ignore hidden directories which start with ".", e.g. ".git".
            let is_hidden = path.file_name().unwrap().to_str().unwrap().starts_with('.');
            if !is_hidden {
                let can_stop = _try_to_remove_empty_dir(&path, &top_dir);
                if !can_stop {
                    // Continue to remove sub-directories.
                    _remove_empty_subdirs(&path, &top_dir)?;
                }
            }
        }
    }
    Ok(())
}

/// Try to recursively remove empty directories upwards.
fn _try_to_remove_empty_dir(dir: &Path, top_dir: &Path) -> bool {
    if dir == top_dir {
        // No need to remove top directory.
        return true;
    }
    // Try to remove empty directory.
    match fs::remove_dir(&dir) {
        Ok(_) => {
            println!("Empty directory `{}` is removed.", dir.display());
            // Then try to remove parent directory.
            let parent_dir = dir.parent().unwrap();
            _try_to_remove_empty_dir(&parent_dir, &top_dir);
            true
        }
        Err(ref err) if err.kind() == io::ErrorKind::PermissionDenied => {
            // Permission denied. Then every sub-direcotry can't not be removed too.
            println!(
                "Failed to remove directory `{}` due to Permission denied.",
                dir.display()
            );
            true
        }
        Err(ref err) if err.kind() == io::ErrorKind::NotFound => {
            // Already removed?
            println!(
                "Failed to remove directory `{}` due to Not found.",
                dir.display()
            );
            true
        }
        Err(_err) => {
            // Not empty directory or other os error while removing directory.
            false
        }
    }
}