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 }