Skip to content

Commit cbccc9a

Browse files
committed
realpath: implement -E
1 parent 32eef06 commit cbccc9a

File tree

4 files changed

+82
-3
lines changed

4 files changed

+82
-3
lines changed

src/uu/realpath/locales/en-US.ftl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ realpath-help-strip = Only strip '.' and '..' components, but don't resolve symb
77
realpath-help-zero = Separate output filenames with \0 rather than newline
88
realpath-help-logical = resolve '..' components before symlinks
99
realpath-help-physical = resolve symlinks as encountered (default)
10+
realpath-help-canonicalize = all but the last component must exist (default)
1011
realpath-help-canonicalize-existing = canonicalize by following every symlink in every component of the given name recursively, all components must exist
1112
realpath-help-canonicalize-missing = canonicalize by following every symlink in every component of the given name recursively, without requirements on components existence
1213
realpath-help-relative-to = print the resolved path relative to DIR

src/uu/realpath/locales/fr-FR.ftl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ realpath-help-strip = Supprimer uniquement les composants '.' et '..', mais ne p
77
realpath-help-zero = Séparer les noms de fichiers de sortie avec \0 plutôt qu'avec un saut de ligne
88
realpath-help-logical = résoudre les composants '..' avant les liens symboliques
99
realpath-help-physical = résoudre les liens symboliques rencontrés (par défaut)
10+
realpath-help-canonicalize = tous les composants sauf le dernier doivent exister (par défaut)
1011
realpath-help-canonicalize-existing = canonicaliser en suivant récursivement chaque lien symbolique dans chaque composant du nom donné, tous les composants doivent exister
1112
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
1213
realpath-help-relative-to = afficher le chemin résolu relativement à RÉP

src/uu/realpath/src/realpath.rs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ const OPT_ZERO: &str = "zero";
3131
const OPT_PHYSICAL: &str = "physical";
3232
const OPT_LOGICAL: &str = "logical";
3333
const OPT_CANONICALIZE_MISSING: &str = "canonicalize-missing";
34+
const OPT_CANONICALIZE: &str = "canonicalize";
3435
const OPT_CANONICALIZE_EXISTING: &str = "canonicalize-existing";
3536
const OPT_RELATIVE_TO: &str = "relative-to";
3637
const OPT_RELATIVE_BASE: &str = "relative-base";
@@ -86,11 +87,15 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
8687
let line_ending = LineEnding::from_zero_flag(matches.get_flag(OPT_ZERO));
8788
let quiet = matches.get_flag(OPT_QUIET);
8889
let logical = matches.get_flag(OPT_LOGICAL);
89-
let can_mode = if matches.get_flag(OPT_CANONICALIZE_EXISTING) {
90-
MissingHandling::Existing
91-
} else if matches.get_flag(OPT_CANONICALIZE_MISSING) {
90+
let can_mode = if matches.get_flag(OPT_CANONICALIZE_MISSING) {
9291
MissingHandling::Missing
92+
} else if matches.get_flag(OPT_CANONICALIZE_EXISTING) {
93+
// -e: all components must exist
94+
// Despite the name, MissingHandling::Existing requires all components to exist
95+
MissingHandling::Existing
9396
} else {
97+
// Default behavior (same as -E): all but last component must exist
98+
// MissingHandling::Normal allows the final component to not exist
9499
MissingHandling::Normal
95100
};
96101
let resolve_mode = if strip {
@@ -164,17 +169,27 @@ pub fn uu_app() -> Command {
164169
.help(translate!("realpath-help-physical"))
165170
.action(ArgAction::SetTrue),
166171
)
172+
.arg(
173+
Arg::new(OPT_CANONICALIZE)
174+
.short('E')
175+
.long(OPT_CANONICALIZE)
176+
.overrides_with_all([OPT_CANONICALIZE_EXISTING, OPT_CANONICALIZE_MISSING])
177+
.help(translate!("realpath-help-canonicalize"))
178+
.action(ArgAction::SetTrue),
179+
)
167180
.arg(
168181
Arg::new(OPT_CANONICALIZE_EXISTING)
169182
.short('e')
170183
.long(OPT_CANONICALIZE_EXISTING)
184+
.overrides_with_all([OPT_CANONICALIZE, OPT_CANONICALIZE_MISSING])
171185
.help(translate!("realpath-help-canonicalize-existing"))
172186
.action(ArgAction::SetTrue),
173187
)
174188
.arg(
175189
Arg::new(OPT_CANONICALIZE_MISSING)
176190
.short('m')
177191
.long(OPT_CANONICALIZE_MISSING)
192+
.overrides_with_all([OPT_CANONICALIZE, OPT_CANONICALIZE_EXISTING])
178193
.help(translate!("realpath-help-canonicalize-missing"))
179194
.action(ArgAction::SetTrue),
180195
)

tests/by-util/test_realpath.rs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -505,3 +505,65 @@ fn test_realpath_empty_string() {
505505
.fails()
506506
.code_is(1);
507507
}
508+
509+
#[test]
510+
fn test_realpath_canonicalize_options() {
511+
// Test that default, -E, and --canonicalize all allow nonexistent final component
512+
let scene = TestScenario::new(util_name!());
513+
let at = &scene.fixtures;
514+
at.mkdir("existing_dir");
515+
516+
let test_cases = [
517+
vec![], // default behavior
518+
vec!["-E"], // explicit -E flag
519+
vec!["--canonicalize"], // --canonicalize long form
520+
];
521+
522+
#[cfg(windows)]
523+
let expected_path = "existing_dir\\nonexistent";
524+
#[cfg(not(windows))]
525+
let expected_path = "existing_dir/nonexistent";
526+
527+
for args in test_cases {
528+
let mut ucmd = scene.ucmd();
529+
for arg in args {
530+
ucmd.arg(arg);
531+
}
532+
ucmd.arg("existing_dir/nonexistent")
533+
.succeeds()
534+
.stdout_contains(expected_path);
535+
}
536+
}
537+
538+
#[test]
539+
fn test_realpath_canonicalize_vs_existing() {
540+
// Test difference between -E and -e, and option overrides
541+
let scene = TestScenario::new(util_name!());
542+
let at = &scene.fixtures;
543+
at.mkdir("existing_dir");
544+
545+
let test_cases = [
546+
(vec!["-E"], true), // -E should succeed with nonexistent final component
547+
(vec!["-e"], false), // -e should fail with nonexistent final component
548+
(vec!["-e", "-E"], true), // -E should override -e
549+
];
550+
551+
#[cfg(windows)]
552+
let expected_path = "existing_dir\\nonexistent";
553+
#[cfg(not(windows))]
554+
let expected_path = "existing_dir/nonexistent";
555+
556+
for (args, should_succeed) in test_cases {
557+
let mut ucmd = scene.ucmd();
558+
for arg in args {
559+
ucmd.arg(arg);
560+
}
561+
ucmd.arg("existing_dir/nonexistent");
562+
563+
if should_succeed {
564+
ucmd.succeeds().stdout_contains(expected_path);
565+
} else {
566+
ucmd.fails();
567+
}
568+
}
569+
}

0 commit comments

Comments
 (0)