Add rm
applet
This commit is contained in:
parent
f7b7fcca16
commit
8a14cfb590
60
Cargo.lock
generated
60
Cargo.lock
generated
@ -27,9 +27,9 @@ checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.0.29"
|
||||
version = "4.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4d63b9e9c07271b9957ad22c173bae2a4d9a81127680962039296abcd2f8251d"
|
||||
checksum = "4ec7a4128863c188deefe750ac1d1dfe66c236909f845af04beed823638dc1b2"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"clap_lex",
|
||||
@ -40,9 +40,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_complete"
|
||||
version = "4.0.6"
|
||||
version = "4.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7b3c9eae0de7bf8e3f904a5e40612b21fb2e2e566456d177809a48b892d24da"
|
||||
checksum = "ce8955d4e8cd4f28f9a01c93a050194c4d131e73ca02f6636bcddbed867014d7"
|
||||
dependencies = [
|
||||
"clap",
|
||||
]
|
||||
@ -59,18 +59,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.3.0"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8"
|
||||
checksum = "783fe232adfca04f90f56201b26d79682d4cd2625e0bc7290b95123afe558ade"
|
||||
dependencies = [
|
||||
"os_str_bytes",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_mangen"
|
||||
version = "0.2.5"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e503c3058af0a0854668ea01db55c622482a080092fede9dd2e00a00a9436504"
|
||||
checksum = "eb258c6232b4d728d13d6072656627924c16707aae6267cd5a1ea05abff9a25c"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"roff",
|
||||
@ -134,9 +134,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "io-lifetimes"
|
||||
version = "1.0.3"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46112a93252b123d31a119a8d1a1ac19deac4fac6e0e8b0df58f0d4e5870e63c"
|
||||
checksum = "e7d6c6f8c91b4b9ed43484ad1a938e393caf35960fce7f82a040497207bd8e9e"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys",
|
||||
@ -144,9 +144,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "is-terminal"
|
||||
version = "0.4.1"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "927609f78c2913a6f6ac3c27a4fe87f43e2a35367c0c4b0f8265e8f49a104330"
|
||||
checksum = "28dfb6c8100ccc63462345b67d1bbc3679177c75ee4bf59bf29c8b1d110b8189"
|
||||
dependencies = [
|
||||
"hermit-abi 0.2.6",
|
||||
"io-lifetimes",
|
||||
@ -196,9 +196,9 @@ checksum = "b833d8d034ea094b1ea68aa6d5c740e0d04bad9d16568d08ba6f76823a114316"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.36.5"
|
||||
version = "0.36.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a3807b5d10909833d3e9acd1eb5fb988f79376ff10fce42937de71a449c4c588"
|
||||
checksum = "4feacf7db682c6c329c4ede12649cd36ecab0f3be5b7d74e6a20304725db4549"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
@ -239,9 +239,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.1.3"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
|
||||
checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
@ -303,42 +303,42 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.42.0"
|
||||
version = "0.42.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e"
|
||||
checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.42.0"
|
||||
version = "0.42.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4"
|
||||
checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.42.0"
|
||||
version = "0.42.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7"
|
||||
checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.42.0"
|
||||
version = "0.42.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246"
|
||||
checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.42.0"
|
||||
version = "0.42.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed"
|
||||
checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.42.0"
|
||||
version = "0.42.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028"
|
||||
checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.42.0"
|
||||
version = "0.42.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5"
|
||||
checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
|
||||
|
@ -46,7 +46,7 @@ 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, rmdir::Rmdir,
|
||||
nologin::Nologin, nproc::Nproc, r#false::False, r#true::True, rev::Rev, rm::Rm, rmdir::Rmdir,
|
||||
sleep::Sleep, sync::Sync as SyncCmd, unlink::Unlink, which::Which, whoami::Whoami, yes::Yes,
|
||||
};
|
||||
|
||||
@ -87,6 +87,7 @@ pub fn get(name: &str) -> Option<Box<dyn Cmd>> {
|
||||
"nologin" => Some(Box::new(Nologin::default())),
|
||||
"nproc" => Some(Box::new(Nproc::default())),
|
||||
"rev" => Some(Box::new(Rev::default())),
|
||||
"rm" => Some(Box::new(Rm::default())),
|
||||
"rmdir" => Some(Box::new(Rmdir::default())),
|
||||
"shitbox" => Some(Box::new(Shitbox::default())),
|
||||
"sleep" => Some(Box::new(Sleep::default())),
|
||||
@ -100,7 +101,7 @@ pub fn get(name: &str) -> Option<Box<dyn Cmd>> {
|
||||
}
|
||||
}
|
||||
|
||||
pub static COMMANDS: [&str; 28] = [
|
||||
pub static COMMANDS: [&str; 29] = [
|
||||
"base32",
|
||||
"base64",
|
||||
"basename",
|
||||
@ -120,6 +121,7 @@ pub static COMMANDS: [&str; 28] = [
|
||||
"nologin",
|
||||
"nproc",
|
||||
"rev",
|
||||
"rm",
|
||||
"rmdir",
|
||||
"sleep",
|
||||
"shitbox",
|
||||
|
@ -1 +1,330 @@
|
||||
use super::Cmd;
|
||||
use clap::{Arg, ArgAction, ArgGroup, ArgMatches, Command, ValueHint};
|
||||
use std::{
|
||||
error::Error,
|
||||
fmt,
|
||||
fs::{self, File, Metadata},
|
||||
io,
|
||||
path::PathBuf,
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Rm;
|
||||
|
||||
impl Cmd for Rm {
|
||||
fn cli(&self) -> clap::Command {
|
||||
Command::new("rm")
|
||||
.about("remove files or directories")
|
||||
.author("Nathan Fisher")
|
||||
.version(env!("CARGO_PKG_VERSION"))
|
||||
.args([
|
||||
Arg::new("fullnag")
|
||||
.short('i')
|
||||
.help("prompt before every removal")
|
||||
.action(ArgAction::SetTrue),
|
||||
Arg::new("softnag")
|
||||
.short('I')
|
||||
.help(
|
||||
"prompt once before removing more than three files, or \
|
||||
when removing recursively;\nless intrusive than -i, while \
|
||||
still giving protection against most mistakes",
|
||||
)
|
||||
.next_line_help(true)
|
||||
.action(ArgAction::SetTrue),
|
||||
Arg::new("interactive")
|
||||
.long("interactive")
|
||||
.help("when to prompt")
|
||||
.value_parser(["never", "once", "always"])
|
||||
.value_name("WHEN")
|
||||
.num_args(0..=1)
|
||||
.require_equals(true)
|
||||
.default_missing_value("always"),
|
||||
Arg::new("force")
|
||||
.short('f')
|
||||
.long("force")
|
||||
.help("ignore nonexistent files and arguments, never prompt")
|
||||
.action(ArgAction::SetTrue),
|
||||
Arg::new("recursive")
|
||||
.short('r')
|
||||
.long("recursive")
|
||||
.visible_short_alias('R')
|
||||
.help("remove directories and their contents recursively")
|
||||
.action(ArgAction::SetTrue),
|
||||
Arg::new("verbose")
|
||||
.short('v')
|
||||
.long("verbose")
|
||||
.help("explain what is being done")
|
||||
.action(ArgAction::SetTrue),
|
||||
Arg::new("file")
|
||||
.value_name("FILE")
|
||||
.value_hint(ValueHint::AnyPath)
|
||||
.num_args(1..)
|
||||
.required(true),
|
||||
])
|
||||
.group(
|
||||
ArgGroup::new("nag")
|
||||
.args(["fullnag", "softnag", "interactive", "force"])
|
||||
.required(false)
|
||||
.multiple(false),
|
||||
)
|
||||
}
|
||||
|
||||
fn run(&self, matches: Option<&clap::ArgMatches>) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let Some(matches) = matches else {
|
||||
return Err(Box::new(io::Error::new(io::ErrorKind::Other, "no input")));
|
||||
};
|
||||
let mut actions = AllActions::from(matches);
|
||||
let proceed = match actions.prompt {
|
||||
When::Always => true,
|
||||
When::Once => {
|
||||
if actions.items.len() > 2 {
|
||||
let res = actions.prompt();
|
||||
if res {
|
||||
actions.prompt = When::Never;
|
||||
}
|
||||
res
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
_ => true,
|
||||
};
|
||||
if proceed {
|
||||
for act in actions.items {
|
||||
if let Err(e) = act.apply() {
|
||||
if !act.force {
|
||||
return Err(e.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn path(&self) -> Option<crate::Path> {
|
||||
Some(crate::Path::Bin)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
enum When {
|
||||
Never,
|
||||
Once,
|
||||
Always,
|
||||
}
|
||||
|
||||
impl Default for When {
|
||||
fn default() -> Self {
|
||||
Self::Once
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ParseWhenError;
|
||||
|
||||
impl fmt::Display for ParseWhenError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{self:?}")
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for When {
|
||||
type Err = ParseWhenError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"never" => Ok(Self::Never),
|
||||
"once" => Ok(Self::Once),
|
||||
"always" => Ok(Self::Always),
|
||||
_ => Err(ParseWhenError),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum Filetype {
|
||||
File,
|
||||
Dir,
|
||||
Symlink,
|
||||
}
|
||||
|
||||
impl From<Metadata> for Filetype {
|
||||
fn from(meta: Metadata) -> Self {
|
||||
let ft = meta.file_type();
|
||||
if ft.is_dir() {
|
||||
Self::Dir
|
||||
} else if ft.is_symlink() {
|
||||
Self::Symlink
|
||||
} else {
|
||||
Self::File
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Filetype {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match self {
|
||||
Self::File => "file",
|
||||
Self::Symlink => "symlink",
|
||||
Self::Dir => "directory",
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
struct Action {
|
||||
path: String,
|
||||
prompt: When,
|
||||
force: bool,
|
||||
recursive: bool,
|
||||
verbose: bool,
|
||||
}
|
||||
|
||||
impl Action {
|
||||
fn apply(&self) -> Result<(), Box<dyn Error>> {
|
||||
let ft = if self.prompt == When::Always {
|
||||
match self.prompt() {
|
||||
Ok(Some(ft)) => ft,
|
||||
Ok(None) => return Ok(()),
|
||||
Err(e) => {
|
||||
if !self.force {
|
||||
return Err(e.into());
|
||||
} else {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match File::open(&self.path)
|
||||
.and_then(|fd| fd.metadata())
|
||||
.and_then(|meta| Ok(Filetype::from(meta)))
|
||||
{
|
||||
Ok(ft) => ft,
|
||||
Err(e) => {
|
||||
if !self.force {
|
||||
return Err(e.into());
|
||||
} else {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
match ft {
|
||||
Filetype::File | Filetype::Symlink => {
|
||||
fs::remove_file(&self.path)?;
|
||||
if self.verbose {
|
||||
println!("removed '{}'", &self.path);
|
||||
}
|
||||
}
|
||||
Filetype::Dir => {
|
||||
if self.recursive {
|
||||
match fs::read_dir(&self.path) {
|
||||
Ok(items) => {
|
||||
for entry in items {
|
||||
let act = Action {
|
||||
path: entry?.path().to_string_lossy().to_string(),
|
||||
prompt: self.prompt,
|
||||
force: self.force,
|
||||
recursive: self.recursive,
|
||||
verbose: self.verbose,
|
||||
};
|
||||
act.apply()?;
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
if !self.force {
|
||||
return Err(e.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
fs::remove_dir(&self.path)?;
|
||||
if self.verbose {
|
||||
println!("removed directory '{}'", &self.path);
|
||||
}
|
||||
} else if !self.force {
|
||||
let msg = format!("cannot remove '{}': is a directory", &self.path);
|
||||
return Err(Box::new(io::Error::new(io::ErrorKind::Other, msg)));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn prompt(&self) -> Result<Option<Filetype>, Box<dyn Error>> {
|
||||
let path = PathBuf::from(&self.path);
|
||||
let ft: Filetype = if path.is_dir() {
|
||||
if self.recursive {
|
||||
return Ok(Some(Filetype::Dir));
|
||||
} else {
|
||||
return Ok(None);
|
||||
}
|
||||
} else {
|
||||
File::open(path)
|
||||
.and_then(|fd| fd.metadata())
|
||||
.and_then(|meta| Ok(Filetype::from(meta)))?
|
||||
};
|
||||
print!("rm: remove {ft} '{}'? ", &self.path);
|
||||
let mut reply = String::new();
|
||||
let stdin = io::stdin();
|
||||
let _read = stdin.read_line(&mut reply);
|
||||
match reply.as_str() {
|
||||
"y" | "Y" | "yes" | "Yes" => Ok(Some(ft)),
|
||||
_ => Ok(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct AllActions {
|
||||
items: Vec<Action>,
|
||||
prompt: When,
|
||||
}
|
||||
|
||||
impl From<&ArgMatches> for AllActions {
|
||||
fn from(matches: &ArgMatches) -> Self {
|
||||
let force = matches.get_flag("force");
|
||||
let prompt = if force {
|
||||
When::Never
|
||||
} else if matches.get_flag("fullnag") {
|
||||
When::Always
|
||||
} else if matches.get_flag("softnag") {
|
||||
When::Once
|
||||
} else {
|
||||
match matches.get_one::<String>("interactive") {
|
||||
Some(w) => w.parse().unwrap_or_default(),
|
||||
None => When::default(),
|
||||
}
|
||||
};
|
||||
let mut items: Vec<Action> = vec![];
|
||||
if let Some(files) = matches.get_many::<String>("file") {
|
||||
for f in files {
|
||||
items.push(Action {
|
||||
path: f.to_owned(),
|
||||
prompt,
|
||||
force,
|
||||
recursive: matches.get_flag("recursive"),
|
||||
verbose: matches.get_flag("verbose"),
|
||||
});
|
||||
}
|
||||
}
|
||||
AllActions { items, prompt }
|
||||
}
|
||||
}
|
||||
|
||||
impl AllActions {
|
||||
fn prompt(&self) -> bool {
|
||||
let len = self.items.len();
|
||||
if len > 3 {
|
||||
print!("rm: remove {len} arguments? ");
|
||||
}
|
||||
let mut reply = String::new();
|
||||
let stdin = io::stdin();
|
||||
let _read = stdin.read_line(&mut reply);
|
||||
match reply.as_str() {
|
||||
"y" | "Y" | "yes" | "Yes" => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
use std::{io, error::Error, fs, path::Path};
|
||||
use super::Cmd;
|
||||
use clap::{Arg, ArgAction, Command, ValueHint};
|
||||
use std::{error::Error, fs, io, path::Path};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Rmdir;
|
||||
|
Loading…
Reference in New Issue
Block a user