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 <pierre.curto@gmail.com>

* lib/std/core: add String.index_of_char and String.rindex_of_char

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>

* lib/std/io: add Path.is_absolute

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>

* lib/std/io: add Path.absolute

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>

* lib/std: fix Path.normalize on files starting with `.`; add Path.walk

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>

---------

Signed-off-by: Pierre Curto <pierre.curto@gmail.com>
This commit is contained in:
Pierre Curto
2023-07-25 23:23:56 +02:00
committed by GitHub
parent b74b62e242
commit 242006d05d
5 changed files with 145 additions and 23 deletions

View File

@@ -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)

View File

@@ -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;

View File

@@ -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];
}
}

View File

@@ -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')));
}

View File

@@ -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`);
}