2022-10-20 22:30:48 +01:00
|
|
|
const meta = @import("./meta.zig");
|
2022-09-09 22:55:34 +01:00
|
|
|
const stack = @import("./stack.zig");
|
|
|
|
const std = @import("std");
|
|
|
|
|
2022-10-17 10:34:04 +01:00
|
|
|
///
|
|
|
|
/// File-system agnostic abstraction for manipulating a file.
|
|
|
|
///
|
|
|
|
pub const FileAccess = struct {
|
|
|
|
context: *anyopaque,
|
|
|
|
implementation: *const Implementation,
|
|
|
|
|
|
|
|
///
|
|
|
|
/// Provides a set of implementation-specific behaviors to a [FileAccess] instance.
|
|
|
|
///
|
|
|
|
pub const Implementation = struct {
|
|
|
|
close: fn (*anyopaque) void,
|
|
|
|
queryCursor: fn (*anyopaque) Error!u64,
|
|
|
|
queryLength: fn (*anyopaque) Error!u64,
|
|
|
|
read: fn (*anyopaque, []u8) Error!usize,
|
|
|
|
seek: fn (*anyopaque, u64) Error!void,
|
|
|
|
seekToEnd: fn (*anyopaque) Error!void,
|
|
|
|
skip: fn (*anyopaque, i64) Error!void,
|
|
|
|
};
|
|
|
|
|
|
|
|
///
|
|
|
|
/// [Error.FileInaccessible] is a generic catch-all for a [FileAccess] reference no longer
|
|
|
|
/// pointing to a file or the file becomming invalid for whatever reason.
|
|
|
|
///
|
|
|
|
pub const Error = error {
|
|
|
|
FileInaccessible,
|
|
|
|
};
|
|
|
|
|
|
|
|
///
|
|
|
|
/// Close the file referenced by `file_access` on the main thread, invalidating the reference to
|
|
|
|
/// it and releasing any associated resources.
|
|
|
|
///
|
|
|
|
/// Freeing an invalid `file_access` has no effect on the file and logs a warning over the
|
|
|
|
/// wasted effort.
|
|
|
|
///
|
2022-10-19 00:02:23 +01:00
|
|
|
pub fn close(file_access: FileAccess) void {
|
2022-10-17 10:34:04 +01:00
|
|
|
return file_access.implementation.close(file_access.context);
|
|
|
|
}
|
|
|
|
|
|
|
|
///
|
|
|
|
/// Attempts to query the current cursor position for the file referenced by `file_access`.
|
|
|
|
///
|
|
|
|
/// Returns the number of bytes into the file that the cursor is relative to its beginning or a
|
|
|
|
/// [Error] on failure.
|
|
|
|
///
|
2022-10-19 00:02:23 +01:00
|
|
|
pub fn queryCursor(file_access: FileAccess) Error!u64 {
|
2022-10-17 10:34:04 +01:00
|
|
|
return file_access.implementation.queryCursor(file_access.context);
|
|
|
|
}
|
|
|
|
|
|
|
|
///
|
|
|
|
/// Attempts to query the current length for the file referenced by `file_access`.
|
|
|
|
///
|
|
|
|
/// Returns the current length of the file at the time of the operation or a [Error] if the file
|
|
|
|
/// failed to be queried.
|
|
|
|
///
|
2022-10-19 00:02:23 +01:00
|
|
|
pub fn queryLength(file_access: FileAccess) Error!u64 {
|
2022-10-17 10:34:04 +01:00
|
|
|
return file_access.implementation.queryLength(file_access.context);
|
|
|
|
}
|
|
|
|
|
|
|
|
///
|
|
|
|
/// Attempts to read `file_access` from the its current position into `buffer`.
|
|
|
|
///
|
|
|
|
/// Returns the number of bytes that were available to be read, otherwise an [Error] on failure.
|
|
|
|
///
|
2022-10-19 00:02:23 +01:00
|
|
|
pub fn read(file_access: FileAccess, buffer: []u8) Error!usize {
|
2022-10-17 10:34:04 +01:00
|
|
|
return file_access.implementation.read(file_access.context, buffer);
|
|
|
|
}
|
|
|
|
|
|
|
|
///
|
|
|
|
/// Attempts to seek `file_access` from the beginning of the file to `cursor` bytes.
|
|
|
|
///
|
|
|
|
/// Returns [Error] on failure.
|
|
|
|
///
|
2022-10-19 00:02:23 +01:00
|
|
|
pub fn seek(file_access: FileAccess, cursor: u64) Error!void {
|
2022-10-17 10:34:04 +01:00
|
|
|
return file_access.implementation.seek(file_access.context, cursor);
|
|
|
|
}
|
|
|
|
|
|
|
|
///
|
|
|
|
/// Attempts to seek `file_access` to the end of the file.
|
|
|
|
///
|
|
|
|
/// Returns [Error] on failure.
|
|
|
|
///
|
2022-10-19 00:02:23 +01:00
|
|
|
pub fn seekToEnd(file_access: FileAccess) Error!void {
|
2022-10-17 10:34:04 +01:00
|
|
|
return file_access.implementation.seekToEnd(file_access.context);
|
|
|
|
}
|
|
|
|
|
|
|
|
///
|
|
|
|
/// Attempts to seek `file_access` by `offset` from the current file position.
|
|
|
|
///
|
|
|
|
/// Returns [Error] on failure;
|
|
|
|
///
|
2022-10-19 00:02:23 +01:00
|
|
|
pub fn skip(file_access: FileAccess, offset: i64) Error!void {
|
2022-10-17 10:34:04 +01:00
|
|
|
return file_access.implementation.skip(file_access.context, offset);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2022-10-22 23:34:30 +01:00
|
|
|
///
|
|
|
|
/// Closure that captures a reference to readable resources like block devices, memory buffers,
|
|
|
|
/// network sockets, and more.
|
|
|
|
///
|
|
|
|
pub const Reader = meta.Function(@sizeOf(usize), []u8, usize);
|
|
|
|
|
2022-10-17 12:44:32 +01:00
|
|
|
///
|
|
|
|
/// Returns a state machine for lazily computing all `Element` components of a given source input
|
|
|
|
/// that match a delimiting pattern.
|
|
|
|
///
|
|
|
|
pub fn Spliterator(comptime Element: type) type {
|
|
|
|
return struct {
|
|
|
|
source: []const Element,
|
|
|
|
delimiter: []const Element,
|
|
|
|
|
|
|
|
const Self = @This();
|
|
|
|
|
|
|
|
///
|
|
|
|
/// Returns `true` if there is more data to be processed, otherwise `false`.
|
|
|
|
///
|
|
|
|
pub fn hasNext(self: Self) bool {
|
|
|
|
return (self.source.len != 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
///
|
|
|
|
/// Iterates on `self` and returns the next view of [Spliterator.source] that matches
|
|
|
|
/// [Spliterator.delimiter], or `null` if there is no more data to be processed.
|
|
|
|
///
|
|
|
|
pub fn next(self: *Self) ?[]const Element {
|
|
|
|
if (!self.hasNext()) return null;
|
|
|
|
|
|
|
|
if (std.mem.indexOfPos(Element, self.source, 0, self.delimiter)) |index| {
|
|
|
|
defer self.source = self.source[(index + self.delimiter.len) .. self.source.len];
|
|
|
|
|
|
|
|
return self.source[0 .. index];
|
|
|
|
}
|
|
|
|
|
|
|
|
defer self.source = self.source[self.source.len .. self.source.len];
|
|
|
|
|
|
|
|
return self.source;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2022-10-17 14:04:24 +01:00
|
|
|
test "Spliterating text" {
|
2022-10-17 12:44:32 +01:00
|
|
|
const testing = std.testing;
|
|
|
|
|
|
|
|
// Single-character delimiter.
|
|
|
|
{
|
|
|
|
var spliterator = Spliterator(u8){
|
|
|
|
.source = "single.character.separated.hello.world",
|
|
|
|
.delimiter = ".",
|
|
|
|
};
|
|
|
|
|
|
|
|
const components = [_][]const u8{"single", "character", "separated", "hello", "world"};
|
|
|
|
var index = @as(usize, 0);
|
|
|
|
|
|
|
|
while (spliterator.next()) |split| : (index += 1) {
|
|
|
|
try testing.expect(std.mem.eql(u8, split, components[index]));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Multi-character delimiter.
|
|
|
|
{
|
|
|
|
var spliterator = Spliterator(u8){
|
|
|
|
.source = "finding a needle in a needle stack",
|
|
|
|
.delimiter = "needle",
|
|
|
|
};
|
|
|
|
|
|
|
|
const components = [_][]const u8{"finding a ", " in a ", " stack"};
|
|
|
|
var index = @as(usize, 0);
|
|
|
|
|
|
|
|
while (spliterator.next()) |split| : (index += 1) {
|
|
|
|
try testing.expect(std.mem.eql(u8, split, components[index]));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-09 22:55:34 +01:00
|
|
|
///
|
2022-10-22 23:20:33 +01:00
|
|
|
/// Closure that captures a reference to writable resources like block devices, memory buffers,
|
|
|
|
/// network sockets, and more.
|
2022-09-09 22:55:34 +01:00
|
|
|
///
|
2022-10-22 23:20:33 +01:00
|
|
|
pub const Writer = meta.Function(@sizeOf(usize), []const u8, usize);
|
2022-09-09 22:55:34 +01:00
|
|
|
|
2022-10-17 15:48:56 +01:00
|
|
|
///
|
2022-10-23 18:11:02 +01:00
|
|
|
/// Returns `true` if `this` is the same length and contains the same data as `that`, otherwise
|
|
|
|
/// `false`.
|
2022-10-17 15:48:56 +01:00
|
|
|
///
|
2022-10-23 18:11:02 +01:00
|
|
|
pub fn equalsBytes(this: []const u8, that: []const u8) bool {
|
|
|
|
return std.mem.eql(u8, this, that);
|
2022-10-17 15:48:56 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
test "Equivalence of bytes" {
|
|
|
|
const bytes_sequence = &.{69, 42, 0};
|
|
|
|
const testing = std.testing;
|
|
|
|
|
|
|
|
try testing.expect(equalsBytes(bytes_sequence, bytes_sequence));
|
|
|
|
try testing.expect(!equalsBytes(bytes_sequence, &.{69, 42}));
|
|
|
|
}
|
|
|
|
|
|
|
|
///
|
|
|
|
/// Returns a deterministic hash code compiled from each byte in `bytes`.
|
|
|
|
///
|
|
|
|
/// **Note** that this operation has `O(n)` time complexity.
|
|
|
|
///
|
|
|
|
pub fn hashBytes(bytes: []const u8) usize {
|
|
|
|
var hash = @as(usize, 5381);
|
|
|
|
|
|
|
|
for (bytes) |byte| hash = ((hash << 5) + hash) + byte;
|
|
|
|
|
|
|
|
return hash;
|
|
|
|
}
|
|
|
|
|
|
|
|
test "Hashing bytes" {
|
|
|
|
const bytes_sequence = &.{69, 42, 0};
|
|
|
|
const testing = std.testing;
|
|
|
|
|
|
|
|
try testing.expect(hashBytes(bytes_sequence) == hashBytes(bytes_sequence));
|
|
|
|
try testing.expect(hashBytes(bytes_sequence) != hashBytes(&.{69, 42}));
|
|
|
|
}
|
|
|
|
|
2022-09-09 22:55:34 +01:00
|
|
|
///
|
2022-10-22 23:20:33 +01:00
|
|
|
/// Returns a [Writer] that silently consumes all given data without failure and throws it away.
|
2022-09-09 22:55:34 +01:00
|
|
|
///
|
2022-10-22 23:20:33 +01:00
|
|
|
/// This is commonly used for testing or redirected otherwise unwanted output data that has to be
|
2022-09-09 22:55:34 +01:00
|
|
|
/// sent somewhere for whatever reason.
|
|
|
|
///
|
2022-10-22 23:20:33 +01:00
|
|
|
pub fn nullWriter() Writer {
|
|
|
|
return Writer.capture(std.mem.zeroes(usize), struct {
|
|
|
|
fn write(_: usize, buffer: []const u8) usize {
|
|
|
|
return buffer.len;
|
|
|
|
}
|
|
|
|
}.write);
|
|
|
|
}
|
2022-10-02 23:57:41 +01:00
|
|
|
|
2022-10-17 14:00:52 +01:00
|
|
|
test "Null writing" {
|
2022-10-02 23:57:41 +01:00
|
|
|
const testing = std.testing;
|
|
|
|
|
|
|
|
{
|
|
|
|
const sequence = "foo";
|
|
|
|
|
2022-10-22 23:20:33 +01:00
|
|
|
try testing.expectEqual(nullWriter().apply(sequence), sequence.len);
|
2022-10-02 23:57:41 +01:00
|
|
|
}
|
2022-10-20 22:30:48 +01:00
|
|
|
}
|
2022-10-02 23:57:41 +01:00
|
|
|
|
2022-10-20 22:30:48 +01:00
|
|
|
///
|
2022-10-22 23:34:30 +01:00
|
|
|
/// Applies the singular `byte` to `writer`, returning `true` if it was successfully written,
|
2022-10-20 22:30:48 +01:00
|
|
|
/// otherwise `false`.
|
|
|
|
///
|
2022-10-22 23:34:30 +01:00
|
|
|
pub fn writeByte(writer: *Writer, byte: u8) bool {
|
2022-10-22 23:20:33 +01:00
|
|
|
return (writer.apply(std.mem.asBytes(&byte)) != 0);
|
2022-10-02 23:57:41 +01:00
|
|
|
}
|