const std = @import("std"); const iters = @import("./iters.zig"); pub const ciutf8 = @import("./ciutf8.zig"); pub const Uuid = @import("./Uuid.zig"); pub const DateTime = @import("./DateTime.zig"); pub const Url = @import("./Url.zig"); pub const PathIter = iters.PathIter; pub const QueryIter = iters.QueryIter; pub const SqlStmtIter = iters.Separator(';'); /// 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| deepFree(alloc, @field(val, field.name)), .Union, .ErrorUnion => @compileError("TODO: Unions not yet supported by deepFree"), .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) !@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; } }, .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)); }