From d87b59f5a59bbdc96c617ac90f2e1a3cd39bd7b9 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 18 Feb 2025 23:32:25 -0800 Subject: [PATCH] std.fmt.format: support base 64 encoding --- lib/std/fmt.zig | 3 +- lib/std/io/BufferedWriter.zig | 56 +++++++++++++++++------------------ 2 files changed, 30 insertions(+), 29 deletions(-) diff --git a/lib/std/fmt.zig b/lib/std/fmt.zig index 7016497ff0..b095be173d 100644 --- a/lib/std/fmt.zig +++ b/lib/std/fmt.zig @@ -62,10 +62,11 @@ pub const Options = struct { /// one has to specify *alignment* as well, as otherwise the digit following `:` is interpreted as *width*, not *fill*. /// /// The *specifier* has several options for types: -/// - `x` and `X`: output numeric value in hexadecimal notation +/// - `x` and `X`: output numeric value in hexadecimal notation, or string in hexadecimal bytes /// - `s`: /// - for pointer-to-many and C pointers of u8, print as a C-string using zero-termination /// - for slices of u8, print the entire slice as a string without zero-termination +/// - `b64`: output string as standard base64 /// - `e`: output floating point value in scientific notation /// - `d`: output numeric value in decimal notation /// - `b`: output integer value in binary notation diff --git a/lib/std/io/BufferedWriter.zig b/lib/std/io/BufferedWriter.zig index 7790b775d4..7c54be4efc 100644 --- a/lib/std/io/BufferedWriter.zig +++ b/lib/std/io/BufferedWriter.zig @@ -41,7 +41,7 @@ pub fn writer(bw: *BufferedWriter) Writer { const fixed_vtable: Writer.VTable = .{ .writeSplat = fixed_writeSplat, - .writeFile = fixed_writeFile, + .writeFile = Writer.unimplemented_writeFile, }; /// Replaces the `BufferedWriter` with a new one that writes to `buffer` and @@ -74,6 +74,10 @@ pub fn flush(bw: *BufferedWriter) anyerror!void { bw.end = 0; } +pub fn unusedCapacitySlice(bw: *const BufferedWriter) []u8 { + return bw.buffer[bw.end..]; +} + /// The `data` parameter is mutable because this function needs to mutate the /// fields in order to handle partial writes from `Writer.VTable.writev`. pub fn writevAll(bw: *BufferedWriter, data: [][]const u8) anyerror!void { @@ -93,6 +97,10 @@ pub fn writeSplat(bw: *BufferedWriter, data: []const []const u8, splat: usize) a return passthru_writeSplat(bw, data, splat); } +pub fn writev(bw: *BufferedWriter, data: []const []const u8) anyerror!usize { + return passthru_writeSplat(bw, data, 1); +} + fn passthru_writeSplat(context: *anyopaque, data: []const []const u8, splat: usize) anyerror!usize { const bw: *BufferedWriter = @alignCast(@ptrCast(context)); const buffer = bw.buffer; @@ -523,23 +531,6 @@ pub fn writeFileAll(bw: *BufferedWriter, file: std.fs.File, options: WriteFileOp } } -fn fixed_writeFile( - context: *anyopaque, - file: std.fs.File, - offset: u64, - len: Writer.VTable.FileLen, - headers_and_trailers: []const []const u8, - headers_len: usize, -) anyerror!usize { - _ = context; - _ = file; - _ = offset; - _ = len; - _ = headers_and_trailers; - _ = headers_len; - return error.Unimplemented; -} - pub fn alignBuffer( bw: *BufferedWriter, buffer: []const u8, @@ -775,19 +766,22 @@ pub fn printValue( }, .slice => { if (actual_fmt.len == 0) - @compileError("cannot format slice without a specifier (i.e. {s}, {x}, or {any})"); + @compileError("cannot format slice without a specifier (i.e. {s}, {x}, {b64}, or {any})"); if (max_depth == 0) { return bw.writeAll("{ ... }"); } - if (ptr_info.child == u8) { - if (actual_fmt[0] == 's') { - return alignBufferOptions(bw, value, options); - } else if (actual_fmt[0] == 'x') { - return printHex(bw, value, .lower); - } else if (actual_fmt[0] == 'X') { - return printHex(bw, value, .upper); - } - } + if (ptr_info.child == u8) switch (actual_fmt.len) { + 1 => switch (actual_fmt[0]) { + 's' => return alignBufferOptions(bw, value, options), + 'x' => return printHex(bw, value, .lower), + 'X' => return printHex(bw, value, .upper), + else => {}, + }, + 3 => if (actual_fmt[0] == 'b' and actual_fmt[1] == '6' and actual_fmt[2] == '4') { + return printBase64(bw, value); + }, + else => {}, + }; try bw.writeAll("{ "); for (value, 0..) |elem, i| { try printValue(bw, actual_fmt, options, elem, max_depth - 1); @@ -1289,6 +1283,12 @@ pub fn printHex(bw: *BufferedWriter, bytes: []const u8, case: std.fmt.Case) anye } } +pub fn printBase64(bw: *BufferedWriter, bytes: []const u8) anyerror!void { + var chunker = std.mem.window(u8, bytes, 3, 3); + var temp: [5]u8 = undefined; + while (chunker.next()) |chunk| try bw.writeAll(std.base64.standard.Encoder.encode(&temp, chunk)); +} + test "formatValue max_depth" { const Vec2 = struct { const SelfType = @This();