hare

[hare] The Hare programming language
git clone https://git.torresjrjr.com/hare.git
Log | Files | Refs | README | LICENSE

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:
Mpath/buffer.ha | 10++++++++++
Mpath/error.ha | 6+++++-
Mpath/iter.ha | 10+++++-----
Mpath/posix.ha | 11++---------
Mpath/prefix.ha | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Mpath/stack.ha | 45+++++++++++++++++++--------------------------
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.