If the replacement rune was multi-byte, `os.replace_path_separators` would silently fail to replace anything.

Now it properly handles non-ASCII separator. Additionally added a fast path for when all runes in the input path as well as the replacement separator are simple ASCII.

Test added.
This commit is contained in:
Jeroen van Rijn
2026-02-15 12:42:55 +01:00
parent 8f87871f78
commit 95c82e0f76
2 changed files with 39 additions and 11 deletions

View File

@@ -29,22 +29,39 @@ with the `new_sep` rune.
*Allocates Using Provided Allocator*
*/
replace_path_separators :: proc(path: string, new_sep: rune, allocator: runtime.Allocator) -> (new_path: string, err: Error) {
buf := make([]u8, len(path), allocator) or_return
length := len(path)
rep_b, rep_w := utf8.encode_rune(new_sep)
byte_oriented := rep_w == 1
i: int
for r in path {
replacement := r
if r == '/' || r == '\\' {
replacement = new_sep
length += rep_w - 1
}
if r > rune(0x7f) {
byte_oriented = false
}
}
if replacement <= rune(0x7F) {
buf[i] = u8(replacement)
i += 1
} else {
b, w := utf8.encode_rune(r)
copy(buf[i:], b[:w])
i += w
buf := make([]u8, length, allocator) or_return
if byte_oriented {
// Neither replacement rune or any other rune in the path takes up more than 1 byte
str := transmute([]byte)path
#no_bounds_check for b, i in str {
buf[i] = u8(new_sep) if b == '/' || b == '\\' else b
}
} else {
i: int
for r in path {
if r == '/' || r == '\\' {
copy(buf[i:], rep_b[:rep_w])
i += rep_w
} else {
r_b, r_w := utf8.encode_rune(r)
copy(buf[i:], r_b[:r_w])
i += r_w
}
}
}
return string(buf), nil

View File

@@ -99,6 +99,17 @@ test_clean_path :: proc(t: ^testing.T) {
}
}
@(test)
replace_path_separators :: proc(t: ^testing.T) {
P :: "W:/Odin/core/os/path.odin"
p1, _ := os.replace_path_separators(P, '😀', context.temp_allocator)
testing.expect_value(t, p1, "W:😀Odin😀core😀os😀path.odin")
p2, _ := os.replace_path_separators(P, '|', context.temp_allocator)
testing.expect_value(t, p2, "W:|Odin|core|os|path.odin")
}
@(test)
test_is_absolute_path :: proc(t: ^testing.T) {
when ODIN_OS == .Windows {