// path — filesystem path manipulation. UTF-8 paths, '/' separator. // Mirrors Hare's path:: surface. ww doesn't yet ship the stack-buffer // path::buffer; the functions here all take a `str` and return a // borrowed view (basename / dirname) or a fresh owned str (join). package path; import strings; def SEP: u8 = '/'; // abs — `p` is an absolute path (starts with '/'). export fn abs(p: str) bool = { if (p.len == 0) { return false; }; return p[0] == SEP; }; // dirname — directory component of `p`, POSIX-style. Returns a // borrowed view of `p` (or the static "." / "/" strings). Mirrors // Hare's path::dirname. export fn dirname(p: str) str = { if (p.len == 0) { return "."; }; // Strip trailing separators. let n: i32 = p.len; for (n > 0) { if (p[n - 1] != SEP) { break; }; n -= 1; }; if (n == 0) { return "/"; }; // Last separator in the trimmed prefix. let i: i32 = n - 1; for (i >= 0) { if (p[i] == SEP) { break; }; i -= 1; }; if (i < 0) { return "."; }; // Strip trailing separators on the directory part too. for (i > 0) { if (p[i - 1] != SEP) { break; }; i -= 1; }; if (i == 0) { return "/"; }; let r: str; r.ptr = p.ptr; r.len = i; return r; }; // basename — final path component of `p`, POSIX-style. Returns a // borrowed view of `p` (or "." / "/" sentinel strings). Mirrors // Hare's path::basename. export fn basename(p: str) str = { if (p.len == 0) { return "."; }; let n: i32 = p.len; for (n > 0) { if (p[n - 1] != SEP) { break; }; n -= 1; }; if (n == 0) { return "/"; }; let i: i32 = n - 1; for (i >= 0) { if (p[i] == SEP) { break; }; i -= 1; }; let r: str; r.ptr = p.ptr + ((i + 1): u64); r.len = n - (i + 1); return r; }; // extension — the final ".ext" suffix of `basename(p)`, including // the dot, or "" if none. Returns a borrowed view of `p`. Mirrors // Hare's path::extension. export fn extension(p: str) str = { let b: str = basename(p); let i: i32 = b.len - 1; for (i > 0) { if (b[i] == '.') { let r: str; r.ptr = b.ptr + (i: u64); r.len = b.len - i; return r; }; i -= 1; }; let empty: str; empty.ptr = nil; empty.len = 0; return empty; }; // join — concatenate two path components with a single '/' separator. // Returns owned str (release via os.free). If `b` is absolute, the // result is `b`. Mirrors Hare's path::buffer init+push, narrowed to // two-arg join (no variadic). export fn join(a: str, b: str) str = { if (abs(b)) { let buf: []u8 = alloc([], b.len: u64)!; let i: i32 = 0; for (i < b.len) { buf[i] = b[i]; i += 1; }; buf.len = b.len; return strings.frombytes(buf); }; if (a.len == 0) { let buf: []u8 = alloc([], b.len: u64)!; let i: i32 = 0; for (i < b.len) { buf[i] = b[i]; i += 1; }; buf.len = b.len; return strings.frombytes(buf); }; if (b.len == 0) { let buf: []u8 = alloc([], a.len: u64)!; let i: i32 = 0; for (i < a.len) { buf[i] = a[i]; i += 1; }; buf.len = a.len; return strings.frombytes(buf); }; // Trim trailing '/' from a; b never starts with '/' here (checked // above via abs(b)). let an: i32 = a.len; for (an > 0) { if (a[an - 1] != SEP) { break; }; an -= 1; }; let total: i32 = an + 1 + b.len; let buf: []u8 = alloc([], total: u64)!; let i: i32 = 0; for (i < an) { buf[i] = a[i]; i += 1; }; buf[an] = SEP; let j: i32 = 0; for (j < b.len) { buf[an + 1 + j] = b[j]; j += 1; }; buf.len = total; return strings.frombytes(buf); };