Oak

fs.oak

← Standard library See on GitHub ↗

// libfs offers ergonomic filesystem APIs to Oak programs.
//
// It wraps the basic built-in filesystem functions to provide more ergonomic,
// safer, and efficient implementations of basic filesystem tasks like reading
// files and walking a directory tree.
//
// Most functions in libfs are implemented in both synchronous and asynchronous
// variants. Sync variants of functions block, and return the value
// immediately for ease of use. For better performance, we can pass a callback
// to the function to invoke its asynchronous variant. In that case, the
// function will not block; instead, the callback will be called some time
// later with the return value.

// ReadBufSize is the size of the buffer used to read a file in streaming
// file-read operations in libfs. This may be changed to alter the behavior of
// libfs, but it will affect the behavior of libfs globally in your program.
ReadBufSize := 4096

fn readFileSync(path) {
	evt := open(path, :readonly)
	if evt.type {
		:error -> ?
		_ -> {
			fd := evt.fd
			fn sub(file, offset) {
				evt := read(fd, offset, ReadBufSize)
				if evt.type {
					:error -> {
						close(fd)
						?
					}
					_ -> if len(evt.data) {
						ReadBufSize -> sub(
							file << evt.data
							offset + ReadBufSize
						)
						_ -> {
							close(fd)
							file << evt.data
						}
					}
				}
			}

			sub('', 0)
		}
	}
}

fn readFileAsync(path, withFile) with open(path, :readonly) fn(evt) if evt.type {
	:error -> withFile(?)
	_ -> {
		fd := evt.fd

		fn sub(file, offset) with read(fd, offset, ReadBufSize) fn(evt) if evt.type {
			:error -> with close(fd) fn {
				withFile(?)
			}
			_ -> if len(evt.data) {
				ReadBufSize -> sub(
					file << evt.data
					offset + ReadBufSize
				)
				_ -> with close(fd) fn {
					withFile(file << evt.data)
				}
			}
		}

		sub('', 0)
	}
}

// readFile reads the entire contents of a file at `path` and returns the file
// contents as a string if successful, or ? on error.
fn readFile(path, withFile) if withFile {
	? -> readFileSync(path)
	_ -> readFileAsync(path, withFile)
}

fn writeFileSyncWithFlag(path, file, flag) {
	evt := open(path, flag)
	if evt.type {
		:error -> ?
		_ -> {
			fd := evt.fd
			{
				evt := write(fd, 0, file)
				close(fd)
				if evt.type {
					:error -> ?
					_ -> true
				}
			}
		}
	}
}

fn writeFileAsyncWithFlag(path, file, flag, withEnd) with open(path, flag) fn(evt) if evt.type {
	:error -> withEnd(?)
	_ -> with write(fd := evt.fd, 0, file) fn(evt) if evt.type {
		:error -> with close(fd) fn {
			withEnd(?)
		}
		_ -> with close(fd) fn {
			withEnd(true)
		}
	}
}

// writeFile writes all data in `file` to a file at `path`, and returns true on
// success and ? on error. If the file does not exist, it will be created. If
// it exists, it will be truncated.
fn writeFile(path, file, withEnd) if withEnd {
	? -> writeFileSyncWithFlag(path, file, :truncate)
	_ -> writeFileAsyncWithFlag(path, file, :truncate, withEnd)
}

// appendFile appends all data in `file` to the end of the file at `path`, and
// returns true on success and ? on error. If the file does not exist, it will
// be created.
fn appendFile(path, file, withEnd) if withEnd {
	? -> writeFileSyncWithFlag(path, file, :append)
	_ -> writeFileAsyncWithFlag(path, file, :append, withEnd)
}

fn statFileSync(path) {
	evt := stat(path)
	if evt.type {
		:error -> ?
		_ -> evt.data
	}
}

fn statFileAsync(path, withStat) with stat(path) fn(evt) if evt.type {
	:error -> withStat(?)
	_ -> withStat(evt.data)
}

// statFile returns the result of stat() if successful, and ? otherwise.
fn statFile(path, withStat) if withStat {
	? -> statFileSync(path)
	_ -> statFileAsync(path, withStat)
}

fn listFilesSync(path) {
	evt := ls(path)
	if evt.type {
		:error -> ?
		_ -> evt.data
	}
}

fn listFilesAsync(path, withFiles) with ls(path) fn(evt) if evt.type {
	:error -> withFiles(?)
	_ -> withFiles(evt.data)
}

// listFiles returns a list of files and directories in a directory at `path`.
// If the directory does not exist or is not a directory, or if the read
// failed, it returns ?.
fn listFiles(path, withFiles) if withFiles {
	? -> listFilesSync(path)
	_ -> listFilesAsync(path, withFiles)
}