rustutils_rmdir/
lib.rs

1use clap::Parser;
2use rustutils_runnable::Runnable;
3use std::error::Error;
4use std::fs::remove_dir;
5use std::io;
6use std::path::{Path, PathBuf};
7
8/// Remove directories, if they are empty.
9#[derive(Parser, Clone, Debug)]
10#[clap(author, version, about)]
11pub struct Rmdir {
12    /// Remove directory and its ancestors.
13    #[clap(long, short)]
14    pub parents: bool,
15    /// Ignore failures that are solely because a directory is non-empty.
16    #[clap(long, short)]
17    pub ignore_fail_on_non_empty: bool,
18    /// Output a diagnostic every time a directory is removed.
19    #[clap(long, short)]
20    pub verbose: bool,
21    /// List of directories to remove.
22    #[clap(required = true)]
23    pub directories: Vec<PathBuf>,
24}
25
26impl Rmdir {
27    /// Run command
28    pub fn run(&self) -> Result<(), io::Error> {
29        for directory in &self.directories {
30            if self.parents {
31                self.remove_parents(directory)?
32            } else {
33                self.remove_directory(directory)?
34            }
35        }
36        Ok(())
37    }
38
39    /// Remove all parents and the directory
40    pub fn remove_parents(&self, path: &Path) -> Result<(), io::Error> {
41        for ancestor in path.ancestors() {
42            if ancestor.parent().is_some() {
43                self.remove_directory(ancestor)?;
44            }
45        }
46        Ok(())
47    }
48
49    /// Remove directory
50    pub fn remove_directory(&self, dir: &Path) -> Result<(), io::Error> {
51        if self.verbose {
52            eprintln!("rmdir: removing directory, {dir:?}");
53        }
54
55        match remove_dir(dir) {
56            Ok(()) => Ok(()),
57            // Match on io::ErrorKind::DirectoryNotEmpty once library features 'io_error_more'
58            // stabilises. This is a hack for now, it is not pretty but it works.
59            Err(error)
60                if self.ignore_fail_on_non_empty
61                    && error.kind().to_string() == "directory not empty" =>
62            {
63                if self.verbose {
64                    eprintln!("rmdir: ignoring error, '{error}'");
65                }
66                Ok(())
67            }
68            Err(error) => Err(error),
69        }
70    }
71}
72
73impl Runnable for Rmdir {
74    fn run(&self) -> Result<(), Box<dyn Error>> {
75        self.run().map_err(|e| Box::new(e) as Box<dyn Error>)
76    }
77}