From ad5b65eb17ba09909b157dd769de1f72cd270f5e Mon Sep 17 00:00:00 2001 From: Brendan Hansen Date: Sat, 17 Jun 2023 17:08:15 -0500 Subject: [PATCH] added: os `path` functions --- CHANGELOG | 4 ++ core/os/path.onyx | 114 ++++++++++++++++++++++++++++++++++---- tests/stdlib/os_path | 20 +++++++ tests/stdlib/os_path.onyx | 35 ++++++++++++ 4 files changed, 163 insertions(+), 10 deletions(-) create mode 100644 tests/stdlib/os_path create mode 100644 tests/stdlib/os_path.onyx diff --git a/CHANGELOG b/CHANGELOG index fdc946fa..a4bde1bf 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -18,6 +18,10 @@ Additions: - Quickly convert a byte array to and from its hex equivalent. - `Allocator.move`. Moves a value into an allocator, returning a pointer to it. - This is a copy operation (and might be renamed later) +- `os.path_clean` +- `os.path_directory` +- `os.path_extension` +- `os.path_split` Removals: - Remove old syntax for quoted blocks, `#quote` and `#()`. diff --git a/core/os/path.onyx b/core/os/path.onyx index 9ecdb3d4..d0d8312b 100644 --- a/core/os/path.onyx +++ b/core/os/path.onyx @@ -4,24 +4,118 @@ use runtime use core.string use core.conv -#if runtime.compiler_os == .Windows { - PATH_SEP :: '\\' -} else { - PATH_SEP :: '/' +#local Path_Allocator :: context.temp_allocator + +PATH_SEP :: '/' +// #if runtime.compiler_os == .Windows { +// PATH_SEP :: '\\' +// } else { +// } + +#doc """ + Removes: + - Stray '.' in the path + - Stray '..' + - Repeated '/' + - Trailing '/' + + Modifies the string in place, as the length will never be longer. +""" +path_clean :: (path: str, allocator := Path_Allocator) -> str { + if path == "" do return string.alloc_copy(".", Path_Allocator); + + rooted := path[0] == PATH_SEP; + n := path.length; + + out := make(dyn_str, path.length, allocator=context.temp_allocator); + r, dotdot := 0, 0; + + if rooted { + string.append(&out, "/"); + r, dotdot = 1, 1; + } + + while r < n { + if path[r] == '/' do r += 1; + elseif path[r] == '.' && (r+1 == n || path[r + 1] == '/') do r += 1; + elseif path[r] == '.' && path[r + 1] == '.' && (r+2 == n || path[r+2] == '/') { + r += 2; + if out.length > dotdot { + out.length -= 1; + while out.length > dotdot && out[out.length] != '/' { + out.length -= 1; + } + } + elseif !rooted { + if out.length > 0 { + string.append(&out, "/"); + } + string.append(&out, ".."); + dotdot = out.length; + } + } + else { + if (rooted && out.length != 1) || (!rooted && out.length != 0) { + string.append(&out, "/"); + } + + while r < n && path[r] != '/' { + string.append(&out, path[r]); + r += 1; + } + } + } + + if out.length == 0 { + string.append(&out, "."); + } + + return out; +} + +#doc """ + Concatenates path elements, and returns cleaned output. + + This uses the temporary allocator, so a copy may be needed. +""" +path_join :: (path: ..str) -> str { + out := make(dyn_str, allocator=context.temp_allocator); + + for p: path { + conv.format(&out, "{}{}", p, PATH_SEP); + } + + return path_clean(out); } -path_join :: (parent: str, child: str, allocator := context.temp_allocator) -> str { - out := make(dyn_str, allocator=allocator); - if parent[parent.length - 1] == PATH_SEP { - return conv.format(&out, "{}{}", parent, child); - } else { - return conv.format(&out, "{}{}{}", parent, PATH_SEP, child); +#doc """ + Returns everything but the last element in the path. + + This is then cleaned and copied into the temporary allocator. +""" +path_directory :: (path: str) -> str { + dir, _ := path_split(path); + return path_clean(dir); +} + +path_extension :: (path: str) -> str { + for i: range.{ path.length - 1, 0, -1 } { + if path[i] == '/' do break; + if path[i] == '.' do return path[i .. path.length]; } + return ""; } path_basename :: (path: str) -> str { + if path == "" do return "."; + start := string.last_index_of(path, PATH_SEP); end := string.last_index_of(path, '.'); return path[start + 1 .. end]; } +path_split :: (path: str) -> (parent: str, child: str) { + index := string.last_index_of(path, PATH_SEP); + return path[0 .. index], path[index+1 .. path.length]; +} + diff --git a/tests/stdlib/os_path b/tests/stdlib/os_path new file mode 100644 index 00000000..11aa95e6 --- /dev/null +++ b/tests/stdlib/os_path @@ -0,0 +1,20 @@ +true: .==. +true: abc==abc +true: ..==.. +true: ../../abc==../../abc +true: /==/ +true: abc==abc +true: a/b/c==a/b/c +true: /abc==/abc +true: abc/def/ghi==abc/def/ghi +true: /abc==/abc +true: /abc==/abc +true: abc/def==abc/def +true: /abc/def==/abc/def +true: abc==abc +true: abc/def/jkl==abc/def/jkl +true: abc/jkl==abc/jkl +true: def==def +true: def==def +true: ../../def==../../def +true: abc==abc diff --git a/tests/stdlib/os_path.onyx b/tests/stdlib/os_path.onyx new file mode 100644 index 00000000..c216d5d6 --- /dev/null +++ b/tests/stdlib/os_path.onyx @@ -0,0 +1,35 @@ +use core {*} + +main :: () { + for t: Pair(str, str).[ + .{"", "."}, + .{"abc", "abc"}, + .{"..", ".."}, + .{"../../abc", "../../abc"}, + .{"/", "/"}, + + .{"abc/", "abc"}, + .{"a/b/c/", "a/b/c"}, + .{"/abc/", "/abc"}, + + .{"abc//def//ghi", "abc/def/ghi"}, + .{"//abc", "/abc"}, + .{"//abc//", "/abc"}, + + .{"abc/./def", "abc/def"}, + .{"/./abc/def", "/abc/def"}, + .{"abc/.", "abc"}, + + .{"abc/def/ghi/../jkl", "abc/def/jkl"}, + .{"abc/def/../ghi/../jkl", "abc/jkl"}, + + .{"abc/./../def", "def"}, + .{"abc//./../def", "def"}, + .{"abc/../../././../def", "../../def"}, + + .{"abc/def/..", "abc"} + ] { + cleaned := os.path_clean(t.first); + printf("{}: {}=={}\n", cleaned == t.second, cleaned, t.second); + } +} -- 2.25.1