From 242006d05d288029403564ab92f80d0381644484 Mon Sep 17 00:00:00 2001 From: Pierre Curto Date: Tue, 25 Jul 2023 23:23:56 +0200 Subject: [PATCH] add is_absolute and absolute methods to path::Path (#882) * lib/std/io/os: remove unnecessary dup in native_ls Signed-off-by: Pierre Curto * lib/std/core: add String.index_of_char and String.rindex_of_char Signed-off-by: Pierre Curto * lib/std/io: add Path.is_absolute Signed-off-by: Pierre Curto * lib/std/io: add Path.absolute Signed-off-by: Pierre Curto * lib/std: fix Path.normalize on files starting with `.`; add Path.walk Signed-off-by: Pierre Curto --------- Signed-off-by: Pierre Curto --- lib/std/core/string.c3 | 46 ++++++++++++++++++-- lib/std/io/os/ls.c3 | 2 +- lib/std/io/path.c3 | 77 ++++++++++++++++++++++++++------- test/unit/stdlib/core/string.c3 | 16 +++++++ test/unit/stdlib/io/path.c3 | 27 ++++++++++-- 5 files changed, 145 insertions(+), 23 deletions(-) diff --git a/lib/std/core/string.c3 b/lib/std/core/string.c3 index d063b83a6..b0324f06f 100644 --- a/lib/std/core/string.c3 +++ b/lib/std/core/string.c3 @@ -202,6 +202,42 @@ fn bool String.contains(s, String needle) return @ok(s.index_of(needle)); } +/** + * Find the index of the first incidence of a string. + * + * @param [in] s + * @pure + * @ensure return < s.len + * @return "the index of the needle" + * @return! SearchResult.MISSING "if the needle cannot be found" + **/ +fn usz! String.index_of_char(s, char needle) +{ + foreach (i, c : s) + { + if (c == needle) return i; + } + return SearchResult.MISSING?; +} + +/** + * Find the index of the first incidence of a string. + * + * @param [in] s + * @pure + * @ensure return < s.len + * @return "the index of the needle" + * @return! SearchResult.MISSING "if the needle cannot be found" + **/ +fn usz! String.rindex_of_char(s, char needle) +{ + foreach_r (i, c : s) + { + if (c == needle) return i; + } + return SearchResult.MISSING?; +} + /** * Find the index of the first incidence of a string. * @@ -215,10 +251,11 @@ fn bool String.contains(s, String needle) **/ fn usz! String.index_of(s, String needle) { - usz match = 0; usz needed = needle.len; - usz index_start = 0; char search = needle[0]; + if (needed == 1) return s.index_of_char(search); + usz match = 0; + usz index_start = 0; foreach (usz i, char c : s) { if (c == search) @@ -251,10 +288,11 @@ fn usz! String.index_of(s, String needle) **/ fn usz! String.rindex_of(s, String needle) { - usz match = 0; usz needed = needle.len; - usz index_start = 0; char search = needle[^1]; + if (needed == 1) return s.rindex_of_char(search); + usz match; + usz index_start; foreach_r (usz i, char c : s) { if (c == search) diff --git a/lib/std/io/os/ls.c3 b/lib/std/io/os/ls.c3 index 14aa85195..b2c0811e8 100644 --- a/lib/std/io/os/ls.c3 +++ b/lib/std/io/os/ls.c3 @@ -14,7 +14,7 @@ fn PathList! native_ls(Path dir, bool no_dirs, bool no_symlinks, String mask, Al if (!name || name == "." || name == "..") continue; if (entry.d_type == posix::DT_LNK && no_symlinks) continue; if (entry.d_type == posix::DT_DIR && no_dirs) continue; - Path path = path::new(name.copy(using), using)!!; + Path path = path::new(name, using)!!; list.append(path); } return list; diff --git a/lib/std/io/path.c3 b/lib/std/io/path.c3 index 14e2fbf65..90c6a01ac 100644 --- a/lib/std/io/path.c3 +++ b/lib/std/io/path.c3 @@ -26,7 +26,6 @@ enum PathEnv POSIX } - fn Path! getcwd(Allocator* using = mem::heap()) { @pool(using) @@ -138,7 +137,6 @@ fn bool Path.equals(self, Path p2) * Append the string to the current path. * * @param [in] filename - * @ensure return.path_string.len >= self.path_string.len **/ fn Path! Path.append(self, String filename, Allocator* using = mem::heap()) { @@ -163,9 +161,38 @@ fn usz Path.start_of_base_name(self) @local if (!path_str.len) return 0; if (self.env == PathEnv.WIN32) { - return path_str.rindex_of(`\`) + 1 ?? volume_name_len(path_str, self.env)!!; + return path_str.rindex_of_char('\\') + 1 ?? volume_name_len(path_str, self.env)!!; } - return path_str.rindex_of("/") + 1 ?? 0; + return path_str.rindex_of_char('/') + 1 ?? 0; +} + +fn bool! Path.is_absolute(self) +{ + String path_str = self.as_str(); + if (!path_str.len) return false; + usz path_start = volume_name_len(path_str, self.env)!; + return path_start < path_str.len && is_separator(path_str[path_start], self.env); +} + +fn Path! Path.absolute(self, Allocator* using = mem::heap()) +{ + String path_str = self.as_str(); + if (!path_str.len) path_str = "."; + if (path_str == ".") + { + String cwd = os::getcwd(mem::temp())!; + return new(cwd, using, self.env); + } + switch (self.env) + { + case WIN32: + usz path_start = volume_name_len(path_str, self.env)!; + if (path_start > 0) return self; + case POSIX: + if (path_str[0] == PREFERRED_SEPARATOR_POSIX) return self; + } + String cwd = os::getcwd(mem::temp())!; + return Path{ cwd, self.env }.append(path_str, using)!; } fn String Path.basename(self) @@ -247,7 +274,7 @@ fn Path! Path.parent(self) fn String! normalize(String path_str, PathEnv path_env = DEFAULT_PATH_ENV) { - if (!path_str.len) return path_str; + if (!path_str.len) return ""; usz path_start = volume_name_len(path_str, path_env)!; usz path_len = path_str.len; if (path_start == path_len) return path_str; @@ -288,13 +315,20 @@ fn String! normalize(String path_str, PathEnv path_env = DEFAULT_PATH_ENV) // Get the number of dots until next separator, expecting 1 or 2 bool is_last = i == path_len - 1; int dots = 1; - if (!is_last && path_str[i + 1] == '.') + if (!is_last) { - dots = 2; - is_last = i == path_len - 2; - if (!is_last && !is_separator(path_str[i + 2], path_env)) + char next = path_str[i + 1]; + switch { - dots = 0; + case next == '.': + dots = 2; + is_last = i == path_len - 2; + if (!is_last && !is_separator(path_str[i + 2], path_env)) + { + dots = 0; + } + case !is_separator(next, path_env): + dots = 0; } } switch (dots) @@ -373,8 +407,24 @@ fn String Path.root_directory(self) return path_str; } +def PathWalker = fn bool(Path); -fn String Path.as_str(self) +fn bool! Path.walk(self, PathWalker w, Allocator* using = mem::heap()) +{ + Path abs = self.absolute(using)!; + defer abs.free(); + PathList files = ls(abs, .using = using)!; + foreach (f : files) + { + if (f.as_str() == "." || f.as_str() == "..") continue; + f = abs.append(f.as_str(), using)!; + if (w(f)) return true; + if (is_dir(f) && f.walk(w, using)!) return true; + } + return false; +} + +fn String Path.as_str(self) @inline { return self.path_string; } @@ -385,13 +435,11 @@ fn bool Path.has_suffix(self, String str) return self.as_str().ends_with(str); } - fn void Path.free(self) { free(self.path_string.ptr); } - const bool[256] RESERVED_PATH_CHAR_POSIX = { [0] = true, ['/'] = true, @@ -419,5 +467,4 @@ macro bool is_reserved_path_char(char c, PathEnv path_env = DEFAULT_PATH_ENV) return path_env == PathEnv.WIN32 ? RESERVED_PATH_CHAR_WIN32[c] : RESERVED_PATH_CHAR_POSIX[c]; -} - +} \ No newline at end of file diff --git a/test/unit/stdlib/core/string.c3 b/test/unit/stdlib/core/string.c3 index 4b6d3f799..b02610730 100644 --- a/test/unit/stdlib/core/string.c3 +++ b/test/unit/stdlib/core/string.c3 @@ -91,4 +91,20 @@ fn void! test_rindex_of() assert(test.rindex_of("he")! == 12); assert(test.rindex_of("world")! == 6); assert(@catchof(test.rindex_of("wi"))); +} + +fn void! test_index_of_char() +{ + String test = "hello world hello"; + assert(test.index_of_char('o')! == 4); + assert(test.index_of_char('l')! == 2); + assert(@catchof(test.index_of_char('x'))); +} + +fn void! test_rindex_of_char() +{ + String test = "hello world hello"; + assert(test.rindex_of_char('o')! == 16); + assert(test.rindex_of_char('l')! == 15); + assert(@catchof(test.index_of_char('x'))); } \ No newline at end of file diff --git a/test/unit/stdlib/io/path.c3 b/test/unit/stdlib/io/path.c3 index 58607b256..dc01be610 100644 --- a/test/unit/stdlib/io/path.c3 +++ b/test/unit/stdlib/io/path.c3 @@ -1,6 +1,5 @@ module std::io::path @test; - fn void! test_parent() { Path p = path::new("")!; @@ -42,6 +41,8 @@ fn void! test_path_normalized() assert(path::new("/foo/bar/../", .path_env = PathEnv.POSIX).as_str()! == "/foo"); assert(path::new("/foo//bar", .path_env = PathEnv.POSIX).as_str()! == "/foo/bar"); assert(path::new("/foo//bar/../", .path_env = PathEnv.POSIX).as_str()! == "/foo"); + assert(path::new("/foo/.bar", .path_env = PathEnv.POSIX).as_str()! == "/foo/.bar"); + assert(path::new(`\foo\.bar`, .path_env = PathEnv.WIN32).as_str()! == `\foo\.bar`); assert(path::new("a\\b/c.txt", .path_env = PathEnv.WIN32).as_str()! == `a\b\c.txt`); assert(path::new("a\\b/c.txt", .path_env = PathEnv.POSIX).as_str()! == "a\\b/c.txt"); assert(path::new("C:\\a\\b/c.txt", .path_env = PathEnv.WIN32).as_str()! == `C:\a\b\c.txt`); @@ -223,7 +224,6 @@ fn void! test_extension() fn void! test_basename() { - assert(path::new_windows("file.txt").basename()! == "file.txt"); assert(path::new_posix("file.txt").basename()! == "file.txt"); @@ -251,7 +251,6 @@ fn void! test_basename() fn void! test_dirname() { - assert(path::new_windows("file.txt").dirname()! == ""); assert(path::new_posix("file.txt").dirname()! == ""); @@ -289,3 +288,25 @@ fn void! test_path_volume() assert(path::new_windows(`\\server\`).volume_name()! == `\\server`); assert(path::new_windows(`\\server\abc`).volume_name()! == `\\server`); } + +fn void! test_path_is_absolute() +{ + assert(!path::new_posix("").is_absolute()!); + assert(path::new_posix("/").is_absolute()!); + assert(path::new_posix("/a/b").is_absolute()!); + assert(!path::new_posix("a/b").is_absolute()!); + + assert(!path::new_windows(`C:`).is_absolute()!); + assert(path::new_windows(`C:\abs`).is_absolute()!); + assert(!path::new_windows(`C:abs`).is_absolute()!); + assert(path::new_windows(`\\server\`).is_absolute()!); + assert(path::new_windows(`\\server\abc`).is_absolute()!); +} + +fn void! test_path_absolute() +{ + assert(path::new_posix("/").absolute()!.as_str() == "/"); + assert(path::new_posix(".").absolute()!.as_str() == path::getcwd(mem::temp())!!.as_str()); + + assert(path::new_windows(`C:\abs`).absolute()!.as_str() == `C:\abs`); +} \ No newline at end of file