From 85fce07d94003d862fd067669564d2fb6465d9bd Mon Sep 17 00:00:00 2001 From: Joe Mckay Date: Tue, 17 Sep 2024 19:44:00 +0100 Subject: [PATCH] Add diagnostics and error handling example --- build.zig | 1 + examples/error_handling.zig | 60 +++++++++++++++++++++++++++++++++++++ src/parse.zig | 15 ++++++++++ src/root.zig | 5 +--- 4 files changed, 77 insertions(+), 4 deletions(-) create mode 100644 examples/error_handling.zig diff --git a/build.zig b/build.zig index 87b7e3a..e10b87e 100644 --- a/build.zig +++ b/build.zig @@ -27,6 +27,7 @@ pub fn build(b: *std.Build) void { const example_option = b.option( enum { overview, + error_handling, }, "example", "Example to run for example step (default = overview)", diff --git a/examples/error_handling.zig b/examples/error_handling.zig new file mode 100644 index 0000000..04cc8a4 --- /dev/null +++ b/examples/error_handling.zig @@ -0,0 +1,60 @@ +const std = @import("std"); +const flags = @import("flags"); + +pub fn main() !void { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer _ = gpa.deinit(); + + var args = try std.process.argsWithAllocator(gpa.allocator()); + defer args.deinit(); + + // Contains info about a (sub)command where a parsing error occured. + var diagnositics: flags.Diagnostics = undefined; + + // Replace with your writer for reporting errors. + // Using stderr explicitly is redundant as flags uses stderr by default. + var error_writer = std.io.getStdErr().writer(); + + const options = flags.parse(&args, "error-handling", Flags, .{ + .diagnostics = &diagnositics, + .stderr = error_writer.any(), + }) catch |err| { + // When parsing is stopped due to --help being passed, this special error is returned. + if (err == flags.Error.PrintedHelp) { + // In this case help has already been printed to stdout and + // no actual parsing error has occured so we exit. + std.posix.exit(0); + } + + std.debug.print( + "caught {!} while parsing command \"{s}\"\n", + .{ err, diagnositics.command }, + ); + + std.debug.print("command help:\n{s}", .{diagnositics.help}); + + std.posix.exit(1); + }; + + try std.json.stringify( + options, + .{ .whitespace = .indent_2 }, + std.io.getStdOut().writer(), + ); +} + +const Flags = struct { + pub const description = "Test program to showcase error handling."; + + foo: bool, + bar: ?[]const u8, + + command: union(enum) { + baz: struct { + a: bool, + }, + qux: struct { + b: bool, + }, + }, +}; diff --git a/src/parse.zig b/src/parse.zig index e3df0ac..e27e9bb 100644 --- a/src/parse.zig +++ b/src/parse.zig @@ -32,10 +32,19 @@ pub const Error = error{ MissingCommand, } || std.fmt.ParseIntError || std.fmt.ParseFloatError; +pub const Diagnostics = struct { + /// The command name in which an error occured. + command: []const u8, + /// The help message for the command. + help: []const u8, +}; + const Env = struct { args: *ArgIterator, stdout: AnyWriter, stderr: AnyWriter, + /// Gives information about the command if a parsing error occurs. + diagnostics: ?*Diagnostics, }; var env: Env = undefined; @@ -47,6 +56,7 @@ fn report(comptime message: []const u8, args: anytype) void { pub const Options = struct { stdout: ?AnyWriter = null, stderr: ?AnyWriter = null, + diagnostics: ?*Diagnostics = null, skip_first_arg: bool = true, }; @@ -63,6 +73,7 @@ pub fn parse(args: *ArgIterator, comptime exe_name: []const u8, Flags: type, opt .args = args, .stdout = options.stdout orelse std.io.getStdOut().writer().any(), .stderr = options.stderr orelse std.io.getStdErr().writer().any(), + .diagnostics = options.diagnostics, }; return parse2(Flags, exe_name); } @@ -74,6 +85,10 @@ fn parse2(Flags: type, comptime command_name: []const u8) Error!Flags { else comptime help.generate(Flags, info, command_name); + if (env.diagnostics) |error_info| { + error_info.* = .{ .command = command_name, .help = help_message }; + } + var flags: Flags = undefined; var passed: std.enums.EnumFieldStruct(std.meta.FieldEnum(Flags), bool, false) = .{}; diff --git a/src/root.zig b/src/root.zig index 1857f07..141a830 100644 --- a/src/root.zig +++ b/src/root.zig @@ -1,10 +1,7 @@ const help = @import("help.zig"); -const parse_mod = @import("parse.zig"); -pub const parse = parse_mod.parse; -pub const parseOrExit = parse_mod.parseOrExit; +pub usingnamespace @import("parse.zig"); test { _ = help; - _ = parse_mod; }