commit d73900c2b93a2558e9d814316a0ede90fa0e3a9d
parent 25c5c490f983636de33a921ec2468096af727682
Author: Drew DeVault <sir@cmpwn.com>
Date: Mon, 8 Mar 2021 17:26:31 -0500
path: add iter, fix misc. bugs
Diffstat:
3 files changed, 93 insertions(+), 1 deletion(-)
diff --git a/path/iter.ha b/path/iter.ha
@@ -0,0 +1,78 @@
+use bytes;
+use strings;
+
+use io;
+
+const pathsep: []u8 = [PATHSEP];
+
+export type iflags = enum uint {
+ NONE = 0,
+ STRING = 1 << 0,
+ ABSOLUTE = 1 << 1,
+};
+
+// An iterator which yields each component of a path.
+export type iterator = struct {
+ tok: bytes::tokenizer,
+ flags: iflags,
+};
+
+// Returns an iterator which yields each component of a path. If the path is
+// absolute, the first component will be the root path (e.g. /).
+export fn iter(path: path) iterator = {
+ let flags = iflags::NONE;
+ if (path is str) {
+ flags |= iflags::STRING;
+ };
+
+ let pb = pathbytes(path);
+ if (len(pb) > 0 && pb[0] == PATHSEP) {
+ flags |= iflags::ABSOLUTE;
+ pb = pb[1..];
+ };
+ if (len(pb) > 1 && pb[len(pb) - 1] == PATHSEP) {
+ pb = pb[..len(pb) - 1];
+ };
+
+ return iterator {
+ tok = bytes::tokenize(pb, pathsep),
+ flags = flags,
+ };
+};
+
+// Returns the next path component from an iterator, or void if none remain.
+export fn next(iter: *iterator) (path | void) = {
+ if (iter.flags & iflags::ABSOLUTE == iflags::ABSOLUTE) {
+ iter.flags &= ~iflags::ABSOLUTE;
+ static assert(PATHSEP <= 0x7F);
+ return strings::from_utf8_unsafe(pathsep);
+ };
+ return match (bytes::next_token(&iter.tok)) {
+ void => void,
+ b: []u8 => if (iter.flags & iflags::STRING == iflags::STRING)
+ strings::from_utf8_unsafe(b)
+ else b,
+ };
+};
+
+@test fn iter() void = {
+ assert(PATHSEP == '/': u32: u8); // meh
+ let i = iter("/foo/bar/baz");
+ assert(equal(next(&i) as path, "/"));
+ assert(equal(next(&i) as path, "foo"));
+ assert(equal(next(&i) as path, "bar"));
+ assert(equal(next(&i) as path, "baz"));
+ assert(next(&i) is void);
+ let i = iter("foo/bar/baz/");
+ assert(equal(next(&i) as path, "foo"));
+ assert(equal(next(&i) as path, "bar"));
+ assert(equal(next(&i) as path, "baz"));
+ assert(next(&i) is void);
+ let i = iter("foo");
+ assert(equal(next(&i) as path, "foo"));
+ assert(next(&i) is void);
+ // Hm?
+ //let i = iter("/");
+ //assert(equal(next(&i) as path, "/"));
+ //assert(next(&i) is void);
+};
diff --git a/path/join.ha b/path/join.ha
@@ -1,3 +1,4 @@
+use bytes;
use bufio;
use strings;
use io;
@@ -14,10 +15,10 @@ export fn join(paths: path...) path = {
let buf = pathbytes(paths[i]);
let l = len(buf);
+ if (l == 0) continue;
for (l > 0 && buf[l - 1] == PATHSEP) {
l -= 1;
};
- if (l == 0) continue;
for (let q = 0z; q < l) {
let w = io::write(sink, buf[q..l]) as size;
q += w;
@@ -61,4 +62,8 @@ export fn join(paths: path...) path = {
let p = join("foo", "", "bar");
defer path_free(p);
assert(p as str == "foo/bar");
+
+ let p = join("/", "foo", "bar", "baz");
+ defer path_free(p);
+ assert(p as str == "/foo/bar/baz");
};
diff --git a/path/util.ha b/path/util.ha
@@ -18,3 +18,12 @@ export fn path_free(p: path) void = match (p) {
// Returns true if two paths are equal.
export fn equal(a: path, b: path) bool =
bytes::equal(pathbytes(a), pathbytes(b));
+
+// Returns true if a path is an absolute path.
+export fn abs(path: path::path) bool = {
+ let b = pathbytes(path);
+ if (len(b) == 0) {
+ return false;
+ };
+ return b[0] == PATHSEP;
+};