commit b88e9ece05c5fe935b7cc9bc5ef43e38b4f1dc77
parent edb9ae65c0526a3c8a809d5cbead44857deef5f1
Author: Autumn! <autumnull@posteo.net>
Date: Sun, 7 May 2023 01:43:33 +0000
path: add trimprefix() and local()
Signed-off-by: Autumn! <autumnull@posteo.net>
Diffstat:
6 files changed, 96 insertions(+), 45 deletions(-)
diff --git a/path/buffer.ha b/path/buffer.ha
@@ -42,3 +42,13 @@ export fn isroot(path: (*buffer | str)) bool = match (path) {
case let path: str => return path == pathsepstr;
case let buf: *buffer => return buf.end == 1 && buf.buf[0] == PATHSEP;
};
+
+// Replace '/' with system-dependent path separator in a string. Modifies the
+// original string, but is idempotent. The result is borrowed from the input.
+export fn local(path: str) str = {
+ let bs = strings::toutf8(path);
+ for (let k = 0z; k < len(bs); k += 1) {
+ if (bs[k] == '/') bs[k] = PATHSEP;
+ };
+ return path;
+};
diff --git a/path/error.ha b/path/error.ha
@@ -3,10 +3,14 @@
export type cant_extend = !void;
// Returned when a path buffer would have overflowed.
export type too_long = !void;
-export type error = !(cant_extend | too_long);
+// Returned when [[trimprefix]] receives a prefix that is not present.
+export type not_prefix = !void;
+// Represents an error during a path manipulation
+export type error = !(cant_extend | too_long | not_prefix);
// Convert an [[error]] into a descriptive string.
export fn strerror(e: error) str = match (e) {
case cant_extend => return "Can't add extension (filename is root or all dots)";
case too_long => return "Path buffer overflow";
+case not_prefix => return "Prefix not present";
};
diff --git a/path/iter.ha b/path/iter.ha
@@ -51,15 +51,15 @@ export fn next(iter: *iterator) (str | void) = {
};
@test fn iter() void = {
- const buf = init(pathsepstr, "foo", "bar", "baz")!;
+ const buf = init(local("/foo/bar/baz"))!;
let i = iter(&buf);
- assert(next(&i) as str == pathsepstr);
+ assert(next(&i) as str == local("/"));
assert(next(&i) as str == "foo");
assert(next(&i) as str == "bar");
assert(next(&i) as str == "baz");
assert(next(&i) is void);
- set(&buf, "foo", "bar", "baz")!;
+ set(&buf, local("foo/bar/baz"))!;
let i = iter(&buf);
assert(next(&i) as str == "foo");
assert(next(&i) as str == "bar");
@@ -71,8 +71,8 @@ export fn next(iter: *iterator) (str | void) = {
assert(next(&i) as str == "foo");
assert(next(&i) is void);
- set(&buf, pathsepstr)!;
+ set(&buf, local("/"))!;
let i = iter(&buf);
- assert(next(&i) as str == pathsepstr);
+ assert(next(&i) as str == local("/"));
assert(next(&i) is void);
};
diff --git a/path/posix.ha b/path/posix.ha
@@ -65,14 +65,7 @@ export fn basename(path: const str) const str = {
["/home//dwc//test", "/home//dwc", "test"],
];
for (let i = 0z; i < len(table); i += 1) {
- // replace '/' with system-dependent path separator
- for (let j = 0z; j < len(table[i]); j += 1) {
- const bs = strings::toutf8(table[i][j]);
- for (let k = 0z; k < len(bs); k += 1) {
- if (bs[k] == '/') bs[k] = PATHSEP;
- };
- };
- assert(dirname(table[i][0]) == table[i][1]);
- assert(basename(table[i][0]) == table[i][2]);
+ assert(dirname(local(table[i][0])) == local(table[i][1]));
+ assert(basename(local(table[i][0])) == local(table[i][2]));
};
};
diff --git a/path/prefix.ha b/path/prefix.ha
@@ -1,3 +1,4 @@
+use bytes;
use strings;
// Add a prefix to a buffer. The buffer will be modified, and it will
@@ -10,11 +11,61 @@ export fn prepend(buf: *buffer, prefix: str...) (str | error) = {
return push(buf, string(&tmp));
};
+// Returns a buffer without a prefix. The prefix is normalized before
+// processing, and this function will return [[too_long]] if the prefix is
+// longer than [[PATH_MAX]]. If the prefix is not present, returns
+// [[not_prefix]]. The resulting path will always be relative.
+//
+// This function does not modify the buffer. See [[popprefix]].
+export fn trimprefix(buf: *buffer, prefix: str) (str | error) = {
+ const start = splitprefix(buf, prefix)?;
+ if (start == buf.end) return ".";
+ return strings::fromutf8_unsafe(buf.buf[start..buf.end]);
+};
+
+// Equivalent to [[trimprefix]], but modifies the buffer in the process.
+export fn popprefix(buf: *buffer, prefix: str) (str | error) = {
+ const start = splitprefix(buf, prefix)?;
+ static delete(buf.buf[..][..start]);
+ buf.end -= start;
+ return string(buf);
+};
+
+// helper function for trimprefix and popprefix, returns the new
+// start of the buffer, or an error.
+fn splitprefix(buf: *buffer, prefix: str) (size | error) = {
+ let pref = init(prefix)?;
+ if (pref.end == 0) {
+ if (abs(buf)) return not_prefix;
+ } else if (pref.end < buf.end && pref.buf[pref.end-1] != PATHSEP) {
+ pref.buf[pref.end] = PATHSEP;
+ pref.end += 1;
+ };
+ if (bytes::hasprefix(buf.buf[..buf.end], pref.buf[..pref.end])) {
+ return pref.end;
+ } else {
+ return not_prefix;
+ };
+};
+
@test fn prepend() void = {
const buf = init("a")!;
- assert(prepend(&buf, "b")! == strings::fromutf8(['b', PATHSEP, 'a'])!);
- set(&buf, pathsepstr, "a")!;
- assert(prepend(&buf, "b")! == strings::fromutf8(['b', PATHSEP, 'a'])!);
-};
+ // relative
+ assert(prepend(&buf, "apple")! == local("apple/a"));
+ assert(popprefix(&buf, "b") is error);
+ assert(popprefix(&buf, "appl") is error);
+ assert(popprefix(&buf, local("/")) is error);
+ assert(popprefix(&buf, ".")! == local("apple/a"));
+ assert(popprefix(&buf, "apple")! == "a");
+ assert(popprefix(&buf, "a")! == ".");
+ // absolute
+ assert(prepend(&buf, local("/apple/a"))! == local("/apple/a"));
+ assert(popprefix(&buf, local("/b")) is error);
+ assert(popprefix(&buf, local("/appl")) is error);
+ assert(popprefix(&buf, ".") is error);
+ assert(popprefix(&buf, local("/"))! == local("apple/a"));
+ assert(prepend(&buf, local("/"))! == local("/apple/a"));
+ assert(popprefix(&buf, local("/apple/a"))! == ".");
+};
diff --git a/path/stack.ha b/path/stack.ha
@@ -84,43 +84,36 @@ fn appendlit(buf: *buffer, bs: []u8) (size | error) = {
assert(push(&buf, "..")! == "..");
assert(push(&buf, "")! == "..");
assert(push(&buf, ".")! == "..");
- assert(push(&buf, pathsepstr)! == "..");
+ assert(push(&buf, local("/"))! == "..");
assert(set(&buf)! == ".");
// root dir invariants
- assert(push(&buf, pathsepstr)! == pathsepstr);
- assert(push(&buf, "")! == pathsepstr);
- assert(push(&buf, ".")! == pathsepstr);
- assert(push(&buf, "..")! == pathsepstr);
- assert(push(&buf, pathsepstr)! == pathsepstr);
+ assert(push(&buf, local("/"))! == local("/"));
+ assert(push(&buf, "")! == local("/"));
+ assert(push(&buf, ".")! == local("/"));
+ assert(push(&buf, "..")! == local("/"));
+ assert(push(&buf, local("/"))! == local("/"));
assert(set(&buf)! == ".");
// regular path and parent
assert(push(&buf, "foo")! == "foo");
assert(push(&buf, ".")! == "foo");
- assert(push(&buf, pathsepstr)! == "foo");
+ assert(push(&buf, local("/"))! == "foo");
assert(push(&buf, "..")! == ".");
// multiple segments
- push(&buf, "a", "b")!;
- assert(bytes::equal(buf.buf[..buf.end], ['a', PATHSEP, 'b']));
- push(&buf, "..", "c")!;
- assert(bytes::equal(buf.buf[..buf.end], ['a', PATHSEP, 'c']));
+ assert(push(&buf, "a", "b")! == local("a/b"));
+ assert(push(&buf, "..", "c")! == local("a/c"));
assert(push(&buf, "..")! == "a");
- push(&buf, strings::fromutf8([PATHSEP, 'd'])!)!;
- assert(bytes::equal(buf.buf[..buf.end], ['a', PATHSEP, 'd']));
+ assert(push(&buf, local("/d"))! == local("a/d"));
assert(push(&buf, "..", "..")! == ".");
// multiple segments, absolute
- push(&buf, pathsepstr, "a", "b")!;
- assert(bytes::equal(buf.buf[..buf.end], [PATHSEP, 'a', PATHSEP, 'b']));
- push(&buf, "..", "c")!;
- assert(bytes::equal(buf.buf[..buf.end], [PATHSEP, 'a', PATHSEP, 'c']));
- push(&buf, "..")!;
- assert(bytes::equal(buf.buf[..buf.end], [PATHSEP, 'a']));
- push(&buf, strings::fromutf8([PATHSEP, 'd'])!)!;
- assert(bytes::equal(buf.buf[..buf.end], [PATHSEP, 'a', PATHSEP, 'd']));
- assert(push(&buf, "..", "..", "..")! == pathsepstr);
+ assert(push(&buf, local("/"), "a", "b")! == local("/a/b"));
+ assert(push(&buf, "..", "c")! == local("/a/c"));
+ assert(push(&buf, "..")! == local("/a"));
+ assert(push(&buf, local("/d"))! == local("/a/d"));
+ assert(push(&buf, "..", "..", "..")! == local("/"));
};
// Examine the final path segment in a buffer.
@@ -157,9 +150,9 @@ fn split(buf: *buffer) (size, (str | void)) = {
// root dir
buf.end = 0;
- push(&buf, pathsepstr)!;
+ push(&buf, local("/"))!;
assert(pop(&buf) is void);
- assert(string(&buf) == pathsepstr);
+ assert(string(&buf) == local("/"));
// relative file
buf.end = 0;
@@ -169,9 +162,9 @@ fn split(buf: *buffer) (size, (str | void)) = {
// abs file
buf.end = 0;
- push(&buf, pathsepstr, "foo")!;
+ push(&buf, local("/foo"))!;
assert(pop(&buf) as str == "foo");
- assert(string(&buf) == pathsepstr);
+ assert(string(&buf) == local("/"));
};
// Returns the parent directory for a given path, without modifying the buffer.