hare

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

commit 49dd6cb556850f04aee2f132307e9127d297262f
parent a48194508062e2f60eb956273f52052d6efe7ffe
Author: Drew DeVault <sir@cmpwn.com>
Date:   Sat,  8 Jan 2022 13:50:22 +0100

path: implement ".." for path::buffer

Signed-off-by: Drew DeVault <sir@cmpwn.com>

Diffstat:
Mpath/buffer.ha | 76+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Mpath/join.ha | 4++++
Mpath/names.ha | 2+-
3 files changed, 74 insertions(+), 8 deletions(-)

diff --git a/path/buffer.ha b/path/buffer.ha @@ -31,16 +31,25 @@ export fn dup(buf: *buffer) buffer = { // Returns the current path stored in this buffer. The path will always be // normalized, which is to say that it will not include any of the following: // -// - "." components // - Redundant ".." components -// - Repeated path separators +// - Redundant path separators +// - Any "." components, except in the case of "." +// - Trailing slashes, except in the case of "/" // // "/usr//bin/../bin/./hare" becomes "/usr/bin/hare" and "../../foo/bar" is // unchanged. export fn string(buf: *buffer) str = { - return strings::fromutf8_unsafe(buf.cur); + const value = strings::fromutf8_unsafe(buf.cur); + if (value == "") { + return "."; + }; + return value; }; +const dot: []u8 = ['.': u8]; +const dotdot: []u8 = ['.': u8, '.': u8]; +const dotdotslash: []u8 = ['.': u8, '.': u8, PATHSEP]; + // Normalizes and appends a path component to a buffer. // // Invariant: elem must either be equal to [PATHSEP], or contain no path @@ -58,11 +67,42 @@ fn appendnorm(buf: *buffer, elem: (str | []u8)) (void | errors::overflow) = { return; }; return; - } else if (bytes::equal(elem, ['.': u8])) { + } else if (bytes::equal(elem, dot)) { return; - } else if (bytes::equal(elem, ['.': u8, '.': u8])) { - abort(); // TODO + } else if (bytes::equal(elem, dotdot)) { + return parent(buf); + }; + return doappend(buf, elem); +}; + +// Moves the buffer to the parent of the current directory. +fn parent(buf: *buffer) (void | errors::overflow) = { + let ascending = true; + const iter = iter(buf); + for (true) { + match (next(&iter)) { + case let elem: str => + if (elem != "..") { + ascending = false; + break; + }; + case void => + break; + }; + }; + if (ascending) { + // If we are appending ".." to a path which is entirely composed + // of ".." elements, then we want to append it normally, so that + // "../.." becomes "../../.." instead of "..". + return doappend(buf, dotdot); }; + // XXX: This is not super efficient + const name = dirname(string(buf)); + reset(buf); + add(buf, name)?; +}; + +fn doappend(buf: *buffer, elem: []u8) (void | errors::overflow) = { if (len(buf.cur) + len(elem) + 1 >= PATH_MAX) { return errors::overflow; }; @@ -74,7 +114,7 @@ fn appendnorm(buf: *buffer, elem: (str | []u8)) (void | errors::overflow) = { @test fn appendnorm() void = { let buf = init(); - assert(string(&buf) == ""); + assert(string(&buf) == "."); appendnorm(&buf, "foo")!; appendnorm(&buf, "bar")!; appendnorm(&buf, "baz")!; @@ -99,4 +139,26 @@ fn appendnorm(buf: *buffer, elem: (str | []u8)) (void | errors::overflow) = { appendnorm(&buf, "/")!; appendnorm(&buf, "/")!; assert(string(&buf) == "/"); + + let buf = init(); + appendnorm(&buf, ".")!; + appendnorm(&buf, "foo")!; + assert(string(&buf) == "foo"); + + let buf = init(); + appendnorm(&buf, "..")!; + assert(string(&buf) == ".."); + appendnorm(&buf, "..")!; + assert(string(&buf) == "../.."); + appendnorm(&buf, "..")!; + assert(string(&buf) == "../../.."); + + let buf = init(); + appendnorm(&buf, "foo")!; + appendnorm(&buf, "bar")!; + assert(string(&buf) == "foo/bar"); + appendnorm(&buf, "..")!; + assert(string(&buf) == "foo"); + appendnorm(&buf, "..")!; + assert(string(&buf) == "."); }; diff --git a/path/join.ha b/path/join.ha @@ -36,6 +36,10 @@ export fn add(buf: *buffer, items: str...) (void | errors::overflow) = { reset(&buf); add(&buf, "/", "foo/bar", "baz")!; assert(string(&buf) == "/foo/bar/baz"); + + reset(&buf); + add(&buf, "./foo/bar")!; + assert(string(&buf) == "foo/bar"); }; // Joins a list of path components together, normalizes it, and returns the diff --git a/path/names.ha b/path/names.ha @@ -4,7 +4,7 @@ use strings; // Returns the directory name for a given path. For a path to a file name, this // returns the directory in which that file resides. For a path to a directory, -// this returns the path to its parent directory. The return value is eitherr +// this returns the path to its parent directory. The return value is either // borrowed from the input or statically allocated), use [[dup]] to extend its // lifetime or modify it. export fn dirname(path: str) const str = {