const std = @import("std"); fn writeAllFd(fd: std.posix.fd_t, data: []const u8) std.Io.Writer.Error!void { var off: usize = 0; while (off < data.len) { const n = std.posix.write(fd, data[off..]) catch return error.WriteFailed; if (n == 0) return error.WriteFailed; off += n; } } fn drainStdout(w: *std.Io.Writer, data: []const []const u8, splat: usize) std.Io.Writer.Error!usize { _ = w; if (data.len == 0) return 0; var written: usize = 0; for (data) |chunk| { try writeAllFd(std.posix.STDOUT_FILENO, chunk); written += chunk.len; } if (splat > 0) { const last = data[data.len - 1]; var i: usize = 0; while (i < splat) : (i += 1) { try writeAllFd(std.posix.STDOUT_FILENO, last); written += last.len; } } return written; } fn drainStderr(w: *std.Io.Writer, data: []const []const u8, splat: usize) std.Io.Writer.Error!usize { _ = w; if (data.len == 0) return 0; var written: usize = 0; for (data) |chunk| { try writeAllFd(std.posix.STDERR_FILENO, chunk); written += chunk.len; } if (splat > 0) { const last = data[data.len - 1]; var i: usize = 0; while (i < splat) : (i += 1) { try writeAllFd(std.posix.STDERR_FILENO, last); written += last.len; } } return written; } const stdout_vtable = std.Io.Writer.VTable{ .drain = drainStdout }; const stderr_vtable = std.Io.Writer.VTable{ .drain = drainStderr }; pub fn stdoutWriter() std.Io.Writer { return .{ .vtable = &stdout_vtable, .buffer = &[_]u8{}, .end = 0 }; } pub fn stderrWriter() std.Io.Writer { return .{ .vtable = &stderr_vtable, .buffer = &[_]u8{}, .end = 0 }; } /// Write a JSON value to stdout pub fn stdoutWriteJson(value: std.json.Value) !void { var buf = std.ArrayList(u8).empty; defer buf.deinit(std.heap.page_allocator); try writeJSONValue(buf.writer(std.heap.page_allocator), value); var stdout_file = std.fs.File{ .handle = std.posix.STDOUT_FILENO }; try stdout_file.writeAll(buf.items); try stdout_file.writeAll("\n"); } fn writeJSONValue(writer: anytype, v: std.json.Value) !void { switch (v) { .null => try writer.writeAll("null"), .bool => |b| try writer.print("{}", .{b}), .integer => |i| try writer.print("{d}", .{i}), .float => |f| try writer.print("{d}", .{f}), .string => |s| try writeJSONString(writer, s), .array => |arr| { try writer.writeAll("["); for (arr.items, 0..) |item, idx| { if (idx > 0) try writer.writeAll(","); try writeJSONValue(writer, item); } try writer.writeAll("]"); }, .object => |obj| { try writer.writeAll("{"); var first = true; var it = obj.iterator(); while (it.next()) |entry| { if (!first) try writer.writeAll(","); first = false; try writer.print("\"{s}\":", .{entry.key_ptr.*}); try writeJSONValue(writer, entry.value_ptr.*); } try writer.writeAll("}"); }, .number_string => |s| try writer.print("{s}", .{s}), } } fn writeJSONString(writer: anytype, s: []const u8) !void { try writer.writeAll("\""); for (s) |c| { switch (c) { '"' => try writer.writeAll("\\\""), '\\' => try writer.writeAll("\\\\"), '\n' => try writer.writeAll("\\n"), '\r' => try writer.writeAll("\\r"), '\t' => try writer.writeAll("\\t"), else => { if (c < 0x20) { var buf: [6]u8 = undefined; buf[0] = '\\'; buf[1] = 'u'; buf[2] = '0'; buf[3] = '0'; buf[4] = hexDigit(@intCast((c >> 4) & 0x0F)); buf[5] = hexDigit(@intCast(c & 0x0F)); try writer.writeAll(&buf); } else { try writer.writeAll(&[_]u8{c}); } }, } } try writer.writeAll("\""); } fn hexDigit(v: u8) u8 { return if (v < 10) ('0' + v) else ('a' + (v - 10)); }