lib/pretty/core/src/diagnostic.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 const builder_mod = @import("builder.zig");
  3 const render = @import("render.zig");
  4 const stream = @import("stream.zig");
  5 const types = @import("types.zig");
  6 
  7 const Builder = builder_mod.Builder;
  8 const Doc = types.Doc;
  9 const hardline = types.hardline;
 10 const fs_io = std.Options.debug_io;
 11 
 12 pub const Report = struct {
 13     allocator: std.mem.Allocator,
 14     builder: Builder,
 15     parts: std.ArrayListUnmanaged(Doc) = .empty,
 16 
 17     pub fn init(allocator: std.mem.Allocator, title: []const u8) !Report {
 18         var self = Report{
 19             .allocator = allocator,
 20             .builder = Builder.init(allocator),
 21         };
 22         try self.parts.append(allocator, try self.builder.styledText(.danger, title));
 23         return self;
 24     }
 25 
 26     pub fn deinit(self: *Report) void {
 27         self.parts.deinit(self.allocator);
 28         self.* = undefined;
 29     }
 30 
 31     pub fn field(
 32         self: *Report,
 33         name: []const u8,
 34         comptime fmt: []const u8,
 35         args: anytype,
 36     ) !void {
 37         try self.parts.append(self.allocator, hardline);
 38         try self.parts.append(self.allocator, try self.builder.concat(&.{
 39             try self.builder.styledText(.name, name),
 40             try self.builder.punct(": "),
 41             try self.builder.styledFmt(.value, fmt, args),
 42         }));
 43     }
 44 
 45     pub fn section(self: *Report, name: []const u8) !void {
 46         try self.parts.append(self.allocator, hardline);
 47         try self.parts.append(self.allocator, hardline);
 48         try self.parts.append(self.allocator, try self.builder.styledText(.title, name));
 49     }
 50 
 51     pub fn line(
 52         self: *Report,
 53         comptime fmt: []const u8,
 54         args: anytype,
 55     ) !void {
 56         try self.parts.append(self.allocator, hardline);
 57         try self.parts.append(self.allocator, try self.builder.concat(&.{
 58             self.builder.text("  "),
 59             try self.builder.textFmt(fmt, args),
 60         }));
 61     }
 62 
 63     pub fn doc(self: *Report) !Doc {
 64         return try self.builder.concat(self.parts.items);
 65     }
 66 
 67     pub fn write(
 68         self: *Report,
 69         writer: *std.Io.Writer,
 70         options: types.LayoutOptions,
 71     ) !void {
 72         try render.write(writer, try self.doc(), options);
 73     }
 74 
 75     pub fn renderAlloc(
 76         self: *Report,
 77         options: types.LayoutOptions,
 78     ) ![]u8 {
 79         return try render.renderAlloc(self.allocator, try self.doc(), options);
 80     }
 81 };
 82 
 83 /// Renders one report to standard error and suppresses I/O failures.
 84 ///
 85 /// A standard stream is written in streaming mode. The positional writer starts every
 86 /// instance at offset 0, so when the stream is a regular file it overwrites whatever the
 87 /// process wrote before it rather than appending, and the report's own first field is the
 88 /// first thing lost.
 89 pub fn writeStderr(report: *Report, options: types.LayoutOptions) void {
 90     var buffer: [8192]u8 = undefined;
 91     var writer = std.Io.File.stderr().writerStreaming(fs_io, &buffer);
 92     defer writer.interface.flush() catch {};
 93     report.write(&writer.interface, options) catch {};
 94     writer.interface.writeByte('\n') catch {};
 95 }
 96 
 97 /// Renders one document to standard output and reports I/O failures.
 98 pub fn writeStdoutDoc(doc: Doc, options: types.LayoutOptions) !void {
 99     var buffer: [8192]u8 = undefined;
100     var writer = std.Io.File.stdout().writerStreaming(fs_io, &buffer);
101     try render.write(&writer.interface, doc, options);
102     try writer.interface.flush();
103 }
104 
105 /// Renders one document to standard error and reports I/O failures.
106 pub fn writeStderrDoc(doc: Doc, options: types.LayoutOptions) !void {
107     var buffer: [8192]u8 = undefined;
108     var writer = std.Io.File.stderr().writerStreaming(fs_io, &buffer);
109     try render.write(&writer.interface, doc, options);
110     try writer.interface.flush();
111 }
112 
113 pub fn writeStderrText(comptime fmt: []const u8, args: anytype) void {
114     var file_buffer: [4096]u8 = undefined;
115     var file_writer = std.Io.File.stderr().writerStreaming(fs_io, &file_buffer);
116     defer file_writer.interface.flush() catch {};
117     var text_buffer: [4096]u8 = undefined;
118     var text = stream.TextWriter.init(&file_writer.interface, &text_buffer, .{});
119     defer text.writer.flush() catch {};
120     text.writer.print(fmt, args) catch {};
121 }
122 
123 /// Writes plain formatted text to standard output and suppresses I/O failures.
124 pub fn writeStdoutText(comptime fmt: []const u8, args: anytype) void {
125     var file_buffer: [4096]u8 = undefined;
126     var file_writer = std.Io.File.stdout().writerStreaming(fs_io, &file_buffer);
127     defer file_writer.interface.flush() catch {};
128     var text_buffer: [4096]u8 = undefined;
129     var text = stream.TextWriter.init(&file_writer.interface, &text_buffer, .{});
130     defer text.writer.flush() catch {};
131     text.writer.print(fmt, args) catch {};
132 }
133 
134 test "diagnostic report renders fields and sections" {
135     var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
136     defer arena.deinit();
137     var report = try Report.init(arena.allocator(), "property failed");
138     defer report.deinit();
139 
140     try report.field("seed", "{}", .{42});
141     try report.section("expected");
142     try report.line("row {d} score {d}", .{ 7, 3 });
143 
144     const rendered = try report.renderAlloc(.{ .width = 80 });
145     try std.testing.expect(std.mem.indexOf(u8, rendered, "property failed") != null);
146     try std.testing.expect(std.mem.indexOf(u8, rendered, "seed") != null);
147     try std.testing.expect(std.mem.indexOf(u8, rendered, "row 7 score 3") != null);
148 }
149 
150 test "diagnostic stderr helpers type check" {
151     writeStderrText("", .{});
152 }
153 
154 test "diagnostic stdout helpers type check" {
155     writeStdoutText("", .{});
156     try writeStdoutDoc(.{ .text = "" }, .{});
157     try writeStderrDoc(.{ .text = "" }, .{});
158 }