From 095409eb0421f0f970c246b371b39dab2ff912aa Mon Sep 17 00:00:00 2001 From: Jimmi Holst Christensen Date: Tue, 22 Jul 2025 10:44:11 +0200 Subject: feat: Add `Diagnostic.reportToFile` The code for reporting errors became quite verbose after writegate. Reporting to stderr is very common, so this wrapper provides a default, buffered way to report to a file. --- README.md | 25 +++++-------------------- clap.zig | 12 ++++++++++-- clap/streaming.zig | 2 +- example/simple-ex.zig | 5 +---- example/simple.zig | 6 ++---- example/streaming-clap.zig | 6 ++---- example/subcommands.zig | 11 +++-------- 7 files changed, 24 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index 2e64264..8db67c9 100644 --- a/README.md +++ b/README.md @@ -77,10 +77,7 @@ pub fn main() !void { .allocator = gpa.allocator(), }) catch |err| { // Report useful error and exit. - var buf: [1024]u8 = undefined; - var stderr = std.fs.File.stderr().writer(&buf); - diag.report(&stderr.interface, err) catch {}; - try stderr.interface.flush(); + try diag.reportToFile(.stderr(), err); return err; }; defer res.deinit(); @@ -144,10 +141,7 @@ pub fn main() !void { // allowed. .assignment_separators = "=:", }) catch |err| { - var buf: [1024]u8 = undefined; - var stderr = std.fs.File.stderr().writer(&buf); - diag.report(&stderr.interface, err) catch {}; - try stderr.interface.flush(); + try diag.reportToFile(.stderr(), err); return err; }; defer res.deinit(); @@ -217,10 +211,7 @@ pub fn main() !void { // not fully consumed. It can then be reused to parse the arguments for subcommands. .terminating_positional = 0, }) catch |err| { - var buf: [1024]u8 = undefined; - var stderr = std.fs.File.stderr().writer(&buf); - diag.report(&stderr.interface, err) catch {}; - try stderr.interface.flush(); + try diag.reportToFile(.stderr(), err); return err; }; defer res.deinit(); @@ -256,10 +247,7 @@ fn mathMain(gpa: std.mem.Allocator, iter: *std.process.ArgIterator, main_args: M .diagnostic = &diag, .allocator = gpa, }) catch |err| { - var buf: [1024]u8 = undefined; - var stderr = std.fs.File.stderr().writer(&buf); - diag.report(&stderr.interface, err) catch {}; - try stderr.interface.flush(); + try diag.reportToFile(.stderr(), err); return err; // propagate error }; defer res.deinit(); @@ -320,10 +308,7 @@ pub fn main() !void { // Because we use a streaming parser, we have to consume each argument parsed individually. while (parser.next() catch |err| { // Report useful error and exit. - var buf: [1024]u8 = undefined; - var stderr = std.fs.File.stderr().writer(&buf); - diag.report(&stderr.interface, err) catch {}; - try stderr.interface.flush(); + try diag.reportToFile(.stderr(), err); return err; }) |arg| { // arg.param will point to the parameter which matched the argument. diff --git a/clap.zig b/clap.zig index 3863087..c4dd801 100644 --- a/clap.zig +++ b/clap.zig @@ -563,12 +563,20 @@ pub const Diagnostic = struct { else => try stream.print("Error while parsing arguments: {s}\n", .{@errorName(err)}), } } + + /// Wrapper around `report`, which writes to a file in a buffered manner + pub fn reportToFile(diag: Diagnostic, file: std.fs.File, err: anyerror) !void { + var buf: [1024]u8 = undefined; + var writer = file.writer(&buf); + try diag.report(&writer.interface, err); + return writer.end(); + } }; fn testDiag(diag: Diagnostic, err: anyerror, expected: []const u8) !void { var buf: [1024]u8 = undefined; var writer = std.Io.Writer.fixed(&buf); - diag.report(&writer, err) catch unreachable; + try diag.report(&writer, err); try std.testing.expectEqualStrings(expected, writer.buffered()); } @@ -1264,7 +1272,7 @@ fn testErr( }) catch |err| { var buf: [1024]u8 = undefined; var writer = std.Io.Writer.fixed(&buf); - diag.report(&writer, err) catch return error.TestFailed; + try diag.report(&writer, err); try std.testing.expectEqualStrings(expected, writer.buffered()); return; }; diff --git a/clap/streaming.zig b/clap/streaming.zig index fa5ec70..ec53bf5 100644 --- a/clap/streaming.zig +++ b/clap/streaming.zig @@ -235,7 +235,7 @@ fn expectError( while (parser.next() catch |err| { var buf: [1024]u8 = undefined; var fbs = std.Io.Writer.fixed(&buf); - diag.report(&fbs, err) catch return error.TestFailed; + try diag.report(&fbs, err); try std.testing.expectEqualStrings(expected, fbs.buffered()); return; }) |_| {} diff --git a/example/simple-ex.zig b/example/simple-ex.zig index a993868..77d8d09 100644 --- a/example/simple-ex.zig +++ b/example/simple-ex.zig @@ -32,10 +32,7 @@ pub fn main() !void { .assignment_separators = "=:", }) catch |err| { // Report useful error and exit. - var buf: [1024]u8 = undefined; - var stderr = std.fs.File.stderr().writer(&buf); - try diag.report(&stderr.interface, err); - try stderr.interface.flush(); + try diag.reportToFile(.stderr(), err); return err; }; defer res.deinit(); diff --git a/example/simple.zig b/example/simple.zig index ca6bd75..1834396 100644 --- a/example/simple.zig +++ b/example/simple.zig @@ -21,10 +21,8 @@ pub fn main() !void { .allocator = gpa.allocator(), }) catch |err| { // Report useful error and exit. - var buf: [1024]u8 = undefined; - var stderr = std.fs.File.stderr().writer(&buf); - try diag.report(&stderr.interface, err); - return stderr.interface.flush(); + try diag.reportToFile(.stderr(), err); + return err; }; defer res.deinit(); diff --git a/example/streaming-clap.zig b/example/streaming-clap.zig index d60167c..4cd6962 100644 --- a/example/streaming-clap.zig +++ b/example/streaming-clap.zig @@ -34,10 +34,8 @@ pub fn main() !void { // Because we use a streaming parser, we have to consume each argument parsed individually. while (parser.next() catch |err| { // Report useful error and exit. - var buf: [1024]u8 = undefined; - var stderr = std.fs.File.stderr().writer(&buf); - try diag.report(&stderr.interface, err); - return stderr.interface.flush(); + try diag.reportToFile(.stderr(), err); + return err; }) |arg| { // arg.param will point to the parameter which matched the argument. switch (arg.param.id) { diff --git a/example/subcommands.zig b/example/subcommands.zig index 644e371..7d9f5f1 100644 --- a/example/subcommands.zig +++ b/example/subcommands.zig @@ -41,10 +41,8 @@ pub fn main() !void { // not fully consumed. It can then be reused to parse the arguments for subcommands. .terminating_positional = 0, }) catch |err| { - var buf: [1024]u8 = undefined; - var stderr = std.fs.File.stderr().writer(&buf); - try diag.report(&stderr.interface, err); - return stderr.interface.flush(); + try diag.reportToFile(.stderr(), err); + return err; }; defer res.deinit(); @@ -79,10 +77,7 @@ fn mathMain(gpa: std.mem.Allocator, iter: *std.process.ArgIterator, main_args: M .diagnostic = &diag, .allocator = gpa, }) catch |err| { - var buf: [1024]u8 = undefined; - var stderr = std.fs.File.stderr().writer(&buf); - try diag.report(&stderr.interface, err); - try stderr.interface.flush(); + try diag.reportToFile(.stderr(), err); return err; // propagate error }; defer res.deinit(); -- cgit v1.2.3