A poolable string builder (aka string buffer) for Zig
0

Configure Feed

Select the types of activity you want to include in your feed.

update for latest zig (writegate)

Karl Seguin (Jul 15, 2025, 8:32 PM +0800) e2e6262a 05a38de6

+86 -54
+4 -4
build.zig
··· 4 4 const target = b.standardTargetOptions(.{}); 5 5 const optimize = b.standardOptimizeOption(.{}); 6 6 7 - _ = b.addModule("buffer", .{ 7 + const buffer = b.addModule("buffer", .{ 8 8 .root_source_file = b.path("src/buffer.zig"), 9 + .target = target, 10 + .optimize = optimize, 9 11 }); 10 12 11 13 const lib_test = b.addTest(.{ 12 - .root_source_file = b.path("src/buffer.zig"), 13 - .target = target, 14 - .optimize = optimize, 14 + .root_module = buffer, 15 15 .test_runner = .{ .path = b.path("test_runner.zig"), .mode = .simple }, 16 16 }); 17 17 const run_test = b.addRunArtifact(lib_test);
+53 -7
src/buffer.zig
··· 218 218 return c; 219 219 } 220 220 221 - pub fn writer(self: *Buffer) Writer.IOWriter { 222 - return .{ .context = Writer.init(self) }; 221 + pub fn writer(self: *Buffer) Writer { 222 + return .init(self); 223 223 } 224 224 225 225 pub const Writer = struct { 226 226 w: *Buffer, 227 + interface: std.io.Writer, 227 228 228 229 pub const Error = Allocator.Error; 229 - pub const IOWriter = std.io.Writer(Writer, error{OutOfMemory}, Writer.write); 230 230 231 231 fn init(w: *Buffer) Writer { 232 - return .{ .w = w }; 232 + return .{ 233 + .w = w, 234 + .interface = .{ 235 + .vtable = &.{ 236 + .drain = drain, 237 + }, 238 + .buffer = &.{}, 239 + }, 240 + }; 233 241 } 234 242 235 - pub fn write(self: Writer, data: []const u8) Allocator.Error!usize { 236 - try self.w.write(data); 237 - return data.len; 243 + pub fn adaptToNewApi(self: Writer) Adapter { 244 + return .{ .new_interface = self.interface }; 238 245 } 246 + 247 + pub fn drain(w: *std.Io.Writer, data: []const []const u8, splat: usize) error{WriteFailed}!usize { 248 + const self: *Writer = @alignCast(@fieldParentPtr("interface", w)); 249 + 250 + var written: usize = 0; 251 + for (data) |d| { 252 + self.w.write(d) catch return error.WriteFailed; 253 + written += d.len; 254 + } 255 + if (splat < 2) { 256 + return written; 257 + } 258 + 259 + const last = data[data.len - 1]; 260 + for (0..splat) |_| { 261 + self.w.write(last) catch return error.WriteFailed; 262 + written += last.len; 263 + } 264 + return written; 265 + } 266 + 267 + // Legacy API 268 + pub fn writeByte(self: Writer, b: u8) !void { 269 + return self.w.writeByte(b); 270 + } 271 + pub fn writeByteNTimes(self: *Writer, b: u8, n: usize) !void { 272 + return self.w.writeByteNTimes(b, n); 273 + } 274 + pub fn print(self: *Writer, comptime fmt: []const u8, args: anytype) !void { 275 + return self.interface.print(fmt, args) catch return error.OutOfMemory; 276 + } 277 + pub fn writeAll(self: Writer, data: []const u8) !void { 278 + return self.w.write(data); 279 + } 280 + 281 + pub const Adapter = struct { 282 + err: ?Error = null, 283 + new_interface: std.Io.Writer, 284 + }; 239 285 }; 240 286 }; 241 287
+1 -1
src/pool.zig
··· 145 145 std.debug.assert(sb.buf[0] == 0); 146 146 147 147 sb.buf[0] = 255; 148 - std.time.sleep(random.uintAtMost(u32, 100000)); 148 + std.Thread.sleep(random.uintAtMost(u32, 100000)); 149 149 sb.buf[0] = 0; 150 150 p.release(sb); 151 151 }
+28 -42
test_runner.zig
··· 1 1 // in your build.zig, you can specify a custom test runner: 2 2 // const tests = b.addTest(.{ 3 - // .target = target, 4 - // .optimize = optimize, 5 - // .test_runner = .{ .path = b.path("test_runner.zig"), .mode = .simple }, // add this line 6 - // .root_source_file = b.path("src/main.zig"), 3 + // .root_module = $MODULE_BEING_TESTED, 4 + // .test_runner = .{ .path = b.path("test_runner.zig"), .mode = .simple }, 7 5 // }); 8 6 9 7 const std = @import("std"); ··· 33 31 var skip: usize = 0; 34 32 var leak: usize = 0; 35 33 36 - const printer = Printer.init(); 37 - printer.fmt("\r\x1b[0K", .{}); // beginning of line and clear to end of line 34 + Printer.fmt("\r\x1b[0K", .{}); // beginning of line and clear to end of line 38 35 39 36 for (builtin.test_functions) |t| { 40 37 if (isSetup(t)) { 41 38 t.func() catch |err| { 42 - printer.status(.fail, "\nsetup \"{s}\" failed: {}\n", .{ t.name, err }); 39 + Printer.status(.fail, "\nsetup \"{s}\" failed: {}\n", .{ t.name, err }); 43 40 return err; 44 41 }; 45 42 } ··· 81 78 82 79 if (std.testing.allocator_instance.deinit() == .leak) { 83 80 leak += 1; 84 - printer.status(.fail, "\n{s}\n\"{s}\" - Memory Leak\n{s}\n", .{ BORDER, friendly_name, BORDER }); 81 + Printer.status(.fail, "\n{s}\n\"{s}\" - Memory Leak\n{s}\n", .{ BORDER, friendly_name, BORDER }); 85 82 } 86 83 87 84 if (result) |_| { ··· 94 91 else => { 95 92 status = .fail; 96 93 fail += 1; 97 - printer.status(.fail, "\n{s}\n\"{s}\" - {s}\n{s}\n", .{ BORDER, friendly_name, @errorName(err), BORDER }); 94 + Printer.status(.fail, "\n{s}\n\"{s}\" - {s}\n{s}\n", .{ BORDER, friendly_name, @errorName(err), BORDER }); 98 95 if (@errorReturnTrace()) |trace| { 99 96 std.debug.dumpStackTrace(trace.*); 100 97 } ··· 106 103 107 104 if (env.verbose) { 108 105 const ms = @as(f64, @floatFromInt(ns_taken)) / 1_000_000.0; 109 - printer.status(status, "{s} ({d:.2}ms)\n", .{ friendly_name, ms }); 106 + Printer.status(status, "{s} ({d:.2}ms)\n", .{ friendly_name, ms }); 110 107 } else { 111 - printer.status(status, ".", .{}); 108 + Printer.status(status, ".", .{}); 112 109 } 113 110 } 114 111 115 112 for (builtin.test_functions) |t| { 116 113 if (isTeardown(t)) { 117 114 t.func() catch |err| { 118 - printer.status(.fail, "\nteardown \"{s}\" failed: {}\n", .{ t.name, err }); 115 + Printer.status(.fail, "\nteardown \"{s}\" failed: {}\n", .{ t.name, err }); 119 116 return err; 120 117 }; 121 118 } ··· 123 120 124 121 const total_tests = pass + fail; 125 122 const status = if (fail == 0) Status.pass else Status.fail; 126 - printer.status(status, "\n{d} of {d} test{s} passed\n", .{ pass, total_tests, if (total_tests != 1) "s" else "" }); 123 + Printer.status(status, "\n{d} of {d} test{s} passed\n", .{ pass, total_tests, if (total_tests != 1) "s" else "" }); 127 124 if (skip > 0) { 128 - printer.status(.skip, "{d} test{s} skipped\n", .{ skip, if (skip != 1) "s" else "" }); 125 + Printer.status(.skip, "{d} test{s} skipped\n", .{ skip, if (skip != 1) "s" else "" }); 129 126 } 130 127 if (leak > 0) { 131 - printer.status(.fail, "{d} test{s} leaked\n", .{ leak, if (leak != 1) "s" else "" }); 128 + Printer.status(.fail, "{d} test{s} leaked\n", .{ leak, if (leak != 1) "s" else "" }); 132 129 } 133 - printer.fmt("\n", .{}); 134 - try slowest.display(printer); 135 - printer.fmt("\n", .{}); 130 + Printer.fmt("\n", .{}); 131 + try slowest.display(); 132 + Printer.fmt("\n", .{}); 136 133 std.posix.exit(if (fail == 0) 0 else 1); 137 134 } 138 135 139 136 const Printer = struct { 140 - out: std.fs.File.Writer, 141 - 142 - fn init() Printer { 143 - return .{ 144 - .out = std.io.getStdErr().writer(), 145 - }; 137 + fn fmt(comptime format: []const u8, args: anytype) void { 138 + std.debug.print(format, args); 146 139 } 147 140 148 - fn fmt(self: Printer, comptime format: []const u8, args: anytype) void { 149 - std.fmt.format(self.out, format, args) catch unreachable; 150 - } 151 - 152 - fn status(self: Printer, s: Status, comptime format: []const u8, args: anytype) void { 153 - const color = switch (s) { 154 - .pass => "\x1b[32m", 155 - .fail => "\x1b[31m", 156 - .skip => "\x1b[33m", 157 - else => "", 158 - }; 159 - const out = self.out; 160 - out.writeAll(color) catch @panic("writeAll failed?!"); 161 - std.fmt.format(out, format, args) catch @panic("std.fmt.format failed?!"); 162 - self.fmt("\x1b[0m", .{}); 141 + fn status(s: Status, comptime format: []const u8, args: anytype) void { 142 + switch (s) { 143 + .pass => std.debug.print("\x1b[32m", .{}), 144 + .fail => std.debug.print("\x1b[31m", .{}), 145 + .skip => std.debug.print("\x1b[33m", .{}), 146 + else => {}, 147 + } 148 + std.debug.print(format ++ "\x1b[0m", args); 163 149 } 164 150 }; 165 151 ··· 229 215 return ns; 230 216 } 231 217 232 - fn display(self: *SlowTracker, printer: Printer) !void { 218 + fn display(self: *SlowTracker) !void { 233 219 var slowest = self.slowest; 234 220 const count = slowest.count(); 235 - printer.fmt("Slowest {d} test{s}: \n", .{ count, if (count != 1) "s" else "" }); 221 + Printer.fmt("Slowest {d} test{s}: \n", .{ count, if (count != 1) "s" else "" }); 236 222 while (slowest.removeMinOrNull()) |info| { 237 223 const ms = @as(f64, @floatFromInt(info.ns)) / 1_000_000.0; 238 - printer.fmt(" {d:.2}ms\t{s}\n", .{ ms, info.name }); 224 + Printer.fmt(" {d:.2}ms\t{s}\n", .{ ms, info.name }); 239 225 } 240 226 } 241 227