std.fmt.format: support base 64 encoding

This commit is contained in:
Andrew Kelley 2025-02-18 23:32:25 -08:00
parent 8464efa5dd
commit d87b59f5a5
2 changed files with 30 additions and 29 deletions

View File

@ -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

View File

@ -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();