diff --git a/README.md b/README.md index 8a432ad..1e90c72 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,7 @@ code between applets, making for an overall smaller binary. - nologin - nproc - rev +- rmdir - sleep - shitbox - sync diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs index 74b6d94..9270fe3 100644 --- a/src/cmd/mod.rs +++ b/src/cmd/mod.rs @@ -46,8 +46,8 @@ pub use { self::hostname::Hostname, self::shitbox::Shitbox, base32::Base32, base64::Base64, basename::Basename, bootstrap::Bootstrap, clear::Clear, cut::Cut, dirname::Dirname, echo::Echo, factor::Factor, fold::Fold, groups::Groups, head::Head, link::Link, mountpoint::Mountpoint, - nologin::Nologin, nproc::Nproc, r#false::False, r#true::True, rev::Rev, sleep::Sleep, - sync::Sync as SyncCmd, unlink::Unlink, which::Which, whoami::Whoami, yes::Yes, + nologin::Nologin, nproc::Nproc, r#false::False, r#true::True, rev::Rev, rmdir::Rmdir, + sleep::Sleep, sync::Sync as SyncCmd, unlink::Unlink, which::Which, whoami::Whoami, yes::Yes, }; /// Defines a command or applet, it's cli interface, and it's installation directory @@ -87,6 +87,7 @@ pub fn get(name: &str) -> Option> { "nologin" => Some(Box::new(Nologin::default())), "nproc" => Some(Box::new(Nproc::default())), "rev" => Some(Box::new(Rev::default())), + "rmdir" => Some(Box::new(Rmdir::default())), "shitbox" => Some(Box::new(Shitbox::default())), "sleep" => Some(Box::new(Sleep::default())), "sync" => Some(Box::new(SyncCmd::default())), @@ -99,7 +100,7 @@ pub fn get(name: &str) -> Option> { } } -pub static COMMANDS: [&str; 27] = [ +pub static COMMANDS: [&str; 28] = [ "base32", "base64", "basename", @@ -119,6 +120,7 @@ pub static COMMANDS: [&str; 27] = [ "nologin", "nproc", "rev", + "rmdir", "sleep", "shitbox", "sync", diff --git a/src/cmd/rmdir/mod.rs b/src/cmd/rmdir/mod.rs index 8b13789..91288e8 100644 --- a/src/cmd/rmdir/mod.rs +++ b/src/cmd/rmdir/mod.rs @@ -1 +1,71 @@ +use std::{io, error::Error, fs, path::Path}; +use super::Cmd; +use clap::{Arg, ArgAction, Command, ValueHint}; +#[derive(Debug, Default)] +pub struct Rmdir; + +impl Cmd for Rmdir { + fn cli(&self) -> clap::Command { + Command::new("rmdir") + .about("remove directories") + .author("Nathan Fisher") + .version(env!("CARGO_PKG_VERSION")) + .args([ + Arg::new("parents") + .help("remove DIRECTORY and it's ancestors") + .short('p') + .long("parents") + .action(ArgAction::SetTrue), + Arg::new("dir") + .num_args(1..) + .value_name("DIRECTORY") + .value_hint(ValueHint::DirPath) + .required(true), + Arg::new("verbose") + .help("output a diagnostic for every directory processed") + .short('v') + .long("verbose") + .action(ArgAction::SetTrue), + ]) + } + + fn run(&self, matches: Option<&clap::ArgMatches>) -> Result<(), Box> { + let Some(matches) = matches else { + return Err(Box::new(io::Error::new(io::ErrorKind::Other, "no input"))); + }; + if let Some(directories) = matches.get_many::("dir") { + for dir in directories { + if matches.get_flag("parents") { + let path = Path::new(dir); + rmdir_recursive(path, matches.get_flag("verbose"))?; + } else { + fs::remove_dir(dir)?; + if matches.get_flag("verbose") { + println!("rmdir: removing directory, '{dir}'"); + } + } + } + } + Ok(()) + } + + fn path(&self) -> Option { + Some(crate::Path::Bin) + } +} + +fn rmdir_recursive(dir: &Path, verbose: bool) -> Result<(), Box> { + fs::remove_dir(dir)?; + if verbose { + println!("rmdir: removing directory, '{}'", dir.display()); + } + if let Some(parent) = dir.parent() { + if let Some(name) = parent.to_str() { + if !name.is_empty() { + rmdir_recursive(parent, verbose)?; + } + } + } + Ok(()) +}