diff --git a/fish-rust/src/wutil/mod.rs b/fish-rust/src/wutil/mod.rs index c8ac0938c..705dc109a 100644 --- a/fish-rust/src/wutil/mod.rs +++ b/fish-rust/src/wutil/mod.rs @@ -327,12 +327,12 @@ pub fn wdirname(mut path: WString) -> WString { // This follows OpenGroup dirname recipe. // 1: Double-slash stays. - if path == "//"L { + if path == "//" { return path; } // 2: All slashes => return slash. - if !path.is_empty() && path.chars().find(|c| *c == '/').is_none() { + if !path.is_empty() && path.chars().all(|c| c == '/') { return "/"L.to_owned(); } @@ -342,7 +342,7 @@ pub fn wdirname(mut path: WString) -> WString { } // 4: No slashes left => return period. - let Some(last_slash) = path.chars().rev().position(|c| c == '/') else { + let Some(last_slash) = path.chars().rposition(|c| c == '/') else { return "."L.to_owned() }; @@ -373,19 +373,18 @@ pub fn wbasename(mut path: WString) -> WString { // 2: Skip as permitted. // 3: All slashes => return slash. - if !path.is_empty() && path.chars().find(|c| *c == '/').is_none() { + if !path.is_empty() && path.chars().all(|c| c == '/') { return "/"L.to_owned(); } // 4: Remove trailing slashes. - // while (!path.is_empty() && path.back() == '/') path.pop_back(); while path.as_char_slice().last() == Some(&'/') { path.pop(); } // 5: Remove up to and including last slash. - if let Some(last_slash) = path.chars().rev().position(|c| c == '/') { - path.truncate(last_slash + 1); + if let Some(last_slash) = path.chars().rposition(|c| c == '/') { + path.replace_range(..last_slash + 1, ""L); }; path } @@ -871,3 +870,53 @@ fn test_wstr_offset_in() { assert_eq!(wstr_offset_in(&base[6..], &base[6..]), 0); assert_eq!(wstr_offset_in(&base[base.len()..], base), base.len()); } + +#[test] +#[widestrs] +fn test_wdirname_wbasename() { + // path, dir, base + struct Test(&'static wstr, &'static wstr, &'static wstr); + const testcases: &[Test] = &[ + Test(""L, "."L, "."L), + Test("foo//"L, "."L, "foo"L), + Test("foo//////"L, "."L, "foo"L), + Test("/////foo"L, "/"L, "foo"L), + Test("//foo/////bar"L, "//foo"L, "bar"L), + Test("foo/////bar"L, "foo"L, "bar"L), + // Examples given in XPG4.2. + Test("/usr/lib"L, "/usr"L, "lib"L), + Test("usr"L, "."L, "usr"L), + Test("/"L, "/"L, "/"L), + Test("."L, "."L, "."L), + Test(".."L, "."L, ".."L), + ]; + + for tc in testcases { + let Test(path, tc_dir, tc_base) = *tc; + let dir = wdirname(path.to_owned()); + assert_eq!( + dir, tc_dir, + "\npath: {:?}, dir: {:?}, tc.dir: {:?}", + path, dir, tc_dir + ); + + let base = wbasename(path.to_owned()); + assert_eq!( + base, tc_base, + "\npath: {:?}, base: {:?}, tc.base: {:?}", + path, base, tc_base + ); + } + + // Ensure strings which greatly exceed PATH_MAX still work (#7837). + const PATH_MAX: usize = libc::PATH_MAX as usize; + let mut longpath = WString::new(); + longpath.reserve(PATH_MAX * 2 + 10); + while longpath.char_count() <= PATH_MAX * 2 { + longpath.push_str("/overlong"); + } + let last_slash = longpath.chars().rposition(|c| c == '/').unwrap(); + let longpath_dir = &longpath[..last_slash]; + assert_eq!(wdirname(longpath.clone()), longpath_dir); + assert_eq!(wbasename(longpath), "overlong"L); +}