mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 11:37:44 +00:00
Merge pull request #1632 from sylvestre/clap-rm
refactor(rm): Move to clap + add a test
This commit is contained in:
commit
5eaab5327c
4 changed files with 135 additions and 72 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -1974,7 +1974,7 @@ dependencies = [
|
|||
name = "uu_rm"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"getopts 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"clap 2.33.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"remove_dir_all 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"uucore 0.0.4",
|
||||
"uucore_procs 0.0.4",
|
||||
|
|
|
@ -15,7 +15,7 @@ edition = "2018"
|
|||
path = "src/rm.rs"
|
||||
|
||||
[dependencies]
|
||||
getopts = "0.2.18"
|
||||
clap = "2.33"
|
||||
walkdir = "2.2.8"
|
||||
remove_dir_all = "0.5.1"
|
||||
uucore = { version=">=0.0.4", package="uucore", path="../../uucore" }
|
||||
|
|
|
@ -7,13 +7,14 @@
|
|||
|
||||
// spell-checker:ignore (ToDO) bitor ulong
|
||||
|
||||
extern crate getopts;
|
||||
extern crate clap;
|
||||
extern crate remove_dir_all;
|
||||
extern crate walkdir;
|
||||
|
||||
#[macro_use]
|
||||
extern crate uucore;
|
||||
|
||||
use clap::{App, Arg};
|
||||
use remove_dir_all::remove_dir_all;
|
||||
use std::collections::VecDeque;
|
||||
use std::fs;
|
||||
|
@ -40,83 +41,139 @@ struct Options {
|
|||
verbose: bool,
|
||||
}
|
||||
|
||||
static NAME: &str = "rm";
|
||||
static ABOUT: &str = "Remove (unlink) the FILE(s)";
|
||||
static VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
static OPT_DIR: &str = "dir";
|
||||
static OPT_INTERACTIVE: &str = "interactive";
|
||||
static OPT_FORCE: &str = "force";
|
||||
static OPT_NO_PRESERVE_ROOT: &str = "no-preserve-root";
|
||||
static OPT_ONE_FILE_SYSTEM: &str = "one-file-system";
|
||||
static OPT_PRESERVE_ROOT: &str = "preserve-root";
|
||||
static OPT_PROMPT: &str = "prompt";
|
||||
static OPT_PROMPT_MORE: &str = "prompt-more";
|
||||
static OPT_RECURSIVE: &str = "recursive";
|
||||
static OPT_VERBOSE: &str = "verbose";
|
||||
|
||||
static ARG_FILES: &str = "files";
|
||||
|
||||
fn get_usage() -> String {
|
||||
format!("{0} [OPTION]... FILE...", executable!())
|
||||
}
|
||||
|
||||
fn get_long_usage() -> String {
|
||||
String::from(
|
||||
"By default, rm does not remove directories. Use the --recursive (-r)
|
||||
option to remove each listed directory, too, along with all of its contents
|
||||
|
||||
To remove a file whose name starts with a '-', for example '-foo',
|
||||
use one of these commands:
|
||||
rm -- -foo
|
||||
|
||||
rm ./-foo
|
||||
|
||||
Note that if you use rm to remove a file, it might be possible to recover
|
||||
some of its contents, given sufficient expertise and/or time. For greater
|
||||
assurance that the contents are truly unrecoverable, consider using shred.",
|
||||
)
|
||||
}
|
||||
|
||||
pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||
let args = args.collect_str();
|
||||
let usage = get_usage();
|
||||
let long_usage = get_long_usage();
|
||||
|
||||
let matches = App::new(executable!())
|
||||
.version(VERSION)
|
||||
.about(ABOUT)
|
||||
.usage(&usage[..])
|
||||
.after_help(&long_usage[..])
|
||||
// TODO: make getopts support -R in addition to -r
|
||||
let mut opts = getopts::Options::new();
|
||||
|
||||
opts.optflag(
|
||||
"f",
|
||||
"force",
|
||||
"ignore nonexistent files and arguments, never prompt",
|
||||
);
|
||||
opts.optflag("i", "", "prompt before every removal");
|
||||
opts.optflag("I", "", "prompt once before removing more than three files, or when removing recursively. Less intrusive than -i, while still giving some protection against most mistakes");
|
||||
opts.optflagopt(
|
||||
"",
|
||||
"interactive",
|
||||
"prompt according to WHEN: never, once (-I), or always (-i). Without WHEN, prompts always",
|
||||
"WHEN",
|
||||
);
|
||||
opts.optflag("", "one-file-system", "when removing a hierarchy recursively, skip any directory that is on a file system different from that of the corresponding command line argument (NOT IMPLEMENTED)");
|
||||
opts.optflag("", "no-preserve-root", "do not treat '/' specially");
|
||||
opts.optflag("", "preserve-root", "do not remove '/' (default)");
|
||||
opts.optflag(
|
||||
"r",
|
||||
"recursive",
|
||||
"remove directories and their contents recursively",
|
||||
);
|
||||
opts.optflag("d", "dir", "remove empty directories");
|
||||
opts.optflag("v", "verbose", "explain what is being done");
|
||||
opts.optflag("h", "help", "display this help and exit");
|
||||
opts.optflag("V", "version", "output version information and exit");
|
||||
.arg(
|
||||
Arg::with_name(OPT_FORCE)
|
||||
.short("f")
|
||||
.long(OPT_FORCE)
|
||||
.help("ignore nonexistent files and arguments, never prompt")
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(OPT_PROMPT)
|
||||
.short("i")
|
||||
.long("prompt before every removal")
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(OPT_PROMPT_MORE)
|
||||
.short("I")
|
||||
.help("prompt once before removing more than three files, or when removing recursively. Less intrusive than -i, while still giving some protection against most mistakes")
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(OPT_INTERACTIVE)
|
||||
.long(OPT_INTERACTIVE)
|
||||
.help("prompt according to WHEN: never, once (-I), or always (-i). Without WHEN, prompts always")
|
||||
.value_name("WHEN")
|
||||
.takes_value(true)
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(OPT_ONE_FILE_SYSTEM)
|
||||
.long(OPT_ONE_FILE_SYSTEM)
|
||||
.help("when removing a hierarchy recursively, skip any directory that is on a file system different from that of the corresponding command line argument (NOT IMPLEMENTED)")
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(OPT_NO_PRESERVE_ROOT)
|
||||
.long(OPT_NO_PRESERVE_ROOT)
|
||||
.help("do not treat '/' specially")
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(OPT_PRESERVE_ROOT)
|
||||
.long(OPT_PRESERVE_ROOT)
|
||||
.help("do not remove '/' (default)")
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(OPT_RECURSIVE).short("r")
|
||||
.long(OPT_RECURSIVE)
|
||||
.help("remove directories and their contents recursively")
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(OPT_DIR)
|
||||
.short("d")
|
||||
.long(OPT_DIR)
|
||||
.help("remove empty directories")
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(OPT_VERBOSE)
|
||||
.short("v")
|
||||
.long(OPT_VERBOSE)
|
||||
.help("explain what is being done")
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(ARG_FILES)
|
||||
.multiple(true)
|
||||
.takes_value(true)
|
||||
.min_values(1)
|
||||
)
|
||||
.get_matches_from(args);
|
||||
|
||||
let matches = match opts.parse(&args[1..]) {
|
||||
Ok(m) => m,
|
||||
Err(f) => crash!(1, "{}", f),
|
||||
};
|
||||
let files: Vec<String> = matches
|
||||
.values_of(ARG_FILES)
|
||||
.map(|v| v.map(ToString::to_string).collect())
|
||||
.unwrap_or_default();
|
||||
|
||||
let force = matches.opt_present("force");
|
||||
let force = matches.is_present(OPT_FORCE);
|
||||
|
||||
if matches.opt_present("help") {
|
||||
println!("{} {}", NAME, VERSION);
|
||||
println!();
|
||||
println!("Usage:");
|
||||
println!(" {0} [OPTION]... [FILE]...", NAME);
|
||||
println!();
|
||||
println!("{}", opts.usage("Remove (unlink) the FILE(s)."));
|
||||
println!("By default, rm does not remove directories. Use the --recursive (-r)");
|
||||
println!("option to remove each listed directory, too, along with all of its contents");
|
||||
println!();
|
||||
println!("To remove a file whose name starts with a '-', for example '-foo',");
|
||||
println!("use one of these commands:");
|
||||
println!("rm -- -foo");
|
||||
println!();
|
||||
println!("rm ./-foo");
|
||||
println!();
|
||||
println!("Note that if you use rm to remove a file, it might be possible to recover");
|
||||
println!("some of its contents, given sufficient expertise and/or time. For greater");
|
||||
println!("assurance that the contents are truly unrecoverable, consider using shred.");
|
||||
} else if matches.opt_present("version") {
|
||||
println!("{} {}", NAME, VERSION);
|
||||
} else if matches.free.is_empty() && !force {
|
||||
if files.is_empty() && !force {
|
||||
// Still check by hand and not use clap
|
||||
// Because "rm -f" is a thing
|
||||
show_error!("missing an argument");
|
||||
show_error!("for help, try '{0} --help'", NAME);
|
||||
show_error!("for help, try '{0} --help'", executable!());
|
||||
return 1;
|
||||
} else {
|
||||
let options = Options {
|
||||
force,
|
||||
interactive: {
|
||||
if matches.opt_present("i") {
|
||||
if matches.is_present(OPT_PROMPT) {
|
||||
InteractiveMode::Always
|
||||
} else if matches.opt_present("I") {
|
||||
} else if matches.is_present(OPT_PROMPT_MORE) {
|
||||
InteractiveMode::Once
|
||||
} else if matches.opt_present("interactive") {
|
||||
match &matches.opt_str("interactive").unwrap()[..] {
|
||||
} else if matches.is_present(OPT_INTERACTIVE) {
|
||||
match &matches.value_of(OPT_INTERACTIVE).unwrap()[..] {
|
||||
"none" => InteractiveMode::None,
|
||||
"once" => InteractiveMode::Once,
|
||||
"always" => InteractiveMode::Always,
|
||||
|
@ -126,15 +183,13 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
InteractiveMode::None
|
||||
}
|
||||
},
|
||||
one_fs: matches.opt_present("one-file-system"),
|
||||
preserve_root: !matches.opt_present("no-preserve-root"),
|
||||
recursive: matches.opt_present("recursive"),
|
||||
dir: matches.opt_present("dir"),
|
||||
verbose: matches.opt_present("verbose"),
|
||||
one_fs: matches.is_present(OPT_ONE_FILE_SYSTEM),
|
||||
preserve_root: !matches.is_present(OPT_NO_PRESERVE_ROOT),
|
||||
recursive: matches.is_present(OPT_RECURSIVE),
|
||||
dir: matches.is_present(OPT_DIR),
|
||||
verbose: matches.is_present(OPT_VERBOSE),
|
||||
};
|
||||
if options.interactive == InteractiveMode::Once
|
||||
&& (options.recursive || matches.free.len() > 3)
|
||||
{
|
||||
if options.interactive == InteractiveMode::Once && (options.recursive || files.len() > 3) {
|
||||
let msg = if options.recursive {
|
||||
"Remove all arguments recursively? "
|
||||
} else {
|
||||
|
@ -145,7 +200,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
}
|
||||
}
|
||||
|
||||
if remove(matches.free, options) {
|
||||
if remove(files, options) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,14 @@ fn test_rm_one_file() {
|
|||
assert!(!at.file_exists(file));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rm_failed() {
|
||||
let (_at, mut ucmd) = at_and_ucmd!();
|
||||
let file = "test_rm_one_file";
|
||||
|
||||
ucmd.arg(file).fails(); // Doesn't exist
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rm_multiple_files() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue