mirror of
https://github.com/uutils/coreutils.git
synced 2026-06-10 15:48:22 -07:00
rm: hint to use ./-foo when removing a dash-prefixed file
This commit is contained in:
@@ -21,7 +21,7 @@ doctest = false
|
||||
[dependencies]
|
||||
thiserror = { workspace = true }
|
||||
clap = { workspace = true }
|
||||
uucore = { workspace = true, features = ["fs", "parser"] }
|
||||
uucore = { workspace = true, features = ["fs", "parser", "quoting-style"] }
|
||||
fluent = { workspace = true }
|
||||
indicatif = { workspace = true }
|
||||
|
||||
|
||||
@@ -36,6 +36,8 @@ rm-progress-removing = Removing
|
||||
# Error messages
|
||||
rm-error-missing-operand = missing operand
|
||||
Try '{$util_name} --help' for more information.
|
||||
rm-hint-dash-file = Try '{$util_name} ./{$path}' to remove the file {$file}.
|
||||
Try '{$util_name} --help' for more information.
|
||||
rm-error-cannot-remove-no-such-file = cannot remove {$file}: No such file or directory
|
||||
rm-error-cannot-remove-permission-denied = cannot remove {$file}: Permission denied
|
||||
rm-error-cannot-remove-is-directory = cannot remove {$file}: Is a directory
|
||||
|
||||
@@ -36,6 +36,8 @@ rm-progress-removing = Suppression
|
||||
# Messages d'erreur
|
||||
rm-error-missing-operand = opérande manquant
|
||||
Essayez '{$util_name} --help' pour plus d'informations.
|
||||
rm-hint-dash-file = Essayez '{$util_name} ./{$path}' pour supprimer le fichier {$file}.
|
||||
Essayez '{$util_name} --help' pour plus d'informations.
|
||||
rm-error-cannot-remove-no-such-file = impossible de supprimer {$file} : Aucun fichier ou répertoire de ce type
|
||||
rm-error-cannot-remove-permission-denied = impossible de supprimer {$file} : Permission refusée
|
||||
rm-error-cannot-remove-is-directory = impossible de supprimer {$file} : C'est un répertoire
|
||||
|
||||
+37
-2
@@ -10,7 +10,7 @@ use clap::{Arg, ArgAction, Command, parser::ValueSource};
|
||||
use indicatif::{ProgressBar, ProgressStyle};
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use std::fs::{self, Metadata};
|
||||
use std::io::{self, IsTerminal, stdin};
|
||||
use std::io::{self, IsTerminal, Write, stdin};
|
||||
use std::ops::BitOr;
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
@@ -22,6 +22,7 @@ use thiserror::Error;
|
||||
use uucore::display::Quotable;
|
||||
use uucore::error::{FromIo, UError, UResult};
|
||||
use uucore::parser::shortcut_value_parser::ShortcutValueParser;
|
||||
use uucore::quoting_style::{QuotingStyle, locale_aware_escape_name};
|
||||
use uucore::translate;
|
||||
use uucore::{format_usage, os_str_as_bytes, prompt_yes, show_error};
|
||||
|
||||
@@ -203,7 +204,9 @@ static ARG_FILES: &str = "files";
|
||||
#[uucore::main]
|
||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
let args: Vec<OsString> = args.collect();
|
||||
let matches = uucore::clap_localization::handle_clap_result(uu_app(), args.iter())?;
|
||||
let matches = uu_app()
|
||||
.try_get_matches_from(args.iter())
|
||||
.map_err(|e| handle_parse_error(e, &args))?;
|
||||
|
||||
let files: Vec<_> = matches
|
||||
.get_many::<OsString>(ARG_FILES)
|
||||
@@ -293,6 +296,38 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Turn a clap parsing error into a `UError`, reproducing GNU's hint when an
|
||||
/// unknown `-foo` option is actually the name of an existing file: it suggests
|
||||
/// `rm ./-foo` so the name is treated as a path. See `tests/rm/dash-hint.sh`.
|
||||
fn handle_parse_error(e: clap::Error, args: &[OsString]) -> Box<dyn UError> {
|
||||
let dash_file = args.iter().skip(1).find(|a| {
|
||||
os_str_as_bytes(a).is_ok_and(|b| b.len() >= 2 && b[0] == b'-')
|
||||
&& fs::symlink_metadata(a).is_ok()
|
||||
});
|
||||
if let (true, Some(file)) = (e.exit_code() != 0, dash_file) {
|
||||
// The path is shell-escaped (quoted only if needed), the file name is
|
||||
// always quoted, matching GNU's two quoting styles.
|
||||
let path = locale_aware_escape_name(file, QuotingStyle::SHELL_ESCAPE);
|
||||
let quoted = locale_aware_escape_name(file, QuotingStyle::SHELL_ESCAPE_QUOTE);
|
||||
let _ = writeln!(
|
||||
io::stderr(),
|
||||
"{}",
|
||||
translate!(
|
||||
"rm-hint-dash-file",
|
||||
"util_name" => uucore::execution_phrase(),
|
||||
"path" => path.to_string_lossy(),
|
||||
"file" => quoted.to_string_lossy()
|
||||
)
|
||||
);
|
||||
return 1.into();
|
||||
}
|
||||
// Otherwise defer to the standard clap handling (incl. `--help`/`--version`).
|
||||
match uucore::clap_localization::handle_clap_result(uu_app(), args.iter()) {
|
||||
Ok(_) => 1.into(),
|
||||
Err(e) => e,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn uu_app() -> Command {
|
||||
Command::new("rm")
|
||||
.version(uucore::crate_version!())
|
||||
|
||||
@@ -1581,3 +1581,37 @@ fn test_symlink_to_dot_protection() {
|
||||
assert!(at.file_exists("subdir/file"));
|
||||
assert!(at.file_exists("topfile"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dash_hint() {
|
||||
// `rm -foo` where a file named "-foo" exists: GNU suggests `rm ./-foo`.
|
||||
// The invocation prefix (`{$util_name}`) varies in tests, so only the
|
||||
// stable parts are checked.
|
||||
let ts = TestScenario::new(util_name!());
|
||||
let at = &ts.fixtures;
|
||||
|
||||
at.touch("-foo");
|
||||
ts.ucmd()
|
||||
.arg("-foo")
|
||||
.fails_with_code(1)
|
||||
.stderr_contains("./-foo' to remove the file '-foo'.")
|
||||
.stderr_contains("--help' for more information.");
|
||||
assert!(at.file_exists("-foo"));
|
||||
|
||||
// The suggestion is shell-escaped so it can be copy-pasted. Newlines are
|
||||
// not valid in Windows file names, so only exercise this on Unix.
|
||||
#[cfg(unix)]
|
||||
{
|
||||
at.touch("-foo\nbar");
|
||||
ts.ucmd()
|
||||
.arg("-foo\nbar")
|
||||
.fails_with_code(1)
|
||||
.stderr_contains("./'-foo'$'\\n''bar'' to remove the file '-foo'$'\\n''bar'.");
|
||||
}
|
||||
|
||||
// No matching file exists: the hint must not be shown.
|
||||
ts.ucmd()
|
||||
.arg("-bar")
|
||||
.fails_with_code(1)
|
||||
.stderr_does_not_contain("to remove the file");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user