2022-07-30 05:46:00 +00:00
|
|
|
const std = @import("std");
|
2022-10-09 09:05:01 +00:00
|
|
|
const iters = @import("./iters.zig");
|
2022-07-30 05:46:00 +00:00
|
|
|
|
2022-06-09 05:54:41 +00:00
|
|
|
pub const ciutf8 = @import("./ciutf8.zig");
|
|
|
|
pub const Uuid = @import("./Uuid.zig");
|
2022-07-18 07:03:29 +00:00
|
|
|
pub const DateTime = @import("./DateTime.zig");
|
2022-09-10 22:29:08 +00:00
|
|
|
pub const Url = @import("./Url.zig");
|
2022-10-09 09:05:01 +00:00
|
|
|
pub const PathIter = iters.PathIter;
|
|
|
|
pub const QueryIter = iters.QueryIter;
|
2022-10-12 02:19:34 +00:00
|
|
|
pub const SqlStmtIter = iters.Separator(';');
|
2022-06-09 05:54:41 +00:00
|
|
|
|
2022-10-02 05:18:24 +00:00
|
|
|
/// Joins an array of strings, prefixing every entry with `prefix`,
|
|
|
|
/// and putting `separator` in between each pair
|
|
|
|
pub fn comptimeJoinWithPrefix(
|
|
|
|
comptime separator: []const u8,
|
|
|
|
comptime prefix: []const u8,
|
|
|
|
comptime strs: []const []const u8,
|
|
|
|
) []const u8 {
|
2022-09-29 21:52:01 +00:00
|
|
|
comptime {
|
2022-10-02 05:18:24 +00:00
|
|
|
if (strs.len == 0) return "";
|
|
|
|
|
|
|
|
var size: usize = 0;
|
|
|
|
for (strs) |str| size += prefix.len + str.len + separator.len;
|
|
|
|
size -= separator.len;
|
2022-09-29 21:52:01 +00:00
|
|
|
|
|
|
|
var buf = std.mem.zeroes([size]u8);
|
|
|
|
|
2022-10-02 05:18:24 +00:00
|
|
|
// can't use std.mem.join because of problems with comptime allocation
|
|
|
|
// https://github.com/ziglang/zig/issues/5873#issuecomment-1001778218
|
|
|
|
//var fba = std.heap.FixedBufferAllocator.init(&buf);
|
|
|
|
//return (std.mem.join(fba.allocator(), separator, fields) catch unreachable) ++ " ";
|
|
|
|
|
|
|
|
var buf_idx = 0;
|
|
|
|
for (strs) |str, i| {
|
|
|
|
std.mem.copy(u8, buf[buf_idx..], prefix);
|
|
|
|
buf_idx += prefix.len;
|
|
|
|
std.mem.copy(u8, buf[buf_idx..], str);
|
|
|
|
buf_idx += str.len;
|
|
|
|
|
|
|
|
if (i != strs.len - 1) {
|
|
|
|
std.mem.copy(u8, buf[buf_idx..], separator);
|
|
|
|
buf_idx += separator.len;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return &buf;
|
2022-09-29 21:52:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-02 05:18:24 +00:00
|
|
|
/// Joins an array of strings, putting `separator` in between each pair
|
|
|
|
pub fn comptimeJoin(
|
|
|
|
comptime separator: []const u8,
|
|
|
|
comptime strs: []const []const u8,
|
|
|
|
) []const u8 {
|
|
|
|
return comptimeJoinWithPrefix(separator, "", strs);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Helper function to serialize a runtime enum value as a string inside JSON.
|
|
|
|
/// To use, add
|
|
|
|
/// ```
|
|
|
|
/// pub const jsonStringify = util.jsonSerializeEnumAsString;
|
|
|
|
/// ```
|
|
|
|
/// to your enum type.
|
|
|
|
pub fn jsonSerializeEnumAsString(
|
|
|
|
enum_value: anytype,
|
2022-10-04 02:41:59 +00:00
|
|
|
opt: std.json.StringifyOptions,
|
2022-10-02 05:18:24 +00:00
|
|
|
writer: anytype,
|
|
|
|
) !void {
|
|
|
|
switch (@typeInfo(@TypeOf(enum_value))) {
|
|
|
|
.Enum => |info| if (!info.is_exhaustive) @compileError("Enum must be exhaustive"),
|
2022-10-04 02:41:59 +00:00
|
|
|
.Pointer => |info| if (info.size == .One) {
|
|
|
|
return jsonSerializeEnumAsString(enum_value.*, opt, writer);
|
|
|
|
} else @compileError("Must be enum type or pointer to enum, got " ++ @typeName(@TypeOf(enum_value))),
|
|
|
|
else => @compileError("Must be enum type or pointer to enum, got " ++ @typeName(@TypeOf(enum_value))),
|
2022-10-02 05:18:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return std.fmt.format(writer, "\"{s}\"", .{@tagName(enum_value)});
|
2022-09-08 06:56:29 +00:00
|
|
|
}
|
|
|
|
|
2022-10-02 05:18:24 +00:00
|
|
|
/// Recursively frees a struct/array/slice/etc using the given allocator
|
|
|
|
/// by freeing any slices or pointers inside. Assumes that every pointer-like
|
|
|
|
/// object within points to its own allocation that must be free'd separately.
|
|
|
|
/// Do *not* use on self-referential types or structs that contain duplicate
|
|
|
|
/// slices.
|
|
|
|
/// Meant to be the inverse of `deepClone` below
|
2022-09-11 11:38:46 +00:00
|
|
|
pub fn deepFree(alloc: ?std.mem.Allocator, val: anytype) void {
|
2022-09-10 22:29:22 +00:00
|
|
|
const T = @TypeOf(val);
|
|
|
|
switch (@typeInfo(T)) {
|
|
|
|
.Pointer => |ptr| switch (ptr.size) {
|
2022-10-02 05:18:24 +00:00
|
|
|
.One => {
|
|
|
|
deepFree(alloc, val.*);
|
|
|
|
alloc.?.destroy(val);
|
|
|
|
},
|
2022-09-10 22:29:22 +00:00
|
|
|
.Slice => {
|
|
|
|
for (val) |v| deepFree(alloc, v);
|
2022-09-11 11:38:46 +00:00
|
|
|
alloc.?.free(val);
|
2022-09-10 22:29:22 +00:00
|
|
|
},
|
|
|
|
else => @compileError("Many and C-style pointers not supported by deepfree"),
|
|
|
|
},
|
|
|
|
.Optional => if (val) |v| deepFree(alloc, v) else {},
|
2022-09-15 01:12:07 +00:00
|
|
|
.Struct => |struct_info| inline for (struct_info.fields) |field| deepFree(alloc, @field(val, field.name)),
|
2022-11-15 09:03:25 +00:00
|
|
|
.Union => |union_info| inline for (union_info.fields) |field| {
|
|
|
|
const tag = @field(std.meta.Tag(T), field.name);
|
|
|
|
if (@as(std.meta.Tag(T), val) == tag) {
|
|
|
|
deepFree(alloc, @field(val, field.name));
|
|
|
|
}
|
|
|
|
},
|
|
|
|
.ErrorUnion => if (val) |v| deepFree(alloc, v) else {},
|
2022-09-10 22:29:22 +00:00
|
|
|
.Array => for (val) |v| deepFree(alloc, v),
|
|
|
|
|
2022-09-15 01:12:07 +00:00
|
|
|
.Enum, .Int, .Float, .Bool, .Void, .Type => {},
|
2022-09-10 22:29:22 +00:00
|
|
|
|
|
|
|
else => @compileError("Type " ++ @typeName(T) ++ " not supported by deepFree"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-02 05:18:24 +00:00
|
|
|
/// Clones a struct/array/slice/etc and all its submembers.
|
|
|
|
/// Assumes that there are no self-refrential pointers within and that
|
|
|
|
/// every pointer should be followed.
|
2022-09-11 08:55:20 +00:00
|
|
|
pub fn deepClone(alloc: std.mem.Allocator, val: anytype) !@TypeOf(val) {
|
2022-09-10 22:29:22 +00:00
|
|
|
const T = @TypeOf(val);
|
|
|
|
var result: T = undefined;
|
|
|
|
switch (@typeInfo(T)) {
|
|
|
|
.Pointer => |ptr| switch (ptr.size) {
|
|
|
|
.One => {
|
|
|
|
result = try alloc.create(ptr.child);
|
|
|
|
errdefer alloc.free(result);
|
|
|
|
result.* = try deepClone(alloc, val.*);
|
|
|
|
},
|
|
|
|
.Slice => {
|
|
|
|
const slice = try alloc.alloc(ptr.child, val.len);
|
|
|
|
errdefer alloc.free(slice);
|
|
|
|
|
|
|
|
var count: usize = 0;
|
|
|
|
errdefer for (slice[0..count]) |v| deepFree(alloc, v);
|
|
|
|
|
|
|
|
for (val) |v, i| {
|
|
|
|
slice[i] = try deepClone(alloc, v);
|
|
|
|
count += 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
result = slice;
|
|
|
|
},
|
|
|
|
else => @compileError("Many and C-style pointers not supported"),
|
|
|
|
},
|
|
|
|
.Optional => {
|
|
|
|
result = if (val) |v| try deepClone(alloc, v) else null;
|
|
|
|
},
|
|
|
|
.Struct => {
|
|
|
|
const fields = std.meta.fields(T);
|
|
|
|
var count: usize = 0;
|
|
|
|
errdefer {
|
|
|
|
inline for (fields) |f, i| {
|
|
|
|
if (i < count) deepFree(alloc, @field(result, f.name));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
inline for (fields) |f| {
|
|
|
|
@field(result, f.name) = try deepClone(alloc, @field(val, f.name));
|
|
|
|
count += 1;
|
|
|
|
}
|
|
|
|
},
|
2022-11-27 08:58:56 +00:00
|
|
|
.Union => {
|
|
|
|
inline for (comptime std.meta.fieldNames(T)) |f| {
|
|
|
|
if (std.meta.isTag(val, f)) {
|
|
|
|
return @unionInit(T, f, try deepClone(alloc, @field(val, f)));
|
|
|
|
}
|
|
|
|
} else unreachable;
|
|
|
|
},
|
2022-09-10 22:29:22 +00:00
|
|
|
.Array => {
|
|
|
|
var count: usize = 0;
|
|
|
|
errdefer for (result[0..count]) |v| deepFree(alloc, v);
|
|
|
|
|
|
|
|
for (val) |v, i| {
|
|
|
|
result[i] = try deepClone(alloc, v);
|
|
|
|
count += 1;
|
|
|
|
}
|
|
|
|
},
|
2022-09-15 01:12:07 +00:00
|
|
|
.Enum, .Int, .Float, .Bool, .Void, .Type => {
|
2022-09-10 22:29:22 +00:00
|
|
|
result = val;
|
|
|
|
},
|
|
|
|
else => @compileError("Type " ++ @typeName(T) ++ " not supported"),
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2022-10-08 20:47:54 +00:00
|
|
|
threadlocal var prng: ?std.rand.DefaultPrng = null;
|
|
|
|
|
|
|
|
pub fn getThreadPrng() std.rand.Random {
|
|
|
|
if (prng) |*p| return p.random();
|
|
|
|
|
|
|
|
@panic("Thread PRNG not seeded");
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn seedThreadPrng() !void {
|
|
|
|
@setCold(true);
|
|
|
|
|
|
|
|
var buf: [8]u8 = undefined;
|
|
|
|
try std.os.getrandom(&buf);
|
|
|
|
|
|
|
|
prng = std.rand.DefaultPrng.init(@bitCast(u64, buf));
|
2022-06-09 05:54:41 +00:00
|
|
|
}
|
2022-11-27 08:21:50 +00:00
|
|
|
|
|
|
|
pub const testing = struct {
|
|
|
|
pub fn expectDeepEqual(expected: anytype, actual: @TypeOf(expected)) !void {
|
|
|
|
const T = @TypeOf(expected);
|
|
|
|
switch (@typeInfo(T)) {
|
|
|
|
.Null, .Void => return,
|
|
|
|
.Int, .Float, .Bool, .Enum => try std.testing.expectEqual(expected, actual),
|
|
|
|
.Struct => {
|
|
|
|
inline for (comptime std.meta.fieldNames(T)) |f| {
|
|
|
|
try expectDeepEqual(@field(expected, f), @field(actual, f));
|
|
|
|
}
|
|
|
|
},
|
|
|
|
.Union => {
|
|
|
|
inline for (comptime std.meta.fieldNames(T)) |f| {
|
|
|
|
if (std.meta.isTag(expected, f)) {
|
2022-11-27 09:07:45 +00:00
|
|
|
try std.testing.expect(std.meta.isTag(actual, f));
|
2022-11-27 08:21:50 +00:00
|
|
|
try expectDeepEqual(@field(expected, f), @field(actual, f));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
.Pointer, .Array => {
|
|
|
|
if (comptime std.meta.trait.isIndexable(T)) {
|
|
|
|
try std.testing.expectEqual(expected.len, actual.len);
|
|
|
|
for (expected) |_, i| {
|
|
|
|
try expectDeepEqual(expected[i], actual[i]);
|
|
|
|
}
|
|
|
|
} else if (comptime std.meta.trait.isSingleItemPtr(T)) {
|
|
|
|
try expectDeepEqual(expected.*, actual.*);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
.Optional => {
|
|
|
|
if (expected) |e| {
|
|
|
|
try expectDeepEqual(e, actual orelse return error.TestExpectedEqual);
|
|
|
|
} else {
|
|
|
|
try std.testing.expect(actual == null);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
else => @compileError("Unsupported Type " ++ @typeName(T)),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|