authorgravatar for hello@nektro.netMeghan Denny <hello@nektro.net> 2024-01-23 10:19:34 -08:00
committergravatar for hello@nektro.netMeghan Denny <hello@nektro.net> 2024-01-23 10:19:34 -08:00
log8ac9d9061a70f1b0e234daa918685cdf49bd5c4a
tree30c36d9af37131b82868715a42216f076de2d8b7
parent14f3f971a731b78c854ac2dfdadbdd4f81ffe796

splite extras into separate files so its easier to add tests


68 files changed, 1082 insertions(+), 825 deletions(-)

src/AnyReader.zig created+123
...@@ -0,0 +1,123 @@
1const std = @import("std");
2const string = []const u8;
3const extras = @import("./lib.zig");
4const assert = std.debug.assert;
5
6pub const AnyReader = struct {
7 readFn: *const fn (*anyopaque, []u8) anyerror!usize,
8 state: *anyopaque,
9
10 pub fn from(reader: anytype) AnyReader {
11 const R = @TypeOf(reader);
12 const ctx = reader.context;
13 const Ctx = @TypeOf(ctx);
14 switch (@typeInfo(Ctx)) {
15 .Pointer => {
16 const S = struct {
17 fn foo(s: *anyopaque, buffer: []u8) anyerror!usize {
18 const r = R{ .context = @ptrCast(@alignCast(s)) };
19 return r.read(buffer);
20 }
21 };
22 return .{
23 .readFn = S.foo,
24 .state = ctx,
25 };
26 },
27 .Struct => switch (R) {
28 std.fs.File.Reader => {
29 const S = struct {
30 fn foo(s: *anyopaque, buffer: []u8) anyerror!usize {
31 const r = R{ .context = .{ .handle = @intCast(@intFromPtr(s)) } };
32 return r.read(buffer);
33 }
34 };
35 return .{
36 .readFn = S.foo,
37 .state = @ptrFromInt(@as(usize, @intCast(ctx.handle))),
38 };
39 },
40 else => @compileError(@typeName(R)),
41 },
42 else => |v| @compileError(@typeName(R) ++ " , " ++ @tagName(v)),
43 }
44 }
45
46 pub fn read(r: AnyReader, buffer: []u8) anyerror!usize {
47 return r.readFn(r.state, buffer);
48 }
49
50 pub fn readByte(self: AnyReader) !u8 {
51 var result: [1]u8 = undefined;
52 const amt_read = try self.read(result[0..]);
53 if (amt_read < 1) return error.EndOfStream;
54 return result[0];
55 }
56
57 pub fn readInt(self: AnyReader, comptime T: type, endian: std.builtin.Endian) !T {
58 const bytes = try self.readBytesNoEof(@as(u16, @intCast((@as(u17, @typeInfo(T).Int.bits) + 7) / 8)));
59 return std.mem.readInt(T, &bytes, endian);
60 }
61
62 pub fn readBytesNoEof(self: AnyReader, comptime num_bytes: usize) ![num_bytes]u8 {
63 var bytes: [num_bytes]u8 = undefined;
64 try self.readNoEof(&bytes);
65 return bytes;
66 }
67
68 pub fn readNoEof(self: AnyReader, buf: []u8) !void {
69 const amt_read = try self.readAll(buf);
70 if (amt_read < buf.len) return error.EndOfStream;
71 }
72
73 pub fn readAll(self: AnyReader, buffer: []u8) !usize {
74 return self.readAtLeast(buffer, buffer.len);
75 }
76
77 pub fn readAtLeast(self: AnyReader, buffer: []u8, len: usize) !usize {
78 assert(len <= buffer.len);
79 var index: usize = 0;
80 while (index < len) {
81 const amt = try self.read(buffer[index..]);
82 if (amt == 0) break;
83 index += amt;
84 }
85 return index;
86 }
87
88 pub fn readAllAlloc(self: AnyReader, allocator: std.mem.Allocator, max_size: usize) ![]u8 {
89 var array_list = std.ArrayList(u8).init(allocator);
90 defer array_list.deinit();
91 try self.readAllArrayList(&array_list, max_size);
92 return try array_list.toOwnedSlice();
93 }
94
95 pub fn readAllArrayList(self: AnyReader, array_list: *std.ArrayList(u8), max_append_size: usize) !void {
96 return self.readAllArrayListAligned(null, array_list, max_append_size);
97 }
98
99 pub fn readAllArrayListAligned(self: AnyReader, comptime alignment: ?u29, array_list: *std.ArrayListAligned(u8, alignment), max_append_size: usize) !void {
100 try array_list.ensureTotalCapacity(@min(max_append_size, 4096));
101 const original_len = array_list.items.len;
102 var start_index: usize = original_len;
103 while (true) {
104 array_list.expandToCapacity();
105 const dest_slice = array_list.items[start_index..];
106 const bytes_read = try self.readAll(dest_slice);
107 start_index += bytes_read;
108
109 if (start_index - original_len > max_append_size) {
110 array_list.shrinkAndFree(original_len + max_append_size);
111 return error.StreamTooLong;
112 }
113
114 if (bytes_read != dest_slice.len) {
115 array_list.shrinkAndFree(start_index);
116 return;
117 }
118
119 // This will trigger ArrayList to expand superlinearly at whatever its growth rate is.
120 try array_list.ensureTotalCapacity(start_index + 1);
121 }
122 }
123};
src/BufIndexer.zig created+23
...@@ -0,0 +1,23 @@
1const std = @import("std");
2const string = []const u8;
3const extras = @import("./lib.zig");
4
5pub fn BufIndexer(comptime T: type, comptime endian: std.builtin.Endian) type {
6 return struct {
7 bytes: [*]const u8,
8 max_len: usize,
9
10 const Self = @This();
11
12 pub fn init(bytes: [*]const u8, max_len: usize) Self {
13 return .{
14 .bytes = bytes,
15 .max_len = max_len,
16 };
17 }
18
19 pub fn at(self: *const Self, idx: usize) T {
20 return extras.indexBufferT(self.bytes, T, endian, idx, self.max_len);
21 }
22 };
23}
src/FieldUnion.zig created+22
...@@ -0,0 +1,22 @@
1const std = @import("std");
2const string = []const u8;
3const extras = @import("./lib.zig");
4
5pub fn FieldUnion(comptime T: type) type {
6 const infos = std.meta.fields(T);
7
8 var fields: [infos.len]std.builtin.Type.UnionField = undefined;
9 inline for (infos, 0..) |field, i| {
10 fields[i] = .{
11 .name = field.name,
12 .type = field.type,
13 .alignment = field.alignment,
14 };
15 }
16 return @Type(std.builtin.Type{ .Union = .{
17 .layout = .Auto,
18 .tag_type = std.meta.FieldEnum(T),
19 .fields = &fields,
20 .decls = &.{},
21 } });
22}
src/FixedMaxBuffer.zig created+48
...@@ -0,0 +1,48 @@
1const std = @import("std");
2const string = []const u8;
3const extras = @import("./lib.zig");
4const assert = std.debug.assert;
5
6pub fn FixedMaxBuffer(comptime max_len: usize) type {
7 return struct {
8 buf: [max_len]u8,
9 len: usize,
10 pos: usize,
11
12 const Self = @This();
13 pub const Reader = std.io.Reader(*Self, error{}, read);
14
15 pub fn init(r: anytype, runtime_len: usize) !Self {
16 var fmr = Self{
17 .buf = undefined,
18 .len = runtime_len,
19 .pos = 0,
20 };
21 _ = try r.readAll(fmr.buf[0..runtime_len]);
22 return fmr;
23 }
24
25 pub fn reader(self: *Self) Reader {
26 return .{ .context = self };
27 }
28
29 fn read(self: *Self, dest: []u8) error{}!usize {
30 const buf = self.buf[0..self.len];
31 const size = @min(dest.len, buf.len - self.pos);
32 const end = self.pos + size;
33 std.mem.copy(u8, dest[0..size], buf[self.pos..end]);
34 self.pos = end;
35 return size;
36 }
37
38 pub fn readLen(self: *Self, len: usize) []const u8 {
39 assert(self.pos + len <= self.len);
40 defer self.pos += len;
41 return self.buf[self.pos..][0..len];
42 }
43
44 pub fn atEnd(self: *const Self) bool {
45 return self.pos == self.len;
46 }
47 };
48}
src/LoggingReader.zig created+30
...@@ -0,0 +1,30 @@
1const std = @import("std");
2const string = []const u8;
3const extras = @import("./lib.zig");
4
5pub fn LoggingReader(comptime T: type, comptime scope: @Type(.EnumLiteral)) type {
6 return struct {
7 child_stream: T,
8
9 pub const Error = T.Error;
10 pub const Reader = std.io.Reader(Self, Error, read);
11
12 const Self = @This();
13
14 pub fn init(child_stream: T) Self {
15 return .{
16 .child_stream = child_stream,
17 };
18 }
19
20 pub fn reader(self: Self) Reader {
21 return .{ .context = self };
22 }
23
24 fn read(self: Self, dest: []u8) Error!usize {
25 const n = try self.child_stream.read(dest);
26 std.log.scoped(scope).debug("{s}", .{dest[0..n]});
27 return n;
28 }
29 };
30}
src/LoggingWriter.zig created+29
...@@ -0,0 +1,29 @@
1const std = @import("std");
2const string = []const u8;
3const extras = @import("./lib.zig");
4
5pub fn LoggingWriter(comptime T: type, comptime scope: @Type(.EnumLiteral)) type {
6 return struct {
7 child_stream: T,
8
9 pub const Error = T.Error;
10 pub const Writer = std.io.Writer(Self, Error, write);
11
12 const Self = @This();
13
14 pub fn init(child_stream: T) Self {
15 return .{
16 .child_stream = child_stream,
17 };
18 }
19
20 pub fn writer(self: Self) Writer {
21 return .{ .context = self };
22 }
23
24 fn write(self: Self, bytes: []const u8) Error!usize {
25 std.log.scoped(scope).debug("{s}", .{bytes});
26 return self.child_stream.write(bytes);
27 }
28 };
29}
src/OneBiggerInt.zig created+9
...@@ -0,0 +1,9 @@
1const std = @import("std");
2const string = []const u8;
3const extras = @import("./lib.zig");
4
5pub fn OneBiggerInt(comptime T: type) type {
6 var info = @typeInfo(T);
7 info.Int.bits += 1;
8 return @Type(info);
9}
src/Partial.zig created+24
...@@ -0,0 +1,24 @@
1const std = @import("std");
2const string = []const u8;
3const extras = @import("./lib.zig");
4
5pub fn Partial(comptime T: type) type {
6 const fields_before = std.meta.fields(T);
7 var fields_after: [fields_before.len]std.builtin.Type.StructField = undefined;
8 inline for (fields_before, 0..) |item, i| {
9 fields_after[i] = std.builtin.Type.StructField{
10 .name = item.name,
11 .type = ?item.type,
12 .default_value = &@as(?item.type, null),
13 .is_comptime = false,
14 .alignment = @alignOf(?item.type),
15 };
16 }
17 return @Type(@unionInit(std.builtin.Type, "Struct", .{
18 .layout = .Auto,
19 .backing_integer = null,
20 .fields = &fields_after,
21 .decls = &.{},
22 .is_tuple = false,
23 }));
24}
src/ReverseFields.zig created+14
...@@ -0,0 +1,14 @@
1const std = @import("std");
2const string = []const u8;
3const extras = @import("./lib.zig");
4
5pub fn ReverseFields(comptime T: type) type {
6 var info = @typeInfo(T).Struct;
7 const len = info.fields.len;
8 var fields: [len]std.builtin.Type.StructField = undefined;
9 for (0..len) |i| {
10 fields[i] = info.fields[len - 1 - i];
11 }
12 info.fields = &fields;
13 return @Type(.{ .Struct = info });
14}
src/RingBuffer.zig created+22
...@@ -0,0 +1,22 @@
1const std = @import("std");
2const string = []const u8;
3const extras = @import("./lib.zig");
4
5pub fn RingBuffer(comptime T: type, comptime capacity: usize) type {
6 return struct {
7 items: [capacity]T = undefined,
8 len: usize = 0,
9 comptime capacity: usize = capacity,
10
11 const Self = @This();
12
13 pub fn append(self: *Self, new_item: T) void {
14 if (self.len == self.capacity) {
15 for (1..self.len) |i| self.items[i - 1] = self.items[i];
16 self.len -= 1;
17 }
18 self.items[self.len] = new_item;
19 self.len += 1;
20 }
21 };
22}
src/StringerJsonStringifyMixin.zig created+18
...@@ -0,0 +1,18 @@
1const std = @import("std");
2const string = []const u8;
3const extras = @import("./lib.zig");
4
5pub fn StringerJsonStringifyMixin(comptime S: type) type {
6 return struct {
7 pub fn jsonStringify(self: S, options: std.json.StringifyOptions, out_stream: anytype) !void {
8 var buf: [1024]u8 = undefined;
9 var fba = std.heap.FixedBufferAllocator.init(&buf);
10 const alloc = fba.allocator();
11 var list = std.ArrayList(u8).init(alloc);
12 errdefer list.deinit();
13 const writer = list.writer();
14 try writer.writeAll(try self.toString(alloc));
15 try std.json.stringify(list.toOwnedSlice(), options, out_stream);
16 }
17 };
18}
src/TagNameJsonStringifyMixin.zig created+11
...@@ -0,0 +1,11 @@
1const std = @import("std");
2const string = []const u8;
3const extras = @import("./lib.zig");
4
5pub fn TagNameJsonStringifyMixin(comptime S: type) type {
6 return struct {
7 pub fn jsonStringify(self: S, options: std.json.StringifyOptions, out_stream: anytype) !void {
8 try std.json.stringify(@tagName(self), options, out_stream);
9 }
10 };
11}
src/addSentinel.zig created+11
...@@ -0,0 +1,11 @@
1const std = @import("std");
2const string = []const u8;
3const extras = @import("./lib.zig");
4
5pub fn addSentinel(alloc: std.mem.Allocator, comptime T: type, input: []const T, comptime sentinel: T) ![:sentinel]const T {
6 var list = try std.ArrayList(T).initCapacity(alloc, input.len + 1);
7 try list.appendSlice(input);
8 try list.append(sentinel);
9 const str = list.toOwnedSlice();
10 return str[0 .. str.len - 1 :sentinel];
11}
src/asciiUpper.zig created+11
...@@ -0,0 +1,11 @@
1const std = @import("std");
2const string = []const u8;
3const extras = @import("./lib.zig");
4
5pub fn asciiUpper(alloc: std.mem.Allocator, input: string) ![]u8 {
6 var buf = try alloc.dupe(u8, input);
7 for (0..buf.len) |i| {
8 buf[i] = std.ascii.toUpper(buf[i]);
9 }
10 return buf;
11}
src/base64DecodeAlloc.zig created+10
...@@ -0,0 +1,10 @@
1const std = @import("std");
2const string = []const u8;
3const extras = @import("./lib.zig");
4
5pub fn base64DecodeAlloc(alloc: std.mem.Allocator, input: string) !string {
6 const base64 = std.base64.standard.Decoder;
7 var buf = try alloc.alloc(u8, try base64.calcSizeForSlice(input));
8 try base64.decode(buf, input);
9 return buf;
10}
src/base64EncodeAlloc.zig created+9
...@@ -0,0 +1,9 @@
1const std = @import("std");
2const string = []const u8;
3const extras = @import("./lib.zig");
4
5pub fn base64EncodeAlloc(alloc: std.mem.Allocator, input: string) !string {
6 const base64 = std.base64.standard.Encoder;
7 var buf = try alloc.alloc(u8, base64.calcSize(input.len));
8 return base64.encode(buf, input);
9}
src/coalescePartial.zig created+11
...@@ -0,0 +1,11 @@
1const std = @import("std");
2const string = []const u8;
3const extras = @import("./lib.zig");
4
5pub fn coalescePartial(comptime T: type, into: T, from: extras.Partial(T)) T {
6 var temp = into;
7 inline for (comptime std.meta.fieldNames(T)) |name| {
8 if (@field(from, name)) |val| @field(temp, name) = val;
9 }
10 return temp;
11}
src/containsAggregate.zig created+12
...@@ -0,0 +1,12 @@
1const std = @import("std");
2const string = []const u8;
3const extras = @import("./lib.zig");
4
5pub fn containsAggregate(comptime T: type, haystack: []const T, needle: T) bool {
6 for (haystack) |item| {
7 if (T.eql(item, needle)) {
8 return true;
9 }
10 }
11 return false;
12}
src/containsString.zig created+12
...@@ -0,0 +1,12 @@
1const std = @import("std");
2const string = []const u8;
3const extras = @import("./lib.zig");
4
5pub fn containsString(haystack: []const string, needle: string) bool {
6 for (haystack) |item| {
7 if (std.mem.eql(u8, item, needle)) {
8 return true;
9 }
10 }
11 return false;
12}
src/countScalar.zig created+14
...@@ -0,0 +1,14 @@
1const std = @import("std");
2const string = []const u8;
3const extras = @import("./lib.zig");
4
5pub fn countScalar(comptime T: type, haystack: []const T, needle: T) usize {
6 var found: usize = 0;
7
8 for (haystack) |item| {
9 if (item == needle) {
10 found += 1;
11 }
12 }
13 return found;
14}
src/dirSize.zig created+15
...@@ -0,0 +1,15 @@
1const std = @import("std");
2const string = []const u8;
3const extras = @import("./lib.zig");
4
5pub fn dirSize(alloc: std.mem.Allocator, dir: std.fs.IterableDir) !u64 {
6 var res: u64 = 0;
7
8 var walk = try dir.walk(alloc);
9 defer walk.deinit();
10 while (try walk.next()) |entry| {
11 if (entry.kind != .File) continue;
12 res += try extras.fileSize(dir.dir, entry.path);
13 }
14 return res;
15}
src/doesFileExist.zig created+13
...@@ -0,0 +1,13 @@
1const std = @import("std");
2const string = []const u8;
3const extras = @import("./lib.zig");
4
5pub fn doesFileExist(dir: ?std.fs.Dir, fpath: []const u8) !bool {
6 const file = (dir orelse std.fs.cwd()).openFile(fpath, .{}) catch |e| switch (e) {
7 error.FileNotFound => return false,
8 error.IsDir => return true,
9 else => return e,
10 };
11 defer file.close();
12 return true;
13}
src/doesFolderExist.zig created+17
...@@ -0,0 +1,17 @@
1const std = @import("std");
2const string = []const u8;
3const extras = @import("./lib.zig");
4
5pub fn doesFolderExist(dir: ?std.fs.Dir, fpath: []const u8) !bool {
6 const file = (dir orelse std.fs.cwd()).openFile(fpath, .{}) catch |e| switch (e) {
7 error.FileNotFound => return false,
8 error.IsDir => return true,
9 else => return e,
10 };
11 defer file.close();
12 const s = try file.stat();
13 if (s.kind != .directory) {
14 return false;
15 }
16 return true;
17}
src/ensureFieldSubset.zig created+9
...@@ -0,0 +1,9 @@
1const std = @import("std");
2const string = []const u8;
3const extras = @import("./lib.zig");
4
5pub fn ensureFieldSubset(comptime L: type, comptime R: type) void {
6 for (std.meta.fields(L)) |item| {
7 if (!@hasField(R, item.name)) @compileError(std.fmt.comptimePrint("{s} is missing the {s} field from {s}", .{ R, item.name, L }));
8 }
9}
src/fileList.zig created+16
...@@ -0,0 +1,16 @@
1const std = @import("std");
2const string = []const u8;
3const extras = @import("./lib.zig");
4
5pub fn fileList(alloc: std.mem.Allocator, dir: std.fs.IterableDir) ![]string {
6 var list = std.ArrayList(string).init(alloc);
7 defer list.deinit();
8
9 var walk = try dir.walk(alloc);
10 defer walk.deinit();
11 while (try walk.next()) |entry| {
12 if (entry.kind != .file) continue;
13 try list.append(try alloc.dupe(u8, entry.path));
14 }
15 return list.toOwnedSlice();
16}
src/fileSize.zig created+10
...@@ -0,0 +1,10 @@
1const std = @import("std");
2const string = []const u8;
3const extras = @import("./lib.zig");
4
5pub fn fileSize(dir: std.fs.Dir, sub_path: string) !u64 {
6 const f = try dir.openFile(sub_path, .{});
7 defer f.close();
8 const s = try f.stat();
9 return s.size;
10}
src/fmtByteCountIEC.zig created+7
...@@ -0,0 +1,7 @@
1const std = @import("std");
2const string = []const u8;
3const extras = @import("./lib.zig");
4
5pub fn fmtByteCountIEC(alloc: std.mem.Allocator, b: u64) !string {
6 return try extras.reduceNumber(alloc, b, 1024, "B", "KMGTPEZYRQ");
7}
src/fmtReplacer.zig created+28
...@@ -0,0 +1,28 @@
1const std = @import("std");
2const string = []const u8;
3const extras = @import("./lib.zig");
4
5pub fn fmtReplacer(bytes: string, from: u8, to: u8) std.fmt.Formatter(formatReplacer) {
6 return .{ .data = .{
7 .bytes = bytes,
8 .from = from,
9 .to = to,
10 } };
11}
12
13fn formatReplacer(
14 self: struct {
15 bytes: string,
16 from: u8,
17 to: u8,
18 },
19 comptime fmt: []const u8,
20 options: std.fmt.FormatOptions,
21 writer: anytype,
22) !void {
23 _ = fmt;
24 _ = options;
25 for (self.bytes) |c| {
26 try writer.writeByte(if (c == self.from) self.to else @intCast(c));
27 }
28}
src/hashBytes.zig created+11
...@@ -0,0 +1,11 @@
1const std = @import("std");
2const string = []const u8;
3const extras = @import("./lib.zig");
4
5pub fn hashBytes(comptime Algo: type, bytes: []const u8) [Algo.digest_length]u8 {
6 var h = Algo.init(.{});
7 var out: [Algo.digest_length]u8 = undefined;
8 h.update(bytes);
9 h.final(&out);
10 return out;
11}
src/hashFile.zig created+16
...@@ -0,0 +1,16 @@
1const std = @import("std");
2const string = []const u8;
3const extras = @import("./lib.zig");
4
5pub fn hashFile(dir: std.fs.Dir, sub_path: string, comptime Algo: type) ![Algo.digest_length * 2]u8 {
6 const file = try dir.openFile(sub_path, .{});
7 defer file.close();
8 var h = Algo.init(.{});
9 var out: [Algo.digest_length]u8 = undefined;
10 try extras.pipe(file.reader(), h.writer());
11 h.final(&out);
12 var res: [Algo.digest_length * 2]u8 = undefined;
13 var fbs = std.io.fixedBufferStream(&res);
14 try std.fmt.format(fbs.writer(), "{x}", .{std.fmt.fmtSliceHexLower(&out)});
15 return res;
16}
src/indexBufferT.zig created+11
...@@ -0,0 +1,11 @@
1const std = @import("std");
2const string = []const u8;
3const extras = @import("./lib.zig");
4
5pub fn indexBufferT(bytes: [*]const u8, comptime T: type, endian: std.builtin.Endian, idx: usize, max_len: usize) T {
6 std.debug.assert(idx < max_len);
7 var fbs = std.io.fixedBufferStream((bytes + (idx * @sizeOf(T)))[0..@sizeOf(T)]);
8 return extras.readType(fbs.reader(), T, endian) catch |err| switch (err) {
9 error.EndOfStream => unreachable, // assert above has been violated
10 };
11}
src/is.zig created+9
...@@ -0,0 +1,9 @@
1const std = @import("std");
2const string = []const u8;
3const extras = @import("./lib.zig");
4
5/// ?A == A fails
6/// ?A == @as(?A, b) works
7pub fn is(a: anytype, b: @TypeOf(a)) bool {
8 return a == b;
9}
src/isArrayOf.zig created+15
...@@ -0,0 +1,15 @@
1const std = @import("std");
2const string = []const u8;
3const extras = @import("./lib.zig");
4
5pub fn isArrayOf(comptime T: type) std.meta.trait.TraitFn {
6 const Closure = struct {
7 pub fn trait(comptime C: type) bool {
8 return switch (@typeInfo(C)) {
9 .Array => |ti| ti.child == T,
10 else => false,
11 };
12 }
13 };
14 return Closure.trait;
15}
src/joinPartial.zig created+11
...@@ -0,0 +1,11 @@
1const std = @import("std");
2const string = []const u8;
3const extras = @import("./lib.zig");
4
5pub fn joinPartial(comptime P: type, a: P, b: P) P {
6 var temp = a;
7 inline for (comptime std.meta.fieldNames(P)) |name| {
8 if (@field(b, name)) |val| @field(temp, name) = val;
9 }
10 return temp;
11}
src/lessThanSlice.zig created+15
...@@ -0,0 +1,15 @@
1const std = @import("std");
2const string = []const u8;
3const extras = @import("./lib.zig");
4
5pub fn lessThanSlice(comptime T: type) fn (void, T, T) bool {
6 return struct {
7 fn f(_: void, lhs: T, rhs: T) bool {
8 const result = for (0..@min(lhs.len, rhs.len)) |i| {
9 if (lhs[i] < rhs[i]) break true;
10 if (lhs[i] > rhs[i]) break false;
11 } else false;
12 return result;
13 }
14 }.f;
15}
src/lib.zig+67-825
...@@ -1,839 +1,81 @@...@@ -1,839 +1,81 @@
1const std = @import("std");1const std = @import("std");
2const string = []const u8;2const string = []const u8;
3const extras = @import("./lib.zig");
3const assert = std.debug.assert;4const assert = std.debug.assert;
4const builtin = @import("builtin");5const builtin = @import("builtin");
56
6pub fn fmtByteCountIEC(alloc: std.mem.Allocator, b: u64) !string {7pub usingnamespace @import("./reduceNumber.zig");
7 return try reduceNumber(alloc, b, 1024, "B", "KMGTPEZYRQ");8pub usingnamespace @import("./fmtByteCountIEC.zig");
8}9pub usingnamespace @import("./addSentinel.zig");
910pub usingnamespace @import("./randomSlice.zig");
10pub fn reduceNumber(alloc: std.mem.Allocator, input: u64, comptime unit: u64, comptime base: string, comptime prefixes: string) !string {11pub usingnamespace @import("./trimPrefix.zig");
11 if (input < unit) {12pub usingnamespace @import("./trimPrefixEnsure.zig");
12 return std.fmt.allocPrint(alloc, "{d} {s}", .{ input, base });13pub usingnamespace @import("./trimSuffix.zig");
13 }14pub usingnamespace @import("./trimSuffixEnsure.zig");
14 var div = unit;15pub usingnamespace @import("./base64EncodeAlloc.zig");
15 var exp: usize = 0;16pub usingnamespace @import("./base64DecodeAlloc.zig");
16 var n = input / unit;17pub usingnamespace @import("./asciiUpper.zig");
17 while (n >= unit) : (n /= unit) {18pub usingnamespace @import("./doesFileExist.zig");
18 div *= unit;19pub usingnamespace @import("./doesFolderExist.zig");
19 exp += 1;20pub usingnamespace @import("./sliceToInt.zig");
20 }21pub usingnamespace @import("./fileList.zig");
21 return try std.fmt.allocPrint(alloc, "{d:.3} {s}{s}", .{ @as(f64, @floatFromInt(input)) / @as(f64, @floatFromInt(div)), prefixes[exp .. exp + 1], base });22pub usingnamespace @import("./fileSize.zig");
22}23pub usingnamespace @import("./dirSize.zig");
2324pub usingnamespace @import("./hashFile.zig");
24pub fn addSentinel(alloc: std.mem.Allocator, comptime T: type, input: []const T, comptime sentinel: T) ![:sentinel]const T {25pub usingnamespace @import("./pipe.zig");
25 var list = try std.ArrayList(T).initCapacity(alloc, input.len + 1);26pub usingnamespace @import("./StringerJsonStringifyMixin.zig");
26 try list.appendSlice(input);27pub usingnamespace @import("./TagNameJsonStringifyMixin.zig");
27 try list.append(sentinel);28pub usingnamespace @import("./countScalar.zig");
28 const str = list.toOwnedSlice();29pub usingnamespace @import("./ptrCast.zig");
29 return str[0 .. str.len - 1 :sentinel];30pub usingnamespace @import("./ptrCastConst.zig");
30}31pub usingnamespace @import("./sortBy.zig");
3132pub usingnamespace @import("./sortBySlice.zig");
32const alphabet = "0123456789abcdefghijklmnopqrstuvwxyz";33pub usingnamespace @import("./lessThanSlice.zig");
3334pub usingnamespace @import("./containsString.zig");
34pub fn randomSlice(alloc: std.mem.Allocator, rand: std.rand.Random, comptime T: type, len: usize) ![]T {35pub usingnamespace @import("./positionalInit.zig");
35 var buf = try alloc.alloc(T, len);36pub usingnamespace @import("./ensureFieldSubset.zig");
36 var i: usize = 0;37pub usingnamespace @import("./fmtReplacer.zig");
37 while (i < len) : (i += 1) {38pub usingnamespace @import("./randomBytes.zig");
38 buf[i] = alphabet[rand.int(u8) % alphabet.len];39pub usingnamespace @import("./readExpected.zig");
39 }40pub usingnamespace @import("./readBytes.zig");
40 return buf;41pub usingnamespace @import("./FixedMaxBuffer.zig");
41}42pub usingnamespace @import("./hashBytes.zig");
4243pub usingnamespace @import("./readType.zig");
43pub fn trimPrefix(in: string, prefix: string) string {44pub usingnamespace @import("./indexBufferT.zig");
44 if (std.mem.startsWith(u8, in, prefix)) {45pub usingnamespace @import("./BufIndexer.zig");
45 return in[prefix.len..];46pub usingnamespace @import("./skipToBoundary.zig");
46 }47pub usingnamespace @import("./is.zig");
47 return in;48pub usingnamespace @import("./safeAdd.zig");
48}49pub usingnamespace @import("./safeAddWrap.zig");
4950pub usingnamespace @import("./readBytesAlloc.zig");
50pub fn trimPrefixEnsure(in: string, prefix: string) ?string {51pub usingnamespace @import("./nullifyS.zig");
51 if (!std.mem.startsWith(u8, in, prefix)) return null;52pub usingnamespace @import("./sliceTo.zig");
52 return in[prefix.len..];53pub usingnamespace @import("./matchesAll.zig");
53}54pub usingnamespace @import("./matchesAny.zig");
5455pub usingnamespace @import("./opslice.zig");
55pub fn trimSuffix(in: string, suffix: string) string {
56 if (std.mem.endsWith(u8, in, suffix)) {
57 return in[0 .. in.len - suffix.len];
58 }
59 return in;
60}
61
62pub fn trimSuffixEnsure(in: string, suffix: string) ?string {
63 if (!std.mem.endsWith(u8, in, suffix)) return null;
64 return in[0 .. in.len - suffix.len];
65}
66
67pub fn base64EncodeAlloc(alloc: std.mem.Allocator, input: string) !string {
68 const base64 = std.base64.standard.Encoder;
69 var buf = try alloc.alloc(u8, base64.calcSize(input.len));
70 return base64.encode(buf, input);
71}
72
73pub fn base64DecodeAlloc(alloc: std.mem.Allocator, input: string) !string {
74 const base64 = std.base64.standard.Decoder;
75 var buf = try alloc.alloc(u8, try base64.calcSizeForSlice(input));
76 try base64.decode(buf, input);
77 return buf;
78}
79
80pub fn asciiUpper(alloc: std.mem.Allocator, input: string) ![]u8 {
81 var buf = try alloc.dupe(u8, input);
82 for (0..buf.len) |i| {
83 buf[i] = std.ascii.toUpper(buf[i]);
84 }
85 return buf;
86}
87
88pub fn doesFolderExist(dir: ?std.fs.Dir, fpath: []const u8) !bool {
89 const file = (dir orelse std.fs.cwd()).openFile(fpath, .{}) catch |e| switch (e) {
90 error.FileNotFound => return false,
91 error.IsDir => return true,
92 else => return e,
93 };
94 defer file.close();
95 const s = try file.stat();
96 if (s.kind != .directory) {
97 return false;
98 }
99 return true;
100}
101
102pub fn doesFileExist(dir: ?std.fs.Dir, fpath: []const u8) !bool {
103 const file = (dir orelse std.fs.cwd()).openFile(fpath, .{}) catch |e| switch (e) {
104 error.FileNotFound => return false,
105 error.IsDir => return true,
106 else => return e,
107 };
108 defer file.close();
109 return true;
110}
111
112pub fn sliceToInt(comptime T: type, comptime E: type, slice: []const E) !T {
113 const a = @typeInfo(T).Int.bits;
114 const b = @typeInfo(E).Int.bits;
115 if (a < b * slice.len) return error.Overflow;
116
117 var n: T = 0;
118 for (slice, 0..) |item, i| {
119 const shift: std.math.Log2Int(T) = @intCast(b * (slice.len - 1 - i));
120 n = n | (@as(T, item) << shift);
121 }
122 return n;
123}
124
125pub fn fileList(alloc: std.mem.Allocator, dir: std.fs.IterableDir) ![]string {
126 var list = std.ArrayList(string).init(alloc);
127 defer list.deinit();
128
129 var walk = try dir.walk(alloc);
130 defer walk.deinit();
131 while (try walk.next()) |entry| {
132 if (entry.kind != .file) continue;
133 try list.append(try alloc.dupe(u8, entry.path));
134 }
135 return list.toOwnedSlice();
136}
137
138pub fn dirSize(alloc: std.mem.Allocator, dir: std.fs.IterableDir) !u64 {
139 var res: u64 = 0;
140
141 var walk = try dir.walk(alloc);
142 defer walk.deinit();
143 while (try walk.next()) |entry| {
144 if (entry.kind != .File) continue;
145 res += try fileSize(dir.dir, entry.path);
146 }
147 return res;
148}
149
150pub fn fileSize(dir: std.fs.Dir, sub_path: string) !u64 {
151 const f = try dir.openFile(sub_path, .{});
152 defer f.close();
153 const s = try f.stat();
154 return s.size;
155}
156
157pub fn hashFile(dir: std.fs.Dir, sub_path: string, comptime Algo: type) ![Algo.digest_length * 2]u8 {
158 const file = try dir.openFile(sub_path, .{});
159 defer file.close();
160 var h = Algo.init(.{});
161 var out: [Algo.digest_length]u8 = undefined;
162 try pipe(file.reader(), h.writer());
163 h.final(&out);
164 var res: [Algo.digest_length * 2]u8 = undefined;
165 var fbs = std.io.fixedBufferStream(&res);
166 try std.fmt.format(fbs.writer(), "{x}", .{std.fmt.fmtSliceHexLower(&out)});
167 return res;
168}
169
170pub fn pipe(reader_from: anytype, writer_to: anytype) !void {
171 var buf: [std.mem.page_size]u8 = undefined;
172 var fifo = std.fifo.LinearFifo(u8, .Slice).init(&buf);
173 defer fifo.deinit();
174 try fifo.pump(reader_from, writer_to);
175}
176
177pub fn StringerJsonStringifyMixin(comptime S: type) type {
178 return struct {
179 pub fn jsonStringify(self: S, options: std.json.StringifyOptions, out_stream: anytype) !void {
180 var buf: [1024]u8 = undefined;
181 var fba = std.heap.FixedBufferAllocator.init(&buf);
182 const alloc = fba.allocator();
183 var list = std.ArrayList(u8).init(alloc);
184 errdefer list.deinit();
185 const writer = list.writer();
186 try writer.writeAll(try self.toString(alloc));
187 try std.json.stringify(list.toOwnedSlice(), options, out_stream);
188 }
189 };
190}
191
192pub fn TagNameJsonStringifyMixin(comptime S: type) type {
193 return struct {
194 pub fn jsonStringify(self: S, options: std.json.StringifyOptions, out_stream: anytype) !void {
195 try std.json.stringify(@tagName(self), options, out_stream);
196 }
197 };
198}
199
200pub fn countScalar(comptime T: type, haystack: []const T, needle: T) usize {
201 var found: usize = 0;
202
203 for (haystack) |item| {
204 if (item == needle) {
205 found += 1;
206 }
207 }
208 return found;
209}
210
211pub fn ptrCast(comptime T: type, ptr: *anyopaque) *T {
212 if (@alignOf(T) == 0) @compileError(@typeName(T));
213 return @ptrCast(@alignCast(ptr));
214}
215
216pub fn ptrCastConst(comptime T: type, ptr: *const anyopaque) *const T {
217 if (@alignOf(T) == 0) @compileError(@typeName(T));
218 return @ptrCast(@alignCast(ptr));
219}
220
221pub fn sortBy(comptime T: type, items: []T, comptime field: std.meta.FieldEnum(T)) void {
222 std.mem.sort(T, items, {}, struct {
223 fn f(_: void, lhs: T, rhs: T) bool {
224 return @field(lhs, @tagName(field)) < @field(rhs, @tagName(field));
225 }
226 }.f);
227}
228
229pub fn sortBySlice(comptime T: type, items: []T, comptime field: std.meta.FieldEnum(T)) void {
230 std.mem.sort(T, items, {}, struct {
231 fn f(_: void, lhs: T, rhs: T) bool {
232 return lessThanSlice(std.meta.FieldType(T, field))({}, @field(lhs, @tagName(field)), @field(rhs, @tagName(field)));
233 }
234 }.f);
235}
236
237pub fn lessThanSlice(comptime T: type) fn (void, T, T) bool {
238 return struct {
239 fn f(_: void, lhs: T, rhs: T) bool {
240 const result = for (0..@min(lhs.len, rhs.len)) |i| {
241 if (lhs[i] < rhs[i]) break true;
242 if (lhs[i] > rhs[i]) break false;
243 } else false;
244 return result;
245 }
246 }.f;
247}
248
249pub fn containsString(haystack: []const string, needle: string) bool {
250 for (haystack) |item| {
251 if (std.mem.eql(u8, item, needle)) {
252 return true;
253 }
254 }
255 return false;
256}
257
258pub fn FieldsTuple(comptime T: type) type {
259 const fields = std.meta.fields(T);
260 var types: [fields.len]type = undefined;
261 for (fields, 0..) |item, i| {
262 types[i] = item.type;
263 }
264 return std.meta.Tuple(&types);
265}
266
267pub fn positionalInit(comptime T: type, args: FieldsTuple(T)) T {
268 var t: T = undefined;
269 inline for (std.meta.fields(T), 0..) |field, i| {
270 @field(t, field.name) = args[i];
271 }
272 return t;
273}
274
275pub fn d2index(d1len: usize, d1: usize, d2: usize) usize {
276 return (d1len * d2) + d1;
277}
278
279pub fn ensureFieldSubset(comptime L: type, comptime R: type) void {
280 for (std.meta.fields(L)) |item| {
281 if (!@hasField(R, item.name)) @compileError(std.fmt.comptimePrint("{s} is missing the {s} field from {s}", .{ R, item.name, L }));
282 }
283}
284
285pub fn fmtReplacer(bytes: string, from: u8, to: u8) std.fmt.Formatter(formatReplacer) {
286 return .{ .data = .{ .bytes = bytes, .from = from, .to = to } };
287}
288
289const ReplacerData = struct { bytes: string, from: u8, to: u8 };
290fn formatReplacer(self: ReplacerData, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
291 _ = fmt;
292 _ = options;
293 for (self.bytes) |c| {
294 try writer.writeByte(if (c == self.from) self.to else @intCast(c));
295 }
296}
297
298pub fn randomBytes(comptime len: usize) [len]u8 {
299 var bytes: [len]u8 = undefined;
300 std.crypto.random.bytes(&bytes);
301 return bytes;
302}
303
304pub fn writeEnumBig(writer: anytype, comptime E: type, value: E) !void {
305 try writer.writeIntBig(@typeInfo(E).Enum.tag_type, @intFromEnum(value));
306}
307
308pub fn readEnumBig(reader: anytype, comptime E: type) !E {
309 return @enumFromInt(try reader.readIntBig(@typeInfo(E).Enum.tag_type));
310}
311
312pub fn readExpected(reader: anytype, expected: []const u8) !bool {
313 for (expected) |item| {
314 const actual = try reader.readByte();
315 if (actual != item) {
316 return false;
317 }
318 }
319 return true;
320}
321
322pub fn readBytes(reader: anytype, comptime len: usize) ![len]u8 {
323 var bytes: [len]u8 = undefined;
324 assert(try reader.readAll(&bytes) == len);
325 return bytes;
326}
327
328pub fn FixedMaxBuffer(comptime max_len: usize) type {
329 return struct {
330 buf: [max_len]u8,
331 len: usize,
332 pos: usize,
333
334 const Self = @This();
335 pub const Reader = std.io.Reader(*Self, error{}, read);
336
337 pub fn init(r: anytype, runtime_len: usize) !Self {
338 var fmr = Self{
339 .buf = undefined,
340 .len = runtime_len,
341 .pos = 0,
342 };
343 _ = try r.readAll(fmr.buf[0..runtime_len]);
344 return fmr;
345 }
346
347 pub fn reader(self: *Self) Reader {
348 return .{ .context = self };
349 }
350
351 fn read(self: *Self, dest: []u8) error{}!usize {
352 const buf = self.buf[0..self.len];
353 const size = @min(dest.len, buf.len - self.pos);
354 const end = self.pos + size;
355 std.mem.copy(u8, dest[0..size], buf[self.pos..end]);
356 self.pos = end;
357 return size;
358 }
359
360 pub fn readLen(self: *Self, len: usize) []const u8 {
361 assert(self.pos + len <= self.len);
362 defer self.pos += len;
363 return self.buf[self.pos..][0..len];
364 }
365
366 pub fn atEnd(self: *const Self) bool {
367 return self.pos == self.len;
368 }
369 };
370}
371
372pub fn hashBytes(comptime Algo: type, bytes: []const u8) [Algo.digest_length]u8 {
373 var h = Algo.init(.{});
374 var out: [Algo.digest_length]u8 = undefined;
375 h.update(bytes);
376 h.final(&out);
377 return out;
378}
379
380pub fn readType(reader: anytype, comptime T: type, endian: std.builtin.Endian) !T {
381 if (T == u8) return reader.readByte(); // single bytes dont have an endianness
382 return switch (@typeInfo(T)) {
383 .Struct => |t| {
384 switch (t.layout) {
385 .Auto, .Extern => {
386 var s: T = undefined;
387 inline for (std.meta.fields(T)) |field| {
388 @field(s, field.name) = try readType(reader, field.type, endian);
389 }
390 return s;
391 },
392 .Packed => return @bitCast(try readType(reader, t.backing_integer.?, endian)),
393 }
394 },
395 .Array => |t| {
396 var s: T = undefined;
397 for (0..t.len) |i| {
398 s[i] = try readType(reader, t.child, endian);
399 }
400 return s;
401 },
402 .Int => try reader.readInt(T, endian),
403 .Enum => |t| @enumFromInt(try readType(reader, t.tag_type, endian)),
404 else => |e| @compileError(@tagName(e)),
405 };
406}
407
408pub fn indexBufferT(bytes: [*]const u8, comptime T: type, endian: std.builtin.Endian, idx: usize, max_len: usize) T {
409 std.debug.assert(idx < max_len);
410 var fbs = std.io.fixedBufferStream((bytes + (idx * @sizeOf(T)))[0..@sizeOf(T)]);
411 return readType(fbs.reader(), T, endian) catch |err| switch (err) {
412 error.EndOfStream => unreachable, // assert above has been violated
413 };
414}
415
416pub fn BufIndexer(comptime T: type, comptime endian: std.builtin.Endian) type {
417 return struct {
418 bytes: [*]const u8,
419 max_len: usize,
420
421 const Self = @This();
422
423 pub fn init(bytes: [*]const u8, max_len: usize) Self {
424 return .{
425 .bytes = bytes,
426 .max_len = max_len,
427 };
428 }
429
430 pub fn at(self: *const Self, idx: usize) T {
431 return indexBufferT(self.bytes, T, endian, idx, self.max_len);
432 }
433 };
434}
435
436pub fn skipToBoundary(pos: u64, boundary: u64, reader: anytype) !void {
437 // const gdiff = counter.bytes_read % 4;
438 // for (range(if (gdiff > 0) 4 - gdiff else 0)) |_| {
439 const a = pos;
440 const b = boundary;
441 try reader.skipBytes(((a + (b - 1)) & ~(b - 1)) - a, .{});
442}
443
444/// ?A == A fails
445/// ?A == @as(?A, b) works
446pub fn is(a: anytype, b: @TypeOf(a)) bool {
447 return a == b;
448}
449
450/// Allows u32 + i16 to work
451pub fn safeAdd(a: anytype, b: anytype) @TypeOf(a) {
452 if (b >= 0) {
453 return a + @as(@TypeOf(a), @intCast(b));
454 }
455 return a - @as(@TypeOf(a), @intCast(-@as(OneBiggerInt(@TypeOf(b)), b)));
456}
457
458/// Allows u32 + i16 to work
459pub fn safeAddWrap(a: anytype, b: anytype) @TypeOf(a) {
460 if (b >= 0) {
461 return a +% @as(@TypeOf(a), @intCast(b));
462 }
463 return a -% @as(@TypeOf(a), @intCast(-@as(OneBiggerInt(@TypeOf(b)), b)));
464}
465
466pub fn readBytesAlloc(reader: anytype, alloc: std.mem.Allocator, len: usize) ![]u8 {
467 var list = std.ArrayListUnmanaged(u8){};
468 try list.ensureTotalCapacityPrecise(alloc, len);
469 errdefer list.deinit(alloc);
470 list.appendNTimesAssumeCapacity(0, len);
471 try reader.readNoEof(list.items[0..len]);
472 return list.items;
473}
474
475pub fn readFile(dir: std.fs.Dir, sub_path: string, alloc: std.mem.Allocator) !string {
476 _ = dir;
477 _ = sub_path;
478 _ = alloc;
479 @compileError("use std.fs.Dir.readFileAlloc instead");
480}
481
482pub fn nullifyS(s: ?string) ?string {
483 if (s == null) return null;
484 if (s.?.len == 0) return null;
485 return s.?;
486}
487
488pub fn sliceTo(comptime T: type, haystack: []const T, needle: T) []const T {
489 if (std.mem.indexOfScalar(T, haystack, needle)) |index| {
490 return haystack[0..index];
491 }
492 return haystack;
493}
494
495pub fn matchesAll(comptime T: type, haystack: []const T, comptime needle: fn (T) bool) bool {
496 for (haystack) |c| {
497 if (!needle(c)) {
498 return false;
499 }
500 }
501 return true;
502}
503
504pub fn matchesAny(comptime T: type, haystack: []const T, comptime needle: fn (T) bool) bool {
505 for (haystack) |c| {
506 if (needle(c)) {
507 return true;
508 }
509 }
510 return false;
511}
512
513pub fn opslice(slice: anytype, index: usize) ?std.meta.Child(@TypeOf(slice)) {
514 if (slice.len <= index) return null;
515 return slice[index];
516}
51756
518pub fn assertLog(ok: bool, comptime message: string, args: anytype) void {57pub fn assertLog(ok: bool, comptime message: string, args: anytype) void {
519 if (!ok) std.log.err("assertion failure: " ++ message, args);58 if (!ok) std.log.err("assertion failure: " ++ message, args);
520 if (!ok) unreachable; // assertion failure59 if (!ok) unreachable; // assertion failure
521}60}
52261
523pub fn parse_json(alloc: std.mem.Allocator, input: string) !std.json.Parsed(std.json.Value) {62pub usingnamespace @import("./parse_json.zig");
524 return std.json.parseFromSlice(std.json.Value, alloc, input, .{});63pub usingnamespace @import("./isArrayOf.zig");
525}64pub usingnamespace @import("./parse_int.zig");
52665pub usingnamespace @import("./parse_bool.zig");
527pub fn isArrayOf(comptime T: type) std.meta.trait.TraitFn {66pub usingnamespace @import("./to_hex.zig");
528 const Closure = struct {67pub usingnamespace @import("./FieldUnion.zig");
529 pub fn trait(comptime C: type) bool {68pub usingnamespace @import("./LoggingReader.zig");
530 return switch (@typeInfo(C)) {69pub usingnamespace @import("./LoggingWriter.zig");
531 .Array => |ti| ti.child == T,70pub usingnamespace @import("./Partial.zig");
532 else => false,71pub usingnamespace @import("./coalescePartial.zig");
533 };72pub usingnamespace @import("./OneBiggerInt.zig");
534 }73pub usingnamespace @import("./ReverseFields.zig");
535 };74pub usingnamespace @import("./stringToEnum.zig");
536 return Closure.trait;75pub usingnamespace @import("./containsAggregate.zig");
537}76pub usingnamespace @import("./AnyReader.zig");
53877pub usingnamespace @import("./sum.zig");
539pub fn parse_int(comptime T: type, s: ?string, b: u8, d: T) T {78pub usingnamespace @import("./RingBuffer.zig");
540 if (s == null) return d;
541 return std.fmt.parseInt(T, s.?, b) catch d;
542}
543
544pub fn parse_bool(s: ?string) bool {
545 return parse_int(u1, s, 10, 0) > 0;
546}
547
548pub fn to_hex(array: anytype) [array.len * 2]u8 {
549 var res: [array.len * 2]u8 = undefined;
550 var fbs = std.io.fixedBufferStream(&res);
551 std.fmt.format(fbs.writer(), "{x}", .{std.fmt.fmtSliceHexLower(&array)}) catch unreachable;
552 return res;
553}
554
555pub fn FieldUnion(comptime T: type) type {
556 const infos = std.meta.fields(T);
557
558 var fields: [infos.len]std.builtin.Type.UnionField = undefined;
559 inline for (infos, 0..) |field, i| {
560 fields[i] = .{
561 .name = field.name,
562 .type = field.type,
563 .alignment = field.alignment,
564 };
565 }
566 return @Type(std.builtin.Type{ .Union = .{
567 .layout = .Auto,
568 .tag_type = std.meta.FieldEnum(T),
569 .fields = &fields,
570 .decls = &.{},
571 } });
572}
573
574pub fn LoggingReader(comptime T: type, comptime scope: @Type(.EnumLiteral)) type {
575 return struct {
576 child_stream: T,
577
578 pub const Error = T.Error;
579 pub const Reader = std.io.Reader(Self, Error, read);
580
581 const Self = @This();
582
583 pub fn init(child_stream: T) Self {
584 return .{
585 .child_stream = child_stream,
586 };
587 }
588
589 pub fn reader(self: Self) Reader {
590 return .{ .context = self };
591 }
592
593 fn read(self: Self, dest: []u8) Error!usize {
594 const n = try self.child_stream.read(dest);
595 std.log.scoped(scope).debug("{s}", .{dest[0..n]});
596 return n;
597 }
598 };
599}
600
601pub fn LoggingWriter(comptime T: type, comptime scope: @Type(.EnumLiteral)) type {
602 return struct {
603 child_stream: T,
604
605 pub const Error = T.Error;
606 pub const Writer = std.io.Writer(Self, Error, write);
607
608 const Self = @This();
609
610 pub fn init(child_stream: T) Self {
611 return .{
612 .child_stream = child_stream,
613 };
614 }
615
616 pub fn writer(self: Self) Writer {
617 return .{ .context = self };
618 }
619
620 fn write(self: Self, bytes: []const u8) Error!usize {
621 std.log.scoped(scope).debug("{s}", .{bytes});
622 return self.child_stream.write(bytes);
623 }
624 };
625}
626
627pub fn Partial(comptime T: type) type {
628 const fields_before = std.meta.fields(T);
629 var fields_after: [fields_before.len]std.builtin.Type.StructField = undefined;
630 inline for (fields_before, 0..) |item, i| {
631 fields_after[i] = std.builtin.Type.StructField{
632 .name = item.name,
633 .type = ?item.type,
634 .default_value = &@as(?item.type, null),
635 .is_comptime = false,
636 .alignment = @alignOf(?item.type),
637 };
638 }
639 return @Type(@unionInit(std.builtin.Type, "Struct", .{
640 .layout = .Auto,
641 .backing_integer = null,
642 .fields = &fields_after,
643 .decls = &.{},
644 .is_tuple = false,
645 }));
646}
647
648pub fn coalescePartial(comptime T: type, into: T, from: Partial(T)) T {
649 var temp = into;
650 inline for (comptime std.meta.fieldNames(T)) |name| {
651 if (@field(from, name)) |val| @field(temp, name) = val;
652 }
653 return temp;
654}
655
656pub fn joinPartial(comptime P: type, a: P, b: P) P {
657 var temp = a;
658 inline for (comptime std.meta.fieldNames(P)) |name| {
659 if (@field(b, name)) |val| @field(temp, name) = val;
660 }
661 return temp;
662}
663
664pub fn OneBiggerInt(comptime T: type) type {
665 var info = @typeInfo(T);
666 info.Int.bits += 1;
667 return @Type(info);
668}
669
670pub fn ReverseFields(comptime T: type) type {
671 var info = @typeInfo(T).Struct;
672 const len = info.fields.len;
673 var fields: [len]std.builtin.Type.StructField = undefined;
674 for (0..len) |i| {
675 fields[i] = info.fields[len - 1 - i];
676 }
677 info.fields = &fields;
678 return @Type(.{ .Struct = info });
679}
680
681pub fn stringToEnum(comptime E: type, str: ?string) ?E {
682 return std.meta.stringToEnum(E, str orelse return null);
683}
684
685pub fn containsAggregate(comptime T: type, haystack: []const T, needle: T) bool {
686 for (haystack) |item| {
687 if (T.eql(item, needle)) {
688 return true;
689 }
690 }
691 return false;
692}
693
694pub const AnyReader = struct {
695 readFn: *const fn (*anyopaque, []u8) anyerror!usize,
696 state: *anyopaque,
697
698 pub fn from(reader: anytype) AnyReader {
699 const R = @TypeOf(reader);
700 const ctx = reader.context;
701 const Ctx = @TypeOf(ctx);
702 switch (@typeInfo(Ctx)) {
703 .Pointer => {
704 const S = struct {
705 fn foo(s: *anyopaque, buffer: []u8) anyerror!usize {
706 const r = R{ .context = @ptrCast(@alignCast(s)) };
707 return r.read(buffer);
708 }
709 };
710 return .{
711 .readFn = S.foo,
712 .state = ctx,
713 };
714 },
715 .Struct => switch (R) {
716 std.fs.File.Reader => {
717 const S = struct {
718 fn foo(s: *anyopaque, buffer: []u8) anyerror!usize {
719 const r = R{ .context = .{ .handle = @intCast(@intFromPtr(s)) } };
720 return r.read(buffer);
721 }
722 };
723 return .{
724 .readFn = S.foo,
725 .state = @ptrFromInt(@as(usize, @intCast(ctx.handle))),
726 };
727 },
728 else => @compileError(@typeName(R)),
729 },
730 else => |v| @compileError(@typeName(R) ++ " , " ++ @tagName(v)),
731 }
732 }
733
734 pub fn read(r: AnyReader, buffer: []u8) anyerror!usize {
735 return r.readFn(r.state, buffer);
736 }
737
738 pub fn readByte(self: AnyReader) !u8 {
739 var result: [1]u8 = undefined;
740 const amt_read = try self.read(result[0..]);
741 if (amt_read < 1) return error.EndOfStream;
742 return result[0];
743 }
744
745 pub fn readInt(self: AnyReader, comptime T: type, endian: std.builtin.Endian) !T {
746 const bytes = try self.readBytesNoEof(@as(u16, @intCast((@as(u17, @typeInfo(T).Int.bits) + 7) / 8)));
747 return std.mem.readInt(T, &bytes, endian);
748 }
749
750 pub fn readBytesNoEof(self: AnyReader, comptime num_bytes: usize) ![num_bytes]u8 {
751 var bytes: [num_bytes]u8 = undefined;
752 try self.readNoEof(&bytes);
753 return bytes;
754 }
755
756 pub fn readNoEof(self: AnyReader, buf: []u8) !void {
757 const amt_read = try self.readAll(buf);
758 if (amt_read < buf.len) return error.EndOfStream;
759 }
760
761 pub fn readAll(self: AnyReader, buffer: []u8) !usize {
762 return self.readAtLeast(buffer, buffer.len);
763 }
764
765 pub fn readAtLeast(self: AnyReader, buffer: []u8, len: usize) !usize {
766 assert(len <= buffer.len);
767 var index: usize = 0;
768 while (index < len) {
769 const amt = try self.read(buffer[index..]);
770 if (amt == 0) break;
771 index += amt;
772 }
773 return index;
774 }
775
776 pub fn readAllAlloc(self: AnyReader, allocator: std.mem.Allocator, max_size: usize) ![]u8 {
777 var array_list = std.ArrayList(u8).init(allocator);
778 defer array_list.deinit();
779 try self.readAllArrayList(&array_list, max_size);
780 return try array_list.toOwnedSlice();
781 }
782
783 pub fn readAllArrayList(self: AnyReader, array_list: *std.ArrayList(u8), max_append_size: usize) !void {
784 return self.readAllArrayListAligned(null, array_list, max_append_size);
785 }
786
787 pub fn readAllArrayListAligned(self: AnyReader, comptime alignment: ?u29, array_list: *std.ArrayListAligned(u8, alignment), max_append_size: usize) !void {
788 try array_list.ensureTotalCapacity(@min(max_append_size, 4096));
789 const original_len = array_list.items.len;
790 var start_index: usize = original_len;
791 while (true) {
792 array_list.expandToCapacity();
793 const dest_slice = array_list.items[start_index..];
794 const bytes_read = try self.readAll(dest_slice);
795 start_index += bytes_read;
796
797 if (start_index - original_len > max_append_size) {
798 array_list.shrinkAndFree(original_len + max_append_size);
799 return error.StreamTooLong;
800 }
801
802 if (bytes_read != dest_slice.len) {
803 array_list.shrinkAndFree(start_index);
804 return;
805 }
806
807 // This will trigger ArrayList to expand superlinearly at whatever its growth rate is.
808 try array_list.ensureTotalCapacity(start_index + 1);
809 }
810 }
811};
812
813pub fn sum(comptime T: type, slice: []const T) T {
814 var res: T = 0;
815 for (slice) |item| res += item;
816 return res;
817}
818
819pub fn RingBuffer(comptime T: type, comptime capacity: usize) type {
820 return struct {
821 items: [capacity]T = undefined,
822 len: usize = 0,
823 comptime capacity: usize = capacity,
824
825 const Self = @This();
826
827 pub fn append(self: *Self, new_item: T) void {
828 if (self.len == self.capacity) {
829 for (1..self.len) |i| self.items[i - 1] = self.items[i];
830 self.len -= 1;
831 }
832 self.items[self.len] = new_item;
833 self.len += 1;
834 }
835 };
836}
83779
838pub fn fd_realpath(fd: std.os.fd_t) ![std.fs.MAX_PATH_BYTES:0]u8 {80pub fn fd_realpath(fd: std.os.fd_t) ![std.fs.MAX_PATH_BYTES:0]u8 {
839 switch (builtin.os.tag) {81 switch (builtin.os.tag) {
src/matchesAll.zig created+12
...@@ -0,0 +1,12 @@
1const std = @import("std");
2const string = []const u8;
3const extras = @import("./lib.zig");
4
5pub fn matchesAll(comptime T: type, haystack: []const T, comptime needle: fn (T) bool) bool {
6 for (haystack) |c| {
7 if (!needle(c)) {
8 return false;
9 }
10 }
11 return true;
12}
src/matchesAny.zig created+12
...@@ -0,0 +1,12 @@
1const std = @import("std");
2const string = []const u8;
3const extras = @import("./lib.zig");
4
5pub fn matchesAny(comptime T: type, haystack: []const T, comptime needle: fn (T) bool) bool {
6 for (haystack) |c| {
7 if (needle(c)) {
8 return true;
9 }
10 }
11 return false;
12}
src/nullifyS.zig created+9
...@@ -0,0 +1,9 @@
1const std = @import("std");
2const string = []const u8;
3const extras = @import("./lib.zig");
4
5pub fn nullifyS(s: ?string) ?string {
6 if (s == null) return null;
7 if (s.?.len == 0) return null;
8 return s.?;
9}
src/opslice.zig created+8
...@@ -0,0 +1,8 @@
1const std = @import("std");
2const string = []const u8;
3const extras = @import("./lib.zig");
4
5pub fn opslice(slice: anytype, index: usize) ?std.meta.Child(@TypeOf(slice)) {
6 if (slice.len <= index) return null;
7 return slice[index];
8}
src/parse_bool.zig created+7
...@@ -0,0 +1,7 @@
1const std = @import("std");
2const string = []const u8;
3const extras = @import("./lib.zig");
4
5pub fn parse_bool(s: ?string) bool {
6 return extras.parse_int(u1, s, 10, 0) > 0;
7}
src/parse_int.zig created+8
...@@ -0,0 +1,8 @@
1const std = @import("std");
2const string = []const u8;
3const extras = @import("./lib.zig");
4
5pub fn parse_int(comptime T: type, s: ?string, b: u8, d: T) T {
6 if (s == null) return d;
7 return std.fmt.parseInt(T, s.?, b) catch d;
8}
src/parse_json.zig created+7
...@@ -0,0 +1,7 @@
1const std = @import("std");
2const string = []const u8;
3const extras = @import("./lib.zig");
4
5pub fn parse_json(alloc: std.mem.Allocator, input: string) !std.json.Parsed(std.json.Value) {
6 return std.json.parseFromSlice(std.json.Value, alloc, input, .{});
7}
src/pipe.zig created+10
...@@ -0,0 +1,10 @@
1const std = @import("std");
2const string = []const u8;
3const extras = @import("./lib.zig");
4
5pub fn pipe(reader_from: anytype, writer_to: anytype) !void {
6 var buf: [std.mem.page_size]u8 = undefined;
7 var fifo = std.fifo.LinearFifo(u8, .Slice).init(&buf);
8 defer fifo.deinit();
9 try fifo.pump(reader_from, writer_to);
10}
src/positionalInit.zig created+11
...@@ -0,0 +1,11 @@
1const std = @import("std");
2const string = []const u8;
3const extras = @import("./lib.zig");
4
5pub fn positionalInit(comptime T: type, args: std.meta.FieldsTuple(T)) T {
6 var t: T = undefined;
7 inline for (std.meta.fields(T), 0..) |field, i| {
8 @field(t, field.name) = args[i];
9 }
10 return t;
11}
src/ptrCast.zig created+8
...@@ -0,0 +1,8 @@
1const std = @import("std");
2const string = []const u8;
3const extras = @import("./lib.zig");
4
5pub fn ptrCast(comptime T: type, ptr: *anyopaque) *T {
6 if (@alignOf(T) == 0) @compileError(@typeName(T));
7 return @ptrCast(@alignCast(ptr));
8}
src/ptrCastConst.zig created+8
...@@ -0,0 +1,8 @@
1const std = @import("std");
2const string = []const u8;
3const extras = @import("./lib.zig");
4
5pub fn ptrCastConst(comptime T: type, ptr: *const anyopaque) *const T {
6 if (@alignOf(T) == 0) @compileError(@typeName(T));
7 return @ptrCast(@alignCast(ptr));
8}
src/randomBytes.zig created+9
...@@ -0,0 +1,9 @@
1const std = @import("std");
2const string = []const u8;
3const extras = @import("./lib.zig");
4
5pub fn randomBytes(comptime len: usize) [len]u8 {
6 var bytes: [len]u8 = undefined;
7 std.crypto.random.bytes(&bytes);
8 return bytes;
9}
src/randomSlice.zig created+14
...@@ -0,0 +1,14 @@
1const std = @import("std");
2const string = []const u8;
3const extras = @import("./lib.zig");
4
5const alphabet = "0123456789abcdefghijklmnopqrstuvwxyz";
6
7pub fn randomSlice(alloc: std.mem.Allocator, rand: std.rand.Random, comptime T: type, len: usize) ![]T {
8 var buf = try alloc.alloc(T, len);
9 var i: usize = 0;
10 while (i < len) : (i += 1) {
11 buf[i] = alphabet[rand.int(u8) % alphabet.len];
12 }
13 return buf;
14}
src/readBytes.zig created+10
...@@ -0,0 +1,10 @@
1const std = @import("std");
2const string = []const u8;
3const extras = @import("./lib.zig");
4const assert = std.debug.assert;
5
6pub fn readBytes(reader: anytype, comptime len: usize) ![len]u8 {
7 var bytes: [len]u8 = undefined;
8 assert(try reader.readAll(&bytes) == len);
9 return bytes;
10}
src/readBytesAlloc.zig created+12
...@@ -0,0 +1,12 @@
1const std = @import("std");
2const string = []const u8;
3const extras = @import("./lib.zig");
4
5pub fn readBytesAlloc(reader: anytype, alloc: std.mem.Allocator, len: usize) ![]u8 {
6 var list = std.ArrayListUnmanaged(u8){};
7 try list.ensureTotalCapacityPrecise(alloc, len);
8 errdefer list.deinit(alloc);
9 list.appendNTimesAssumeCapacity(0, len);
10 try reader.readNoEof(list.items[0..len]);
11 return list.items;
12}
src/readExpected.zig created+13
...@@ -0,0 +1,13 @@
1const std = @import("std");
2const string = []const u8;
3const extras = @import("./lib.zig");
4
5pub fn readExpected(reader: anytype, expected: []const u8) !bool {
6 for (expected) |item| {
7 const actual = try reader.readByte();
8 if (actual != item) {
9 return false;
10 }
11 }
12 return true;
13}
src/readType.zig created+31
...@@ -0,0 +1,31 @@
1const std = @import("std");
2const string = []const u8;
3const extras = @import("./lib.zig");
4
5pub fn readType(reader: anytype, comptime T: type, endian: std.builtin.Endian) !T {
6 if (T == u8) return reader.readByte(); // single bytes dont have an endianness
7 return switch (@typeInfo(T)) {
8 .Struct => |t| {
9 switch (t.layout) {
10 .Auto, .Extern => {
11 var s: T = undefined;
12 inline for (std.meta.fields(T)) |field| {
13 @field(s, field.name) = try readType(reader, field.type, endian);
14 }
15 return s;
16 },
17 .Packed => return @bitCast(try readType(reader, t.backing_integer.?, endian)),
18 }
19 },
20 .Array => |t| {
21 var s: T = undefined;
22 for (0..t.len) |i| {
23 s[i] = try readType(reader, t.child, endian);
24 }
25 return s;
26 },
27 .Int => try reader.readInt(T, endian),
28 .Enum => |t| @enumFromInt(try readType(reader, t.tag_type, endian)),
29 else => |e| @compileError(@tagName(e)),
30 };
31}
src/reduceNumber.zig created+17
...@@ -0,0 +1,17 @@
1const std = @import("std");
2const string = []const u8;
3const extras = @import("./lib.zig");
4
5pub fn reduceNumber(alloc: std.mem.Allocator, input: u64, comptime unit: u64, comptime base: string, comptime prefixes: string) !string {
6 if (input < unit) {
7 return std.fmt.allocPrint(alloc, "{d} {s}", .{ input, base });
8 }
9 var div = unit;
10 var exp: usize = 0;
11 var n = input / unit;
12 while (n >= unit) : (n /= unit) {
13 div *= unit;
14 exp += 1;
15 }
16 return try std.fmt.allocPrint(alloc, "{d:.3} {s}{s}", .{ @as(f64, @floatFromInt(input)) / @as(f64, @floatFromInt(div)), prefixes[exp .. exp + 1], base });
17}
src/safeAdd.zig created+11
...@@ -0,0 +1,11 @@
1const std = @import("std");
2const string = []const u8;
3const extras = @import("./lib.zig");
4
5/// Allows u32 + i16 to work
6pub fn safeAdd(a: anytype, b: anytype) @TypeOf(a) {
7 if (b >= 0) {
8 return a + @as(@TypeOf(a), @intCast(b));
9 }
10 return a - @as(@TypeOf(a), @intCast(-@as(extras.OneBiggerInt(@TypeOf(b)), b)));
11}
src/safeAddWrap.zig created+11
...@@ -0,0 +1,11 @@
1const std = @import("std");
2const string = []const u8;
3const extras = @import("./lib.zig");
4
5/// Allows u32 +% i16 to work
6pub fn safeAddWrap(a: anytype, b: anytype) @TypeOf(a) {
7 if (b >= 0) {
8 return a +% @as(@TypeOf(a), @intCast(b));
9 }
10 return a -% @as(@TypeOf(a), @intCast(-@as(extras.OneBiggerInt(@TypeOf(b)), b)));
11}
src/skipToBoundary.zig created+11
...@@ -0,0 +1,11 @@
1const std = @import("std");
2const string = []const u8;
3const extras = @import("./lib.zig");
4
5pub fn skipToBoundary(pos: u64, boundary: u64, reader: anytype) !void {
6 // const gdiff = counter.bytes_read % 4;
7 // for (range(if (gdiff > 0) 4 - gdiff else 0)) |_| {
8 const a = pos;
9 const b = boundary;
10 try reader.skipBytes(((a + (b - 1)) & ~(b - 1)) - a, .{});
11}
src/sliceTo.zig created+10
...@@ -0,0 +1,10 @@
1const std = @import("std");
2const string = []const u8;
3const extras = @import("./lib.zig");
4
5pub fn sliceTo(comptime T: type, haystack: []const T, needle: T) []const T {
6 if (std.mem.indexOfScalar(T, haystack, needle)) |index| {
7 return haystack[0..index];
8 }
9 return haystack;
10}
src/sliceToInt.zig created+16
...@@ -0,0 +1,16 @@
1const std = @import("std");
2const string = []const u8;
3const extras = @import("./lib.zig");
4
5pub fn sliceToInt(comptime T: type, comptime E: type, slice: []const E) !T {
6 const a = @typeInfo(T).Int.bits;
7 const b = @typeInfo(E).Int.bits;
8 if (a < b * slice.len) return error.Overflow;
9
10 var n: T = 0;
11 for (slice, 0..) |item, i| {
12 const shift: std.math.Log2Int(T) = @intCast(b * (slice.len - 1 - i));
13 n = n | (@as(T, item) << shift);
14 }
15 return n;
16}
src/sortBy.zig created+11
...@@ -0,0 +1,11 @@
1const std = @import("std");
2const string = []const u8;
3const extras = @import("./lib.zig");
4
5pub fn sortBy(comptime T: type, items: []T, comptime field: std.meta.FieldEnum(T)) void {
6 std.mem.sort(T, items, {}, struct {
7 fn f(_: void, lhs: T, rhs: T) bool {
8 return @field(lhs, @tagName(field)) < @field(rhs, @tagName(field));
9 }
10 }.f);
11}
src/sortBySlice.zig created+11
...@@ -0,0 +1,11 @@
1const std = @import("std");
2const string = []const u8;
3const extras = @import("./lib.zig");
4
5pub fn sortBySlice(comptime T: type, items: []T, comptime field: std.meta.FieldEnum(T)) void {
6 std.mem.sort(T, items, {}, struct {
7 fn f(_: void, lhs: T, rhs: T) bool {
8 return extras.lessThanSlice(std.meta.FieldType(T, field))({}, @field(lhs, @tagName(field)), @field(rhs, @tagName(field)));
9 }
10 }.f);
11}
src/stringToEnum.zig created+7
...@@ -0,0 +1,7 @@
1const std = @import("std");
2const string = []const u8;
3const extras = @import("./lib.zig");
4
5pub fn stringToEnum(comptime E: type, str: ?string) ?E {
6 return std.meta.stringToEnum(E, str orelse return null);
7}
src/sum.zig created+9
...@@ -0,0 +1,9 @@
1const std = @import("std");
2const string = []const u8;
3const extras = @import("./lib.zig");
4
5pub fn sum(comptime T: type, slice: []const T) T {
6 var res: T = 0;
7 for (slice) |item| res += item;
8 return res;
9}
src/to_hex.zig created+10
...@@ -0,0 +1,10 @@
1const std = @import("std");
2const string = []const u8;
3const extras = @import("./lib.zig");
4
5pub fn to_hex(array: anytype) [array.len * 2]u8 {
6 var res: [array.len * 2]u8 = undefined;
7 var fbs = std.io.fixedBufferStream(&res);
8 std.fmt.format(fbs.writer(), "{x}", .{std.fmt.fmtSliceHexLower(&array)}) catch unreachable;
9 return res;
10}
src/trimPrefix.zig created+10
...@@ -0,0 +1,10 @@
1const std = @import("std");
2const string = []const u8;
3const extras = @import("./lib.zig");
4
5pub fn trimPrefix(in: string, prefix: string) string {
6 if (std.mem.startsWith(u8, in, prefix)) {
7 return in[prefix.len..];
8 }
9 return in;
10}
src/trimPrefixEnsure.zig created+8
...@@ -0,0 +1,8 @@
1const std = @import("std");
2const string = []const u8;
3const extras = @import("./lib.zig");
4
5pub fn trimPrefixEnsure(in: string, prefix: string) ?string {
6 if (!std.mem.startsWith(u8, in, prefix)) return null;
7 return in[prefix.len..];
8}
src/trimSuffix.zig created+10
...@@ -0,0 +1,10 @@
1const std = @import("std");
2const string = []const u8;
3const extras = @import("./lib.zig");
4
5pub fn trimSuffix(in: string, suffix: string) string {
6 if (std.mem.endsWith(u8, in, suffix)) {
7 return in[0 .. in.len - suffix.len];
8 }
9 return in;
10}
src/trimSuffixEnsure.zig created+8
...@@ -0,0 +1,8 @@
1const std = @import("std");
2const string = []const u8;
3const extras = @import("./lib.zig");
4
5pub fn trimSuffixEnsure(in: string, suffix: string) ?string {
6 if (!std.mem.endsWith(u8, in, suffix)) return null;
7 return in[0 .. in.len - suffix.len];
8}