const std = @import("std"); pub const Uuid = @import("./Uuid.zig"); pub const DateTime = @import("./DateTime.zig"); pub const serialize = @import("./serialize.zig"); pub const Deserializer = serialize.Deserializer; pub const DeserializerContext = serialize.DeserializerContext; /// 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 { comptime { if (strs.len == 0) return ""; var size: usize = 0; for (strs) |str| size += prefix.len + str.len + separator.len; size -= separator.len; var buf = std.mem.zeroes([size]u8); // 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; } } /// 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, opt: std.json.StringifyOptions, writer: anytype, ) !void { switch (@typeInfo(@TypeOf(enum_value))) { .Enum => |info| if (!info.is_exhaustive) @compileError("Enum must be exhaustive"), .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))), } return std.fmt.format(writer, "\"{s}\"", .{@tagName(enum_value)}); } /// 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 pub fn deepFree(alloc: ?std.mem.Allocator, val: anytype) void { const T = @TypeOf(val); switch (@typeInfo(T)) { .Pointer => |ptr| switch (ptr.size) { .One => { deepFree(alloc, val.*); alloc.?.destroy(val); }, .Slice => { for (val) |v| deepFree(alloc, v); alloc.?.free(val); }, else => @compileError("Many and C-style pointers not supported by deepfree"), }, .Optional => if (val) |v| deepFree(alloc, v) else {}, .Struct => |struct_info| inline for (struct_info.fields) |field| { const v = @field(val, field.name); const should_free = if (field.default_value) |opaque_ptr| blk: { const aligned = if (field.alignment != 0) @alignCast(field.alignment, opaque_ptr) else opaque_ptr; const ptr = @ptrCast(*const field.field_type, aligned); if (std.meta.eql(v, ptr.*)) break :blk false; break :blk true; } else true; if (should_free) deepFree(alloc, @field(val, field.name)); }, .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 {}, .Array => for (val) |v| deepFree(alloc, v), .Enum, .Int, .Float, .Bool, .Void, .Type => {}, else => @compileError("Type " ++ @typeName(T) ++ " not supported by deepFree"), } } /// 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. pub fn deepClone(alloc: std.mem.Allocator, val: anytype) std.mem.Allocator.Error!@TypeOf(val) { 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; } }, .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; }, .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; } }, .Enum, .Int, .Float, .Bool, .Void, .Type => { result = val; }, else => @compileError("Type " ++ @typeName(T) ++ " not supported"), } return result; } 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)); } pub fn comptimeToCrlf(comptime str: []const u8) []const u8 { comptime { @setEvalBranchQuota(str.len * 10); const size = std.mem.replacementSize(u8, str, "\n", "\r\n"); var buf: [size]u8 = undefined; _ = std.mem.replace(u8, str, "\n", "\r\n", &buf); return &buf; } } pub const PathIter = struct { is_first: bool, iter: std.mem.SplitIterator(u8), pub fn from(path: []const u8) PathIter { return .{ .is_first = true, .iter = std.mem.split(u8, path, "/") }; } pub fn next(self: *PathIter) ?[]const u8 { defer self.is_first = false; while (self.iter.next()) |it| if (it.len != 0) { return it; }; if (self.is_first) return self.iter.rest(); return null; } pub fn first(self: *PathIter) []const u8 { std.debug.assert(self.is_first); return self.next().?; } pub fn rest(self: *PathIter) []const u8 { return self.iter.rest(); } }; test "PathIter" { const testCase = struct { fn case(path: []const u8, segments: []const []const u8) !void { var iter = PathIter.from(path); for (segments) |s| { try std.testing.expectEqualStrings(s, iter.next() orelse return error.TestExpectedEqual); } try std.testing.expect(iter.next() == null); } }.case; try testCase("", &.{""}); try testCase("*", &.{"*"}); try testCase("/", &.{""}); try testCase("/ab/cd", &.{ "ab", "cd" }); try testCase("/ab/cd/", &.{ "ab", "cd" }); try testCase("/ab/cd//", &.{ "ab", "cd" }); try testCase("ab", &.{"ab"}); try testCase("/ab", &.{"ab"}); try testCase("ab/", &.{"ab"}); try testCase("ab//ab//", &.{ "ab", "ab" }); } 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)) { try std.testing.expect(std.meta.isTag(actual, f)); 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)), } } }; test { _ = std.testing.refAllDecls(@This()); }