diff options
| author | 2020-11-03 20:04:52 +0100 | |
|---|---|---|
| committer | 2020-11-03 20:15:00 +0100 | |
| commit | 7f82f87c310a5db3933551fd0df3d0548abf9f7c (patch) | |
| tree | 28b7385f4f50934a98df4e032b40aedfcc76ae01 | |
| parent | Replace `var` with `anytype` (diff) | |
| download | zig-clap-7f82f87c310a5db3933551fd0df3d0548abf9f7c.tar.gz zig-clap-7f82f87c310a5db3933551fd0df3d0548abf9f7c.tar.xz zig-clap-7f82f87c310a5db3933551fd0df3d0548abf9f7c.zip | |
Improve Diagnostic error message reporting
Diffstat (limited to '')
| -rw-r--r-- | clap.zig | 43 | ||||
| -rw-r--r-- | clap/args.zig | 2 | ||||
| -rw-r--r-- | clap/comptime.zig | 2 | ||||
| -rw-r--r-- | clap/streaming.zig | 92 |
4 files changed, 107 insertions, 32 deletions
| @@ -289,27 +289,36 @@ pub fn Args(comptime Id: type, comptime params: []const Param(Id)) type { | |||
| 289 | 289 | ||
| 290 | /// Optional diagnostics used for reporting useful errors | 290 | /// Optional diagnostics used for reporting useful errors |
| 291 | pub const Diagnostic = struct { | 291 | pub const Diagnostic = struct { |
| 292 | name: Names, | 292 | arg: []const u8 = "", |
| 293 | name: Names = Names{}, | ||
| 293 | 294 | ||
| 294 | /// Default diagnostics reporter when all you want is English with no colors. | 295 | /// Default diagnostics reporter when all you want is English with no colors. |
| 295 | /// Use this as a reference for implementing your own if needed. | 296 | /// Use this as a reference for implementing your own if needed. |
| 296 | pub fn report(diag: Diagnostic, stream: anytype, err: anyerror) !void { | 297 | pub fn report(diag: Diagnostic, stream: anytype, err: anyerror) !void { |
| 297 | const prefix = if (diag.name.short) |_| "-" else "--"; | 298 | const Arg = struct { |
| 298 | const name = if (diag.name.short) |*c| @as(*const [1]u8, c)[0..] else diag.name.long.?; | 299 | prefix: []const u8, |
| 300 | name: []const u8, | ||
| 301 | }; | ||
| 302 | const a = if (diag.name.short) |*c| | ||
| 303 | Arg{ .prefix = "-", .name = @as(*const [1]u8, c)[0..] } | ||
| 304 | else if (diag.name.long) |l| | ||
| 305 | Arg{ .prefix = "--", .name = l } | ||
| 306 | else | ||
| 307 | Arg{ .prefix = "", .name = diag.arg }; | ||
| 299 | 308 | ||
| 300 | switch (err) { | 309 | switch (err) { |
| 301 | error.DoesntTakeValue => try stream.print("The argument '{}{}' does not take a value\n", .{ prefix, name }), | 310 | error.DoesntTakeValue => try stream.print("The argument '{}{}' does not take a value\n", .{ a.prefix, a.name }), |
| 302 | error.MissingValue => try stream.print("The argument '{}{}' requires a value but none was supplied\n", .{ prefix, name }), | 311 | error.MissingValue => try stream.print("The argument '{}{}' requires a value but none was supplied\n", .{ a.prefix, a.name }), |
| 303 | error.InvalidArgument => try stream.print("Invalid argument '{}{}'\n", .{ prefix, name }), | 312 | error.InvalidArgument => try stream.print("Invalid argument '{}{}'\n", .{ a.prefix, a.name }), |
| 304 | else => try stream.print("Error while parsing arguments: {}\n", .{@errorName(err)}), | 313 | else => try stream.print("Error while parsing arguments: {}\n", .{@errorName(err)}), |
| 305 | } | 314 | } |
| 306 | } | 315 | } |
| 307 | }; | 316 | }; |
| 308 | 317 | ||
| 309 | fn testDiag(names: Names, err: anyerror, expected: []const u8) void { | 318 | fn testDiag(diag: Diagnostic, err: anyerror, expected: []const u8) void { |
| 310 | var buf: [1024]u8 = undefined; | 319 | var buf: [1024]u8 = undefined; |
| 311 | var slice_stream = io.fixedBufferStream(&buf); | 320 | var slice_stream = io.fixedBufferStream(&buf); |
| 312 | (Diagnostic{ .name = names }).report(slice_stream.outStream(), err) catch unreachable; | 321 | diag.report(slice_stream.outStream(), err) catch unreachable; |
| 313 | 322 | ||
| 314 | const actual = slice_stream.getWritten(); | 323 | const actual = slice_stream.getWritten(); |
| 315 | if (!mem.eql(u8, actual, expected)) { | 324 | if (!mem.eql(u8, actual, expected)) { |
| @@ -322,14 +331,16 @@ fn testDiag(names: Names, err: anyerror, expected: []const u8) void { | |||
| 322 | } | 331 | } |
| 323 | 332 | ||
| 324 | test "Diagnostic.report" { | 333 | test "Diagnostic.report" { |
| 325 | testDiag(.{ .short = 'c' }, error.DoesntTakeValue, "The argument '-c' does not take a value\n"); | 334 | testDiag(.{ .arg = "c" }, error.InvalidArgument, "Invalid argument 'c'\n"); |
| 326 | testDiag(.{ .long = "cc" }, error.DoesntTakeValue, "The argument '--cc' does not take a value\n"); | 335 | testDiag(.{ .name = .{ .long = "cc" } }, error.InvalidArgument, "Invalid argument '--cc'\n"); |
| 327 | testDiag(.{ .short = 'c' }, error.MissingValue, "The argument '-c' requires a value but none was supplied\n"); | 336 | testDiag(.{ .name = .{ .short = 'c' } }, error.DoesntTakeValue, "The argument '-c' does not take a value\n"); |
| 328 | testDiag(.{ .long = "cc" }, error.MissingValue, "The argument '--cc' requires a value but none was supplied\n"); | 337 | testDiag(.{ .name = .{ .long = "cc" } }, error.DoesntTakeValue, "The argument '--cc' does not take a value\n"); |
| 329 | testDiag(.{ .short = 'c' }, error.InvalidArgument, "Invalid argument '-c'\n"); | 338 | testDiag(.{ .name = .{ .short = 'c' } }, error.MissingValue, "The argument '-c' requires a value but none was supplied\n"); |
| 330 | testDiag(.{ .long = "cc" }, error.InvalidArgument, "Invalid argument '--cc'\n"); | 339 | testDiag(.{ .name = .{ .long = "cc" } }, error.MissingValue, "The argument '--cc' requires a value but none was supplied\n"); |
| 331 | testDiag(.{ .short = 'c' }, error.SomethingElse, "Error while parsing arguments: SomethingElse\n"); | 340 | testDiag(.{ .name = .{ .short = 'c' } }, error.InvalidArgument, "Invalid argument '-c'\n"); |
| 332 | testDiag(.{ .long = "cc" }, error.SomethingElse, "Error while parsing arguments: SomethingElse\n"); | 341 | testDiag(.{ .name = .{ .long = "cc" } }, error.InvalidArgument, "Invalid argument '--cc'\n"); |
| 342 | testDiag(.{ .name = .{ .short = 'c' } }, error.SomethingElse, "Error while parsing arguments: SomethingElse\n"); | ||
| 343 | testDiag(.{ .name = .{ .long = "cc" } }, error.SomethingElse, "Error while parsing arguments: SomethingElse\n"); | ||
| 333 | } | 344 | } |
| 334 | 345 | ||
| 335 | /// Parses the command line arguments passed into the program based on an | 346 | /// Parses the command line arguments passed into the program based on an |
diff --git a/clap/args.zig b/clap/args.zig index 4d97017..52626fc 100644 --- a/clap/args.zig +++ b/clap/args.zig | |||
| @@ -32,7 +32,7 @@ pub const SliceIterator = struct { | |||
| 32 | } | 32 | } |
| 33 | }; | 33 | }; |
| 34 | 34 | ||
| 35 | test "clap.args.SliceIterator" { | 35 | test "SliceIterator" { |
| 36 | const args = &[_][]const u8{ "A", "BB", "CCC" }; | 36 | const args = &[_][]const u8{ "A", "BB", "CCC" }; |
| 37 | var iter = SliceIterator{ .args = args }; | 37 | var iter = SliceIterator{ .args = args }; |
| 38 | 38 | ||
diff --git a/clap/comptime.zig b/clap/comptime.zig index e8aae30..1570eaf 100644 --- a/clap/comptime.zig +++ b/clap/comptime.zig | |||
| @@ -146,7 +146,7 @@ pub fn ComptimeClap( | |||
| 146 | }; | 146 | }; |
| 147 | } | 147 | } |
| 148 | 148 | ||
| 149 | test "clap.comptime.ComptimeClap" { | 149 | test "" { |
| 150 | const Clap = ComptimeClap(clap.Help, clap.args.SliceIterator, comptime &[_]clap.Param(clap.Help){ | 150 | const Clap = ComptimeClap(clap.Help, clap.args.SliceIterator, comptime &[_]clap.Param(clap.Help){ |
| 151 | clap.parseParam("-a, --aa ") catch unreachable, | 151 | clap.parseParam("-a, --aa ") catch unreachable, |
| 152 | clap.parseParam("-b, --bb ") catch unreachable, | 152 | clap.parseParam("-b, --bb ") catch unreachable, |
diff --git a/clap/streaming.zig b/clap/streaming.zig index af57e8f..602d94a 100644 --- a/clap/streaming.zig +++ b/clap/streaming.zig | |||
| @@ -3,10 +3,12 @@ const clap = @import("../clap.zig"); | |||
| 3 | const std = @import("std"); | 3 | const std = @import("std"); |
| 4 | 4 | ||
| 5 | const args = clap.args; | 5 | const args = clap.args; |
| 6 | const testing = std.testing; | 6 | const debug = std.debug; |
| 7 | const heap = std.heap; | 7 | const heap = std.heap; |
| 8 | const io = std.io; | ||
| 8 | const mem = std.mem; | 9 | const mem = std.mem; |
| 9 | const os = std.os; | 10 | const os = std.os; |
| 11 | const testing = std.testing; | ||
| 10 | 12 | ||
| 11 | /// The result returned from ::StreamingClap.next | 13 | /// The result returned from ::StreamingClap.next |
| 12 | pub fn Arg(comptime Id: type) type { | 14 | pub fn Arg(comptime Id: type) type { |
| @@ -76,7 +78,7 @@ pub fn StreamingClap(comptime Id: type, comptime ArgIterator: type) type { | |||
| 76 | continue; | 78 | continue; |
| 77 | if (param.takes_value == .None) { | 79 | if (param.takes_value == .None) { |
| 78 | if (maybe_value != null) | 80 | if (maybe_value != null) |
| 79 | return err(diag, param.names, error.DoesntTakeValue); | 81 | return err(diag, arg, .{ .long = name }, error.DoesntTakeValue); |
| 80 | 82 | ||
| 81 | return Arg(Id){ .param = param }; | 83 | return Arg(Id){ .param = param }; |
| 82 | } | 84 | } |
| @@ -86,11 +88,13 @@ pub fn StreamingClap(comptime Id: type, comptime ArgIterator: type) type { | |||
| 86 | break :blk v; | 88 | break :blk v; |
| 87 | 89 | ||
| 88 | break :blk (try parser.iter.next()) orelse | 90 | break :blk (try parser.iter.next()) orelse |
| 89 | return err(diag, param.names, error.MissingValue); | 91 | return err(diag, arg, .{ .long = name }, error.MissingValue); |
| 90 | }; | 92 | }; |
| 91 | 93 | ||
| 92 | return Arg(Id){ .param = param, .value = value }; | 94 | return Arg(Id){ .param = param, .value = value }; |
| 93 | } | 95 | } |
| 96 | |||
| 97 | return err(diag, arg, .{ .long = name }, error.InvalidArgument); | ||
| 94 | }, | 98 | }, |
| 95 | .short => return try parser.chainging(.{ | 99 | .short => return try parser.chainging(.{ |
| 96 | .arg = full_arg, | 100 | .arg = full_arg, |
| @@ -105,10 +109,12 @@ pub fn StreamingClap(comptime Id: type, comptime ArgIterator: type) type { | |||
| 105 | 109 | ||
| 106 | return Arg(Id){ .param = param, .value = arg }; | 110 | return Arg(Id){ .param = param, .value = arg }; |
| 107 | } | 111 | } |
| 112 | |||
| 113 | return err(diag, arg, .{}, error.InvalidArgument); | ||
| 108 | }, | 114 | }, |
| 109 | } | 115 | } |
| 110 | 116 | ||
| 111 | return err(diag, .{ .long = arg }, error.InvalidArgument); | 117 | unreachable; |
| 112 | }, | 118 | }, |
| 113 | .chaining => |state| return try parser.chainging(state, diag), | 119 | .chaining => |state| return try parser.chainging(state, diag), |
| 114 | } | 120 | } |
| @@ -138,28 +144,32 @@ pub fn StreamingClap(comptime Id: type, comptime ArgIterator: type) type { | |||
| 138 | } | 144 | } |
| 139 | } | 145 | } |
| 140 | 146 | ||
| 141 | if (param.takes_value == .None) | 147 | const next_is_eql = if (next_index < arg.len) arg[next_index] == '=' else false; |
| 148 | if (param.takes_value == .None) { | ||
| 149 | if (next_is_eql) | ||
| 150 | return err(diag, arg, .{ .short = short }, error.DoesntTakeValue); | ||
| 142 | return Arg(Id){ .param = param }; | 151 | return Arg(Id){ .param = param }; |
| 152 | } | ||
| 143 | 153 | ||
| 144 | if (arg.len <= next_index) { | 154 | if (arg.len <= next_index) { |
| 145 | const value = (try parser.iter.next()) orelse | 155 | const value = (try parser.iter.next()) orelse |
| 146 | return err(diag, param.names, error.MissingValue); | 156 | return err(diag, arg, .{ .short = short }, error.MissingValue); |
| 147 | 157 | ||
| 148 | return Arg(Id){ .param = param, .value = value }; | 158 | return Arg(Id){ .param = param, .value = value }; |
| 149 | } | 159 | } |
| 150 | 160 | ||
| 151 | if (arg[next_index] == '=') | 161 | if (next_is_eql) |
| 152 | return Arg(Id){ .param = param, .value = arg[next_index + 1 ..] }; | 162 | return Arg(Id){ .param = param, .value = arg[next_index + 1 ..] }; |
| 153 | 163 | ||
| 154 | return Arg(Id){ .param = param, .value = arg[next_index..] }; | 164 | return Arg(Id){ .param = param, .value = arg[next_index..] }; |
| 155 | } | 165 | } |
| 156 | 166 | ||
| 157 | return err(diag, .{ .short = arg[index] }, error.InvalidArgument); | 167 | return err(diag, arg, .{ .short = arg[index] }, error.InvalidArgument); |
| 158 | } | 168 | } |
| 159 | 169 | ||
| 160 | fn err(diag: ?*clap.Diagnostic, names: clap.Names, _err: anytype) @TypeOf(_err) { | 170 | fn err(diag: ?*clap.Diagnostic, arg: []const u8, names: clap.Names, _err: anytype) @TypeOf(_err) { |
| 161 | if (diag) |d| | 171 | if (diag) |d| |
| 162 | d.name = names; | 172 | d.* = .{ .arg = arg, .name = names }; |
| 163 | return _err; | 173 | return _err; |
| 164 | } | 174 | } |
| 165 | }; | 175 | }; |
| @@ -187,7 +197,33 @@ fn testNoErr(params: []const clap.Param(u8), args_strings: []const []const u8, r | |||
| 187 | unreachable; | 197 | unreachable; |
| 188 | } | 198 | } |
| 189 | 199 | ||
| 190 | test "clap.streaming.StreamingClap: short params" { | 200 | fn testErr(params: []const clap.Param(u8), args_strings: []const []const u8, expected: []const u8) void { |
| 201 | var diag: clap.Diagnostic = undefined; | ||
| 202 | var iter = args.SliceIterator{ .args = args_strings }; | ||
| 203 | var c = StreamingClap(u8, args.SliceIterator){ | ||
| 204 | .params = params, | ||
| 205 | .iter = &iter, | ||
| 206 | }; | ||
| 207 | while (c.next(&diag) catch |err| { | ||
| 208 | var buf: [1024]u8 = undefined; | ||
| 209 | var slice_stream = io.fixedBufferStream(&buf); | ||
| 210 | diag.report(slice_stream.outStream(), err) catch unreachable; | ||
| 211 | |||
| 212 | const actual = slice_stream.getWritten(); | ||
| 213 | if (!mem.eql(u8, actual, expected)) { | ||
| 214 | debug.warn("\n============ Expected ============\n", .{}); | ||
| 215 | debug.warn("{}", .{expected}); | ||
| 216 | debug.warn("============= Actual =============\n", .{}); | ||
| 217 | debug.warn("{}", .{actual}); | ||
| 218 | testing.expect(false); | ||
| 219 | } | ||
| 220 | return; | ||
| 221 | }) |_| {} | ||
| 222 | |||
| 223 | testing.expect(false); | ||
| 224 | } | ||
| 225 | |||
| 226 | test "short params" { | ||
| 191 | const params = [_]clap.Param(u8){ | 227 | const params = [_]clap.Param(u8){ |
| 192 | clap.Param(u8){ | 228 | clap.Param(u8){ |
| 193 | .id = 0, | 229 | .id = 0, |
| @@ -239,7 +275,7 @@ test "clap.streaming.StreamingClap: short params" { | |||
| 239 | ); | 275 | ); |
| 240 | } | 276 | } |
| 241 | 277 | ||
| 242 | test "clap.streaming.StreamingClap: long params" { | 278 | test "long params" { |
| 243 | const params = [_]clap.Param(u8){ | 279 | const params = [_]clap.Param(u8){ |
| 244 | clap.Param(u8){ | 280 | clap.Param(u8){ |
| 245 | .id = 0, | 281 | .id = 0, |
| @@ -283,7 +319,7 @@ test "clap.streaming.StreamingClap: long params" { | |||
| 283 | ); | 319 | ); |
| 284 | } | 320 | } |
| 285 | 321 | ||
| 286 | test "clap.streaming.StreamingClap: positional params" { | 322 | test "positional params" { |
| 287 | const params = [_]clap.Param(u8){clap.Param(u8){ | 323 | const params = [_]clap.Param(u8){clap.Param(u8){ |
| 288 | .id = 0, | 324 | .id = 0, |
| 289 | .takes_value = .One, | 325 | .takes_value = .One, |
| @@ -299,7 +335,7 @@ test "clap.streaming.StreamingClap: positional params" { | |||
| 299 | ); | 335 | ); |
| 300 | } | 336 | } |
| 301 | 337 | ||
| 302 | test "clap.streaming.StreamingClap: all params" { | 338 | test "all params" { |
| 303 | const params = [_]clap.Param(u8){ | 339 | const params = [_]clap.Param(u8){ |
| 304 | clap.Param(u8){ | 340 | clap.Param(u8){ |
| 305 | .id = 0, | 341 | .id = 0, |
| @@ -366,3 +402,31 @@ test "clap.streaming.StreamingClap: all params" { | |||
| 366 | }, | 402 | }, |
| 367 | ); | 403 | ); |
| 368 | } | 404 | } |
| 405 | |||
| 406 | test "errors" { | ||
| 407 | const params = [_]clap.Param(u8){ | ||
| 408 | clap.Param(u8){ | ||
| 409 | .id = 0, | ||
| 410 | .names = clap.Names{ | ||
| 411 | .short = 'a', | ||
| 412 | .long = "aa", | ||
| 413 | }, | ||
| 414 | }, | ||
| 415 | clap.Param(u8){ | ||
| 416 | .id = 1, | ||
| 417 | .names = clap.Names{ | ||
| 418 | .short = 'c', | ||
| 419 | .long = "cc", | ||
| 420 | }, | ||
| 421 | .takes_value = .One, | ||
| 422 | }, | ||
| 423 | }; | ||
| 424 | testErr(¶ms, &[_][]const u8{"q"}, "Invalid argument 'q'\n"); | ||
| 425 | testErr(¶ms, &[_][]const u8{"-q"}, "Invalid argument '-q'\n"); | ||
| 426 | testErr(¶ms, &[_][]const u8{"--q"}, "Invalid argument '--q'\n"); | ||
| 427 | testErr(¶ms, &[_][]const u8{"--q=1"}, "Invalid argument '--q'\n"); | ||
| 428 | testErr(¶ms, &[_][]const u8{"-a=1"}, "The argument '-a' does not take a value\n"); | ||
| 429 | testErr(¶ms, &[_][]const u8{"--aa=1"}, "The argument '--aa' does not take a value\n"); | ||
| 430 | testErr(¶ms, &[_][]const u8{"-c"}, "The argument '-c' requires a value but none was supplied\n"); | ||
| 431 | testErr(¶ms, &[_][]const u8{"--cc"}, "The argument '--cc' requires a value but none was supplied\n"); | ||
| 432 | } | ||