realpath: implement -E

This commit is contained in:
Sylvestre Ledru 2025-09-28 10:04:38 +02:00
parent 32eef0687d
commit cbccc9a456
4 changed files with 82 additions and 3 deletions

View file

@ -7,6 +7,7 @@ realpath-help-strip = Only strip '.' and '..' components, but don't resolve symb
realpath-help-zero = Separate output filenames with \0 rather than newline
realpath-help-logical = resolve '..' components before symlinks
realpath-help-physical = resolve symlinks as encountered (default)
realpath-help-canonicalize = all but the last component must exist (default)
realpath-help-canonicalize-existing = canonicalize by following every symlink in every component of the given name recursively, all components must exist
realpath-help-canonicalize-missing = canonicalize by following every symlink in every component of the given name recursively, without requirements on components existence
realpath-help-relative-to = print the resolved path relative to DIR

View file

@ -7,6 +7,7 @@ realpath-help-strip = Supprimer uniquement les composants '.' et '..', mais ne p
realpath-help-zero = Séparer les noms de fichiers de sortie avec \0 plutôt qu'avec un saut de ligne
realpath-help-logical = résoudre les composants '..' avant les liens symboliques
realpath-help-physical = résoudre les liens symboliques rencontrés (par défaut)
realpath-help-canonicalize = tous les composants sauf le dernier doivent exister (par défaut)
realpath-help-canonicalize-existing = canonicaliser en suivant récursivement chaque lien symbolique dans chaque composant du nom donné, tous les composants doivent exister
realpath-help-canonicalize-missing = canonicaliser en suivant récursivement chaque lien symbolique dans chaque composant du nom donné, sans exigences sur l'existence des composants
realpath-help-relative-to = afficher le chemin résolu relativement à RÉP

View file

@ -31,6 +31,7 @@ const OPT_ZERO: &str = "zero";
const OPT_PHYSICAL: &str = "physical";
const OPT_LOGICAL: &str = "logical";
const OPT_CANONICALIZE_MISSING: &str = "canonicalize-missing";
const OPT_CANONICALIZE: &str = "canonicalize";
const OPT_CANONICALIZE_EXISTING: &str = "canonicalize-existing";
const OPT_RELATIVE_TO: &str = "relative-to";
const OPT_RELATIVE_BASE: &str = "relative-base";
@ -86,11 +87,15 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let line_ending = LineEnding::from_zero_flag(matches.get_flag(OPT_ZERO));
let quiet = matches.get_flag(OPT_QUIET);
let logical = matches.get_flag(OPT_LOGICAL);
let can_mode = if matches.get_flag(OPT_CANONICALIZE_EXISTING) {
MissingHandling::Existing
} else if matches.get_flag(OPT_CANONICALIZE_MISSING) {
let can_mode = if matches.get_flag(OPT_CANONICALIZE_MISSING) {
MissingHandling::Missing
} else if matches.get_flag(OPT_CANONICALIZE_EXISTING) {
// -e: all components must exist
// Despite the name, MissingHandling::Existing requires all components to exist
MissingHandling::Existing
} else {
// Default behavior (same as -E): all but last component must exist
// MissingHandling::Normal allows the final component to not exist
MissingHandling::Normal
};
let resolve_mode = if strip {
@ -164,10 +169,19 @@ pub fn uu_app() -> Command {
.help(translate!("realpath-help-physical"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(OPT_CANONICALIZE)
.short('E')
.long(OPT_CANONICALIZE)
.overrides_with_all([OPT_CANONICALIZE_EXISTING, OPT_CANONICALIZE_MISSING])
.help(translate!("realpath-help-canonicalize"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(OPT_CANONICALIZE_EXISTING)
.short('e')
.long(OPT_CANONICALIZE_EXISTING)
.overrides_with_all([OPT_CANONICALIZE, OPT_CANONICALIZE_MISSING])
.help(translate!("realpath-help-canonicalize-existing"))
.action(ArgAction::SetTrue),
)
@ -175,6 +189,7 @@ pub fn uu_app() -> Command {
Arg::new(OPT_CANONICALIZE_MISSING)
.short('m')
.long(OPT_CANONICALIZE_MISSING)
.overrides_with_all([OPT_CANONICALIZE, OPT_CANONICALIZE_EXISTING])
.help(translate!("realpath-help-canonicalize-missing"))
.action(ArgAction::SetTrue),
)

View file

@ -505,3 +505,65 @@ fn test_realpath_empty_string() {
.fails()
.code_is(1);
}
#[test]
fn test_realpath_canonicalize_options() {
// Test that default, -E, and --canonicalize all allow nonexistent final component
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.mkdir("existing_dir");
let test_cases = [
vec![], // default behavior
vec!["-E"], // explicit -E flag
vec!["--canonicalize"], // --canonicalize long form
];
#[cfg(windows)]
let expected_path = "existing_dir\\nonexistent";
#[cfg(not(windows))]
let expected_path = "existing_dir/nonexistent";
for args in test_cases {
let mut ucmd = scene.ucmd();
for arg in args {
ucmd.arg(arg);
}
ucmd.arg("existing_dir/nonexistent")
.succeeds()
.stdout_contains(expected_path);
}
}
#[test]
fn test_realpath_canonicalize_vs_existing() {
// Test difference between -E and -e, and option overrides
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.mkdir("existing_dir");
let test_cases = [
(vec!["-E"], true), // -E should succeed with nonexistent final component
(vec!["-e"], false), // -e should fail with nonexistent final component
(vec!["-e", "-E"], true), // -E should override -e
];
#[cfg(windows)]
let expected_path = "existing_dir\\nonexistent";
#[cfg(not(windows))]
let expected_path = "existing_dir/nonexistent";
for (args, should_succeed) in test_cases {
let mut ucmd = scene.ucmd();
for arg in args {
ucmd.arg(arg);
}
ucmd.arg("existing_dir/nonexistent");
if should_succeed {
ucmd.succeeds().stdout_contains(expected_path);
} else {
ucmd.fails();
}
}
}