hare

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

commit 7cb3a253f43c12a746be2f377ff420ca4d2734dd
parent cd68fa6697115b04b302dde790754585b31d299a
Author: Drew DeVault <sir@cmpwn.com>
Date:   Thu, 25 Feb 2021 20:03:48 -0500

path: new module

Diffstat:
Mfs/fs.ha | 10+++++-----
Mfs/types.ha | 16+++++++---------
Mfs/util.ha | 3++-
Mos/+linux/dirfdfs.ha | 15++++++++-------
Mos/+linux/fs.ha | 5+++--
Mos/+linux/open.ha | 5+++--
Mos/fs.ha | 9+++++----
Apath/+linux.ha | 2++
Apath/join.ha | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Apath/util.ha | 20++++++++++++++++++++
10 files changed, 114 insertions(+), 30 deletions(-)

diff --git a/fs/fs.ha b/fs/fs.ha @@ -9,7 +9,7 @@ export fn close(fs: *fs) void = { }; // Opens a file. -export fn open(fs: *fs, path: path, mode: io::mode) (*io::stream | error) = { +export fn open(fs: *fs, path: path::path, mode: io::mode) (*io::stream | error) = { return match (fs.open) { null => io::unsupported, f: *openfunc => f(fs, path, mode), @@ -17,7 +17,7 @@ export fn open(fs: *fs, path: path, mode: io::mode) (*io::stream | error) = { }; // Creates a new file. The default file permissions are implementation defined. -export fn create(fs: *fs, path: path, mode: io::mode) (*io::stream | error) = { +export fn create(fs: *fs, path: path::path, mode: io::mode) (*io::stream | error) = { return match (fs.create) { null => io::unsupported, f: *createfunc => f(fs, path, mode), @@ -27,7 +27,7 @@ export fn create(fs: *fs, path: path, mode: io::mode) (*io::stream | error) = { // Returns an iterator for a path, which yields the contents of a directory. // Pass empty string to yield from the root. The order in which entries are // returned is undefined. -export fn iter(fs: *fs, path: path) (*iterator | error) = { +export fn iter(fs: *fs, path: path::path) (*iterator | error) = { return match (fs.iter) { null => io::unsupported, f: *iterfunc => f(fs, path), @@ -36,7 +36,7 @@ export fn iter(fs: *fs, path: path) (*iterator | error) = { // Obtains information about a file or directory. If the target is a symlink, // information is returned about the link, not its target. -export fn stat(fs: *fs, path: path) (filestat | error) = { +export fn stat(fs: *fs, path: path::path) (filestat | error) = { return match (fs.stat) { null => io::unsupported, f: *statfunc => f(fs, path), @@ -44,7 +44,7 @@ export fn stat(fs: *fs, path: path) (filestat | error) = { }; // Opens a new filesystem for a subdirectory. -export fn subdir(fs: *fs, path: path) (*fs | error) = { +export fn subdir(fs: *fs, path: path::path) (*fs | error) = { return match (fs.subdir) { null => io::unsupported, f: *subdirfunc => f(fs, path), diff --git a/fs/types.ha b/fs/types.ha @@ -1,8 +1,6 @@ use io; use strings; - -// A path or path component. -export type path = (str | []u8); +use path; // An entry was requested which does not exist. export type noentry = void!; @@ -108,7 +106,7 @@ export type filestat = struct { export type dirent = struct { // The name of this entry. Not fully qualified: for example, // "foo/bar/baz.txt" would store "baz.txt" here. - name: path, + name: path::path, // The type of this entry. The permission bits may be unset. ftype: mode, @@ -135,11 +133,11 @@ export fn dirent_free(e: dirent) void = match (e.name) { }; export type closefunc = fn(fs: *fs) void; -export type openfunc = fn(fs: *fs, path: path, mode: io::mode) (*io::stream | error); -export type createfunc = fn(fs: *fs, path: path, mode: io::mode) (*io::stream | error); -export type iterfunc = fn(fs: *fs, path: path) (*iterator | error); -export type statfunc = fn(fs: *fs, path: path) (filestat | error); -export type subdirfunc = fn(fs: *fs, path: path) (*fs | error); +export type openfunc = fn(fs: *fs, path: path::path, mode: io::mode) (*io::stream | error); +export type createfunc = fn(fs: *fs, path: path::path, mode: io::mode) (*io::stream | error); +export type iterfunc = fn(fs: *fs, path: path::path) (*iterator | error); +export type statfunc = fn(fs: *fs, path: path::path) (filestat | error); +export type subdirfunc = fn(fs: *fs, path: path::path) (*fs | error); // An abstract implementation of a filesystem. To create a custom stream, embed // this type as the first member of a struct with user-specific data and fill diff --git a/fs/util.ha b/fs/util.ha @@ -1,4 +1,5 @@ use io; +use path; use strings; // Returns a human-friendly representation of an error. @@ -77,7 +78,7 @@ export fn is_socket(mode: mode) bool = mode & mode::SOCK == mode::SOCK; // Reads all entries from a directory. The caller must free the return value // with [dirents_free]. -export fn readdir(fs: *fs, path: path) ([]dirent | error) = { +export fn readdir(fs: *fs, path: path::path) ([]dirent | error) = { let i = iter(fs, path)?; let ents: []dirent = []; for (true) { diff --git a/os/+linux/dirfdfs.ha b/os/+linux/dirfdfs.ha @@ -1,6 +1,7 @@ use encoding::utf8; use fs; use io; +use path; use rt; use strings; @@ -54,7 +55,7 @@ fn static_dirfdopen(fd: int, filesystem: *os_filesystem) *fs::fs = { return &filesystem.fs; }; -fn pathbytes(path: fs::path) *const char = { +fn pathbytes(path: path::path) *const char = { let p = match (path) { s: str => strings::to_utf8(s), b: []u8 => b, @@ -89,7 +90,7 @@ export fn dirfs_clone(fs: *fs::fs, resolve: resolve...) *fs::fs = { fn _fs_open( fs: *fs::fs, - path: fs::path, + path: path::path, mode: io::mode, oh: *rt::open_how, ) (*io::stream | fs::error) = { @@ -123,7 +124,7 @@ fn _fs_open( return fdopen(fd, name, mode); }; -fn fs_open(fs: *fs::fs, path: fs::path, mode: io::mode) (*io::stream | fs::error) = { +fn fs_open(fs: *fs::fs, path: path::path, mode: io::mode) (*io::stream | fs::error) = { let oflags = rt::O_NOCTTY | rt::O_CLOEXEC; if (mode & io::mode::RDWR == io::mode::RDWR) { oflags |= rt::O_RDWR; @@ -140,7 +141,7 @@ fn fs_open(fs: *fs::fs, path: fs::path, mode: io::mode) (*io::stream | fs::error return _fs_open(fs, path, mode, &oh); }; -fn fs_create(fs: *fs::fs, path: fs::path, mode: io::mode) (*io::stream | fs::error) = { +fn fs_create(fs: *fs::fs, path: path::path, mode: io::mode) (*io::stream | fs::error) = { let oflags = rt::O_NOCTTY | rt::O_CLOEXEC | rt::O_CREATE; if (mode & io::mode::RDWR == io::mode::RDWR) { oflags |= rt::O_RDWR; @@ -157,7 +158,7 @@ fn fs_create(fs: *fs::fs, path: fs::path, mode: io::mode) (*io::stream | fs::err return _fs_open(fs, path, mode, &oh); }; -fn fs_stat(fs: *fs::fs, path: fs::path) (fs::filestat | fs::error) = { +fn fs_stat(fs: *fs::fs, path: path::path) (fs::filestat | fs::error) = { let fs = fs: *os_filesystem; let st = rt::st { ... }; match (rt::fstatat(fs.dirfd, pathbytes(path), @@ -178,7 +179,7 @@ fn fs_stat(fs: *fs::fs, path: fs::path) (fs::filestat | fs::error) = { }; }; -fn fs_subdir(fs: *fs::fs, path: fs::path) (*fs::fs | fs::error) = { +fn fs_subdir(fs: *fs::fs, path: path::path) (*fs::fs | fs::error) = { let fs = fs: *os_filesystem; let oh = rt::open_how { flags = (rt::O_RDONLY | rt::O_CLOEXEC | rt::O_DIRECTORY): u64, @@ -210,7 +211,7 @@ type os_iterator = struct { buf: [BUFSIZ]u8, }; -fn fs_iter(fs: *fs::fs, path: fs::path) (*fs::iterator | fs::error) = { +fn fs_iter(fs: *fs::fs, path: path::path) (*fs::iterator | fs::error) = { let fs = fs: *os_filesystem; let oh = rt::open_how { flags = (rt::O_RDONLY | rt::O_CLOEXEC | rt::O_DIRECTORY): u64, diff --git a/os/+linux/fs.ha b/os/+linux/fs.ha @@ -1,4 +1,5 @@ use fs; +use path; use rt; use strings; @@ -13,7 +14,7 @@ use strings; }; // Change the current working directory. -export fn chdir(target: (*fs::fs | ...fs::path)) (void | fs::error) = { +export fn chdir(target: (*fs::fs | ...path::path)) (void | fs::error) = { const path: []u8 = match (target) { fs: *fs::fs => { assert(fs.open == &fs_open); @@ -36,7 +37,7 @@ export fn chdir(target: (*fs::fs | ...fs::path)) (void | fs::error) = { // have root or otherwise elevated permissions. // // This function is not appropriate for sandboxing. -export fn chroot(target: fs::path) (void | fs::error) = { +export fn chroot(target: path::path) (void | fs::error) = { const path: []u8 = match (target) { s: str => strings::to_utf8(s), b: []u8 => b, diff --git a/os/+linux/open.ha b/os/+linux/open.ha @@ -1,5 +1,6 @@ use fs; use io; +use path; use rt; use strings; @@ -27,7 +28,7 @@ export type flags = enum int { // // If no flags are provided, the default for +linux is RDONLY | NOCTTY | CLOEXEC. export fn open( - path: fs::path, + path: path::path, flag: flags... ) (*io::stream | io::error) = { const name: str = match (path) { @@ -63,7 +64,7 @@ export fn open( // Equivalent to [open], but allows the caller to specify a file mode for the // new file. If no other flag is given, the default read/write mode is WRONLY. export fn create( - path: fs::path, + path: path::path, mode: uint, flag: flags... ) (*io::stream | io::error) = { diff --git a/os/fs.ha b/os/fs.ha @@ -1,4 +1,5 @@ use fs; +use path; // Provides an implementation of [fs::fs] for the host filesystem. export let root: *fs::fs = null: *fs::fs; @@ -7,16 +8,16 @@ export let root: *fs::fs = null: *fs::fs; export let cwd: *fs::fs = null: *fs::fs; // Creates an [fs::iterator] for a given directory to read its contents. -export fn iterdir(path: fs::path) (*fs::iterator | fs::error) = +export fn iterdir(path: path::path) (*fs::iterator | fs::error) = fs::iter(cwd, path); // Reads all entries from a directory. The caller must free the return value // with [fs::dirents_free]. -export fn readdir(path: fs::path) ([]fs::dirent | fs::error) = +export fn readdir(path: path::path) ([]fs::dirent | fs::error) = fs::readdir(cwd, path); // Returns file information for a given path. -export fn stat(path: fs::path) (fs::filestat | fs::error) = fs::stat(cwd, path); +export fn stat(path: path::path) (fs::filestat | fs::error) = fs::stat(cwd, path); // Opens a directory as a filesystem. -export fn diropen(path: fs::path) (*fs::fs | fs::error) = fs::subdir(cwd, path); +export fn diropen(path: path::path) (*fs::fs | fs::error) = fs::subdir(cwd, path); diff --git a/path/+linux.ha b/path/+linux.ha @@ -0,0 +1,2 @@ +// Path separator, platform-specific. +export def PATHSEP: u8 = '/': u32: u8; diff --git a/path/join.ha b/path/join.ha @@ -0,0 +1,59 @@ +use bufio; +use strings; +use io; + +// Joins together several path components with the path separator. The caller +// must free the return value with [path_free]. +export fn join(paths: path...) path = { + // TODO: Normalize inputs so that if they end with a / we don't double + // up on delimiters + let sink = bufio::dynamic(); + let utf8 = true; + for (let i = 0z; i < len(paths); i += 1) { + utf8 = utf8 && paths[i] is str; + + let buf = pathbytes(paths[i]); + let l = len(buf); + for (buf[l - 1] == PATHSEP) { + l -= 1; + }; + for (let q = 0z; q < l) { + let w = io::write(sink, buf[q..l]) as size; + q += w; + }; + if (i + 1 < len(paths)) { + assert(io::write(sink, [PATHSEP]) as size == 1); + }; + }; + + return + if (utf8) strings::from_utf8_unsafe(bufio::finish(sink)) + else bufio::finish(sink); +}; + +@test fn join() void = { + assert(PATHSEP == '/': u32: u8); // TODO: meh + let i = join("foo"); + defer path_free(i); + assert(i as str == "foo"); + + let p = join(i, "bar", "baz"); + defer path_free(p); + assert(p as str == "foo/bar/baz"); + + let q = join(p, "bat", "bad"); + defer path_free(q); + assert(q as str == "foo/bar/baz/bat/bad"); + + let r = join(p, q); + defer path_free(r); + assert(r as str == "foo/bar/baz/foo/bar/baz/bat/bad"); + + let p = join("foo/", "bar"); + defer path_free(p); + assert(p as str == "foo/bar"); + + let p = join("foo///", "bar"); + defer path_free(p); + assert(p as str == "foo/bar"); +}; diff --git a/path/util.ha b/path/util.ha @@ -0,0 +1,20 @@ +use bytes; +use strings; + +// A path or path component. +export type path = (str | []u8); + +fn pathbytes(path: path) []u8 = match (path) { + s: str => strings::to_utf8(s), + b: []u8 => b, +}; + +// Frees a path. +export fn path_free(p: path) void = match (p) { + s: str => free(s), + b: []u8 => free(b), +}; + +// Returns true if two paths are equal. +export fn equal(a: path, b: path) bool = + bytes::equal(pathbytes(a), pathbytes(b));