diff options
| author | 2020-11-02 18:04:30 +0000 | |
|---|---|---|
| committer | 2020-11-02 18:04:30 +0000 | |
| commit | 093d29899b8fdf449b944e973b07b5992be2144a (patch) | |
| tree | fb669157fa6e17523b22635c93f920bd4cc57760 | |
| parent | adjust examples, README template (diff) | |
| download | zig-clap-093d29899b8fdf449b944e973b07b5992be2144a.tar.gz zig-clap-093d29899b8fdf449b944e973b07b5992be2144a.tar.xz zig-clap-093d29899b8fdf449b944e973b07b5992be2144a.zip | |
Report error context in Diagnostic (#26)
| -rw-r--r-- | README.md | 38 | ||||
| -rw-r--r-- | clap.zig | 51 | ||||
| -rw-r--r-- | clap/comptime.zig | 14 | ||||
| -rw-r--r-- | clap/streaming.zig | 84 | ||||
| -rw-r--r-- | example/comptime-clap.zig | 12 | ||||
| -rw-r--r-- | example/simple-error.zig | 4 | ||||
| -rw-r--r-- | example/simple.zig | 11 | ||||
| -rw-r--r-- | example/streaming-clap.zig | 11 |
8 files changed, 167 insertions, 58 deletions
| @@ -42,7 +42,16 @@ pub fn main() !void { | |||
| 42 | }, | 42 | }, |
| 43 | }; | 43 | }; |
| 44 | 44 | ||
| 45 | var args = try clap.parse(clap.Help, ¶ms, std.heap.page_allocator); | 45 | // Initalize our diagnostics, which can be used for reporting useful errors. |
| 46 | // This is optional. You can also just pass `null` to `parser.next` if you | ||
| 47 | // don't care about the extra information `Diagnostics` provides. | ||
| 48 | var diag: clap.Diagnostic = undefined; | ||
| 49 | |||
| 50 | var args = clap.parse(clap.Help, ¶ms, std.heap.page_allocator, &diag) catch |err| { | ||
| 51 | // Report useful error and exit | ||
| 52 | diag.report(std.io.getStdErr().outStream(), err) catch {}; | ||
| 53 | return err; | ||
| 54 | }; | ||
| 46 | defer args.deinit(); | 55 | defer args.deinit(); |
| 47 | 56 | ||
| 48 | if (args.flag("--help")) | 57 | if (args.flag("--help")) |
| @@ -66,13 +75,11 @@ const std = @import("std"); | |||
| 66 | const clap = @import("clap"); | 75 | const clap = @import("clap"); |
| 67 | 76 | ||
| 68 | pub fn main() !void { | 77 | pub fn main() !void { |
| 69 | // First we specify what parameters our program can take. | ||
| 70 | // We can use `parseParam` to parse a string to a `Param(Help)` | ||
| 71 | const params = comptime [_]clap.Param(clap.Help){ | 78 | const params = comptime [_]clap.Param(clap.Help){ |
| 72 | clap.parseParam("-h, --help Display this help and exit.") catch unreachable, | 79 | clap.parseParam("-h, --help Display this help and exit.") catch unreachable, |
| 73 | }; | 80 | }; |
| 74 | 81 | ||
| 75 | var args = try clap.parse(clap.Help, ¶ms, std.heap.direct_allocator); | 82 | var args = try clap.parse(clap.Help, ¶ms, std.heap.direct_allocator, null); |
| 76 | defer args.deinit(); | 83 | defer args.deinit(); |
| 77 | 84 | ||
| 78 | _ = args.flag("--helps"); | 85 | _ = args.flag("--helps"); |
| @@ -118,14 +125,24 @@ pub fn main() !void { | |||
| 118 | .takes_value = .One, | 125 | .takes_value = .One, |
| 119 | }, | 126 | }, |
| 120 | }; | 127 | }; |
| 128 | const Clap = clap.ComptimeClap(clap.Help, clap.args.OsIterator, ¶ms); | ||
| 121 | 129 | ||
| 122 | // We then initialize an argument iterator. We will use the OsIterator as it nicely | 130 | // We then initialize an argument iterator. We will use the OsIterator as it nicely |
| 123 | // wraps iterating over arguments the most efficient way on each os. | 131 | // wraps iterating over arguments the most efficient way on each os. |
| 124 | var iter = try clap.args.OsIterator.init(allocator); | 132 | var iter = try clap.args.OsIterator.init(allocator); |
| 125 | defer iter.deinit(); | 133 | defer iter.deinit(); |
| 126 | 134 | ||
| 135 | // Initalize our diagnostics, which can be used for reporting useful errors. | ||
| 136 | // This is optional. You can also just pass `null` to `parser.next` if you | ||
| 137 | // don't care about the extra information `Diagnostics` provides. | ||
| 138 | var diag: clap.Diagnostic = undefined; | ||
| 139 | |||
| 127 | // Parse the arguments | 140 | // Parse the arguments |
| 128 | var args = try clap.ComptimeClap(clap.Help, ¶ms).parse(allocator, clap.args.OsIterator, &iter); | 141 | var args = Clap.parse(allocator, &iter, &diag) catch |err| { |
| 142 | // Report useful error and exit | ||
| 143 | diag.report(std.io.getStdErr().outStream(), err) catch {}; | ||
| 144 | return err; | ||
| 145 | }; | ||
| 129 | defer args.deinit(); | 146 | defer args.deinit(); |
| 130 | 147 | ||
| 131 | if (args.flag("--help")) | 148 | if (args.flag("--help")) |
| @@ -182,8 +199,17 @@ pub fn main() !void { | |||
| 182 | .iter = &iter, | 199 | .iter = &iter, |
| 183 | }; | 200 | }; |
| 184 | 201 | ||
| 202 | // Initalize our diagnostics, which can be used for reporting useful errors. | ||
| 203 | // This is optional. You can also just pass `null` to `parser.next` if you | ||
| 204 | // don't care about the extra information `Diagnostics` provides. | ||
| 205 | var diag: clap.Diagnostic = undefined; | ||
| 206 | |||
| 185 | // Because we use a streaming parser, we have to consume each argument parsed individually. | 207 | // Because we use a streaming parser, we have to consume each argument parsed individually. |
| 186 | while (try parser.next()) |arg| { | 208 | while (parser.next(&diag) catch |err| { |
| 209 | // Report useful error and exit | ||
| 210 | diag.report(std.io.getStdErr().outStream(), err) catch {}; | ||
| 211 | return err; | ||
| 212 | }) |arg| { | ||
| 187 | // arg.param will point to the parameter which matched the argument. | 213 | // arg.param will point to the parameter which matched the argument. |
| 188 | switch (arg.param.id) { | 214 | switch (arg.param.id) { |
| 189 | 'h' => debug.warn("Help!\n", .{}), | 215 | 'h' => debug.warn("Help!\n", .{}), |
| @@ -261,7 +261,7 @@ fn find(str: []const u8, f: []const u8) []const u8 { | |||
| 261 | pub fn Args(comptime Id: type, comptime params: []const Param(Id)) type { | 261 | pub fn Args(comptime Id: type, comptime params: []const Param(Id)) type { |
| 262 | return struct { | 262 | return struct { |
| 263 | arena: std.heap.ArenaAllocator, | 263 | arena: std.heap.ArenaAllocator, |
| 264 | clap: ComptimeClap(Id, params), | 264 | clap: ComptimeClap(Id, args.OsIterator, params), |
| 265 | exe_arg: ?[]const u8, | 265 | exe_arg: ?[]const u8, |
| 266 | 266 | ||
| 267 | pub fn deinit(a: *@This()) void { | 267 | pub fn deinit(a: *@This()) void { |
| @@ -287,15 +287,62 @@ pub fn Args(comptime Id: type, comptime params: []const Param(Id)) type { | |||
| 287 | }; | 287 | }; |
| 288 | } | 288 | } |
| 289 | 289 | ||
| 290 | /// Optional diagnostics used for reporting useful errors | ||
| 291 | pub const Diagnostic = struct { | ||
| 292 | name: Names, | ||
| 293 | |||
| 294 | /// 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 | pub fn report(diag: Diagnostic, stream: var, err: anyerror) !void { | ||
| 297 | const prefix = if (diag.name.short) |_| "-" else "--"; | ||
| 298 | const name = if (diag.name.short) |*c| @as(*const [1]u8, c)[0..] else diag.name.long.?; | ||
| 299 | |||
| 300 | switch (err) { | ||
| 301 | error.DoesntTakeValue => try stream.print("The argument '{}{}' does not take a value\n", .{ prefix, name }), | ||
| 302 | error.MissingValue => try stream.print("The argument '{}{}' requires a value but none was supplied\n", .{ prefix, name }), | ||
| 303 | error.InvalidArgument => try stream.print("Invalid argument '{}{}'\n", .{ prefix, name }), | ||
| 304 | else => try stream.print("Error while parsing arguments: {}\n", .{@errorName(err)}), | ||
| 305 | } | ||
| 306 | } | ||
| 307 | }; | ||
| 308 | |||
| 309 | fn testDiag(names: Names, err: anyerror, expected: []const u8) void { | ||
| 310 | var buf: [1024]u8 = undefined; | ||
| 311 | var slice_stream = io.fixedBufferStream(&buf); | ||
| 312 | (Diagnostic{ .name = names }).report(slice_stream.outStream(), err) catch unreachable; | ||
| 313 | |||
| 314 | const actual = slice_stream.getWritten(); | ||
| 315 | if (!mem.eql(u8, actual, expected)) { | ||
| 316 | debug.warn("\n============ Expected ============\n", .{}); | ||
| 317 | debug.warn("{}", .{expected}); | ||
| 318 | debug.warn("============= Actual =============\n", .{}); | ||
| 319 | debug.warn("{}", .{actual}); | ||
| 320 | testing.expect(false); | ||
| 321 | } | ||
| 322 | } | ||
| 323 | |||
| 324 | test "Diagnostic.report" { | ||
| 325 | testDiag(.{ .short = 'c' }, error.DoesntTakeValue, "The argument '-c' does not take a value\n"); | ||
| 326 | testDiag(.{ .long = "cc" }, error.DoesntTakeValue, "The argument '--cc' does not take a value\n"); | ||
| 327 | testDiag(.{ .short = 'c' }, error.MissingValue, "The argument '-c' requires a value but none was supplied\n"); | ||
| 328 | testDiag(.{ .long = "cc" }, error.MissingValue, "The argument '--cc' requires a value but none was supplied\n"); | ||
| 329 | testDiag(.{ .short = 'c' }, error.InvalidArgument, "Invalid argument '-c'\n"); | ||
| 330 | testDiag(.{ .long = "cc" }, error.InvalidArgument, "Invalid argument '--cc'\n"); | ||
| 331 | testDiag(.{ .short = 'c' }, error.SomethingElse, "Error while parsing arguments: SomethingElse\n"); | ||
| 332 | testDiag(.{ .long = "cc" }, error.SomethingElse, "Error while parsing arguments: SomethingElse\n"); | ||
| 333 | } | ||
| 334 | |||
| 290 | /// Parses the command line arguments passed into the program based on an | 335 | /// Parses the command line arguments passed into the program based on an |
| 291 | /// array of `Param`s. | 336 | /// array of `Param`s. |
| 292 | pub fn parse( | 337 | pub fn parse( |
| 293 | comptime Id: type, | 338 | comptime Id: type, |
| 294 | comptime params: []const Param(Id), | 339 | comptime params: []const Param(Id), |
| 295 | allocator: *mem.Allocator, | 340 | allocator: *mem.Allocator, |
| 341 | diag: ?*Diagnostic, | ||
| 296 | ) !Args(Id, params) { | 342 | ) !Args(Id, params) { |
| 297 | var iter = try args.OsIterator.init(allocator); | 343 | var iter = try args.OsIterator.init(allocator); |
| 298 | const clap = try ComptimeClap(Id, params).parse(allocator, args.OsIterator, &iter); | 344 | const Clap = ComptimeClap(Id, args.OsIterator, params); |
| 345 | const clap = try Clap.parse(allocator, &iter, diag); | ||
| 299 | return Args(Id, params){ | 346 | return Args(Id, params){ |
| 300 | .arena = iter.arena, | 347 | .arena = iter.arena, |
| 301 | .clap = clap, | 348 | .clap = clap, |
diff --git a/clap/comptime.zig b/clap/comptime.zig index 28ec42b..6846770 100644 --- a/clap/comptime.zig +++ b/clap/comptime.zig | |||
| @@ -6,7 +6,11 @@ const heap = std.heap; | |||
| 6 | const mem = std.mem; | 6 | const mem = std.mem; |
| 7 | const debug = std.debug; | 7 | const debug = std.debug; |
| 8 | 8 | ||
| 9 | pub fn ComptimeClap(comptime Id: type, comptime params: []const clap.Param(Id)) type { | 9 | pub fn ComptimeClap( |
| 10 | comptime Id: type, | ||
| 11 | comptime ArgIter: type, | ||
| 12 | comptime params: []const clap.Param(Id), | ||
| 13 | ) type { | ||
| 10 | var flags: usize = 0; | 14 | var flags: usize = 0; |
| 11 | var single_options: usize = 0; | 15 | var single_options: usize = 0; |
| 12 | var multi_options: usize = 0; | 16 | var multi_options: usize = 0; |
| @@ -38,7 +42,7 @@ pub fn ComptimeClap(comptime Id: type, comptime params: []const clap.Param(Id)) | |||
| 38 | pos: []const []const u8, | 42 | pos: []const []const u8, |
| 39 | allocator: *mem.Allocator, | 43 | allocator: *mem.Allocator, |
| 40 | 44 | ||
| 41 | pub fn parse(allocator: *mem.Allocator, comptime ArgIter: type, iter: *ArgIter) !@This() { | 45 | pub fn parse(allocator: *mem.Allocator, iter: *ArgIter, diag: ?*clap.Diagnostic) !@This() { |
| 42 | var multis = [_]std.ArrayList([]const u8){undefined} ** multi_options; | 46 | var multis = [_]std.ArrayList([]const u8){undefined} ** multi_options; |
| 43 | for (multis) |*multi| { | 47 | for (multis) |*multi| { |
| 44 | multi.* = std.ArrayList([]const u8).init(allocator); | 48 | multi.* = std.ArrayList([]const u8).init(allocator); |
| @@ -58,7 +62,7 @@ pub fn ComptimeClap(comptime Id: type, comptime params: []const clap.Param(Id)) | |||
| 58 | .params = converted_params, | 62 | .params = converted_params, |
| 59 | .iter = iter, | 63 | .iter = iter, |
| 60 | }; | 64 | }; |
| 61 | while (try stream.next()) |arg| { | 65 | while (try stream.next(diag)) |arg| { |
| 62 | const param = arg.param; | 66 | const param = arg.param; |
| 63 | if (param.names.long == null and param.names.short == null) { | 67 | if (param.names.long == null and param.names.short == null) { |
| 64 | try pos.append(arg.value.?); | 68 | try pos.append(arg.value.?); |
| @@ -143,7 +147,7 @@ pub fn ComptimeClap(comptime Id: type, comptime params: []const clap.Param(Id)) | |||
| 143 | } | 147 | } |
| 144 | 148 | ||
| 145 | test "clap.comptime.ComptimeClap" { | 149 | test "clap.comptime.ComptimeClap" { |
| 146 | const Clap = ComptimeClap(clap.Help, comptime &[_]clap.Param(clap.Help){ | 150 | const Clap = ComptimeClap(clap.Help, clap.args.SliceIterator, comptime &[_]clap.Param(clap.Help){ |
| 147 | clap.parseParam("-a, --aa ") catch unreachable, | 151 | clap.parseParam("-a, --aa ") catch unreachable, |
| 148 | clap.parseParam("-b, --bb ") catch unreachable, | 152 | clap.parseParam("-b, --bb ") catch unreachable, |
| 149 | clap.parseParam("-c, --cc <V>") catch unreachable, | 153 | clap.parseParam("-c, --cc <V>") catch unreachable, |
| @@ -160,7 +164,7 @@ test "clap.comptime.ComptimeClap" { | |||
| 160 | "-a", "-c", "0", "something", "-d", "a", "--dd", "b", | 164 | "-a", "-c", "0", "something", "-d", "a", "--dd", "b", |
| 161 | }, | 165 | }, |
| 162 | }; | 166 | }; |
| 163 | var args = try Clap.parse(&fb_allocator.allocator, clap.args.SliceIterator, &iter); | 167 | var args = try Clap.parse(&fb_allocator.allocator, &iter, null); |
| 164 | defer args.deinit(); | 168 | defer args.deinit(); |
| 165 | 169 | ||
| 166 | testing.expect(args.flag("-a")); | 170 | testing.expect(args.flag("-a")); |
diff --git a/clap/streaming.zig b/clap/streaming.zig index b843bff..90c4e02 100644 --- a/clap/streaming.zig +++ b/clap/streaming.zig | |||
| @@ -24,8 +24,8 @@ pub fn Arg(comptime Id: type) type { | |||
| 24 | pub fn StreamingClap(comptime Id: type, comptime ArgIterator: type) type { | 24 | pub fn StreamingClap(comptime Id: type, comptime ArgIterator: type) type { |
| 25 | return struct { | 25 | return struct { |
| 26 | const State = union(enum) { | 26 | const State = union(enum) { |
| 27 | Normal, | 27 | normal, |
| 28 | Chaining: Chaining, | 28 | chaining: Chaining, |
| 29 | 29 | ||
| 30 | const Chaining = struct { | 30 | const Chaining = struct { |
| 31 | arg: []const u8, | 31 | arg: []const u8, |
| @@ -35,49 +35,48 @@ pub fn StreamingClap(comptime Id: type, comptime ArgIterator: type) type { | |||
| 35 | 35 | ||
| 36 | params: []const clap.Param(Id), | 36 | params: []const clap.Param(Id), |
| 37 | iter: *ArgIterator, | 37 | iter: *ArgIterator, |
| 38 | state: State = State.Normal, | 38 | state: State = .normal, |
| 39 | 39 | ||
| 40 | /// Get the next ::Arg that matches a ::Param. | 40 | /// Get the next ::Arg that matches a ::Param. |
| 41 | pub fn next(parser: *@This()) !?Arg(Id) { | 41 | pub fn next(parser: *@This(), diag: ?*clap.Diagnostic) !?Arg(Id) { |
| 42 | const ArgInfo = struct { | 42 | const ArgInfo = struct { |
| 43 | const Kind = enum { | ||
| 44 | Long, | ||
| 45 | Short, | ||
| 46 | Positional, | ||
| 47 | }; | ||
| 48 | |||
| 49 | arg: []const u8, | 43 | arg: []const u8, |
| 50 | kind: Kind, | 44 | kind: enum { |
| 45 | long, | ||
| 46 | short, | ||
| 47 | positional, | ||
| 48 | }, | ||
| 51 | }; | 49 | }; |
| 52 | 50 | ||
| 53 | switch (parser.state) { | 51 | switch (parser.state) { |
| 54 | .Normal => { | 52 | .normal => { |
| 55 | const full_arg = (try parser.iter.next()) orelse return null; | 53 | const full_arg = (try parser.iter.next()) orelse return null; |
| 56 | const arg_info = if (mem.eql(u8, full_arg, "--") or mem.eql(u8, full_arg, "-")) | 54 | const arg_info = if (mem.eql(u8, full_arg, "--") or mem.eql(u8, full_arg, "-")) |
| 57 | ArgInfo{ .arg = full_arg, .kind = .Positional } | 55 | ArgInfo{ .arg = full_arg, .kind = .positional } |
| 58 | else if (mem.startsWith(u8, full_arg, "--")) | 56 | else if (mem.startsWith(u8, full_arg, "--")) |
| 59 | ArgInfo{ .arg = full_arg[2..], .kind = .Long } | 57 | ArgInfo{ .arg = full_arg[2..], .kind = .long } |
| 60 | else if (mem.startsWith(u8, full_arg, "-")) | 58 | else if (mem.startsWith(u8, full_arg, "-")) |
| 61 | ArgInfo{ .arg = full_arg[1..], .kind = .Short } | 59 | ArgInfo{ .arg = full_arg[1..], .kind = .short } |
| 62 | else | 60 | else |
| 63 | ArgInfo{ .arg = full_arg, .kind = .Positional }; | 61 | ArgInfo{ .arg = full_arg, .kind = .positional }; |
| 64 | 62 | ||
| 65 | const arg = arg_info.arg; | 63 | const arg = arg_info.arg; |
| 66 | const kind = arg_info.kind; | 64 | const kind = arg_info.kind; |
| 67 | const eql_index = mem.indexOfScalar(u8, arg, '='); | ||
| 68 | 65 | ||
| 69 | switch (kind) { | 66 | switch (kind) { |
| 70 | ArgInfo.Kind.Long => { | 67 | .long => { |
| 68 | const eql_index = mem.indexOfScalar(u8, arg, '='); | ||
| 69 | const name = if (eql_index) |i| arg[0..i] else arg; | ||
| 70 | const maybe_value = if (eql_index) |i| arg[i + 1 ..] else null; | ||
| 71 | |||
| 71 | for (parser.params) |*param| { | 72 | for (parser.params) |*param| { |
| 72 | const match = param.names.long orelse continue; | 73 | const match = param.names.long orelse continue; |
| 73 | const name = if (eql_index) |i| arg[0..i] else arg; | ||
| 74 | const maybe_value = if (eql_index) |i| arg[i + 1 ..] else null; | ||
| 75 | 74 | ||
| 76 | if (!mem.eql(u8, name, match)) | 75 | if (!mem.eql(u8, name, match)) |
| 77 | continue; | 76 | continue; |
| 78 | if (param.takes_value == .None) { | 77 | if (param.takes_value == .None) { |
| 79 | if (maybe_value != null) | 78 | if (maybe_value != null) |
| 80 | return error.DoesntTakeValue; | 79 | return err(diag, param.names, error.DoesntTakeValue); |
| 81 | 80 | ||
| 82 | return Arg(Id){ .param = param }; | 81 | return Arg(Id){ .param = param }; |
| 83 | } | 82 | } |
| @@ -86,19 +85,18 @@ pub fn StreamingClap(comptime Id: type, comptime ArgIterator: type) type { | |||
| 86 | if (maybe_value) |v| | 85 | if (maybe_value) |v| |
| 87 | break :blk v; | 86 | break :blk v; |
| 88 | 87 | ||
| 89 | break :blk (try parser.iter.next()) orelse return error.MissingValue; | 88 | break :blk (try parser.iter.next()) orelse |
| 89 | return err(diag, param.names, error.MissingValue); | ||
| 90 | }; | 90 | }; |
| 91 | 91 | ||
| 92 | return Arg(Id){ .param = param, .value = value }; | 92 | return Arg(Id){ .param = param, .value = value }; |
| 93 | } | 93 | } |
| 94 | }, | 94 | }, |
| 95 | ArgInfo.Kind.Short => { | 95 | .short => return try parser.chainging(.{ |
| 96 | return try parser.chainging(State.Chaining{ | 96 | .arg = full_arg, |
| 97 | .arg = full_arg, | 97 | .index = full_arg.len - arg.len, |
| 98 | .index = (full_arg.len - arg.len), | 98 | }, diag), |
| 99 | }); | 99 | .positional => { |
| 100 | }, | ||
| 101 | ArgInfo.Kind.Positional => { | ||
| 102 | for (parser.params) |*param| { | 100 | for (parser.params) |*param| { |
| 103 | if (param.names.long) |_| | 101 | if (param.names.long) |_| |
| 104 | continue; | 102 | continue; |
| @@ -110,13 +108,13 @@ pub fn StreamingClap(comptime Id: type, comptime ArgIterator: type) type { | |||
| 110 | }, | 108 | }, |
| 111 | } | 109 | } |
| 112 | 110 | ||
| 113 | return error.InvalidArgument; | 111 | return err(diag, .{ .long = arg }, error.InvalidArgument); |
| 114 | }, | 112 | }, |
| 115 | .Chaining => |state| return try parser.chainging(state), | 113 | .chaining => |state| return try parser.chainging(state, diag), |
| 116 | } | 114 | } |
| 117 | } | 115 | } |
| 118 | 116 | ||
| 119 | fn chainging(parser: *@This(), state: State.Chaining) !?Arg(Id) { | 117 | fn chainging(parser: *@This(), state: State.Chaining, diag: ?*clap.Diagnostic) !?Arg(Id) { |
| 120 | const arg = state.arg; | 118 | const arg = state.arg; |
| 121 | const index = state.index; | 119 | const index = state.index; |
| 122 | const next_index = index + 1; | 120 | const next_index = index + 1; |
| @@ -129,10 +127,10 @@ pub fn StreamingClap(comptime Id: type, comptime ArgIterator: type) type { | |||
| 129 | // Before we return, we have to set the new state of the clap | 127 | // Before we return, we have to set the new state of the clap |
| 130 | defer { | 128 | defer { |
| 131 | if (arg.len <= next_index or param.takes_value != .None) { | 129 | if (arg.len <= next_index or param.takes_value != .None) { |
| 132 | parser.state = State.Normal; | 130 | parser.state = .normal; |
| 133 | } else { | 131 | } else { |
| 134 | parser.state = State{ | 132 | parser.state = .{ |
| 135 | .Chaining = State.Chaining{ | 133 | .chaining = .{ |
| 136 | .arg = arg, | 134 | .arg = arg, |
| 137 | .index = next_index, | 135 | .index = next_index, |
| 138 | }, | 136 | }, |
| @@ -144,7 +142,9 @@ pub fn StreamingClap(comptime Id: type, comptime ArgIterator: type) type { | |||
| 144 | return Arg(Id){ .param = param }; | 142 | return Arg(Id){ .param = param }; |
| 145 | 143 | ||
| 146 | if (arg.len <= next_index) { | 144 | if (arg.len <= next_index) { |
| 147 | const value = (try parser.iter.next()) orelse return error.MissingValue; | 145 | const value = (try parser.iter.next()) orelse |
| 146 | return err(diag, param.names, error.MissingValue); | ||
| 147 | |||
| 148 | return Arg(Id){ .param = param, .value = value }; | 148 | return Arg(Id){ .param = param, .value = value }; |
| 149 | } | 149 | } |
| 150 | 150 | ||
| @@ -154,7 +154,13 @@ pub fn StreamingClap(comptime Id: type, comptime ArgIterator: type) type { | |||
| 154 | return Arg(Id){ .param = param, .value = arg[next_index..] }; | 154 | return Arg(Id){ .param = param, .value = arg[next_index..] }; |
| 155 | } | 155 | } |
| 156 | 156 | ||
| 157 | return error.InvalidArgument; | 157 | return err(diag, .{ .short = arg[index] }, error.InvalidArgument); |
| 158 | } | ||
| 159 | |||
| 160 | fn err(diag: ?*clap.Diagnostic, names: clap.Names, _err: var) @TypeOf(_err) { | ||
| 161 | if (diag) |d| | ||
| 162 | d.name = names; | ||
| 163 | return _err; | ||
| 158 | } | 164 | } |
| 159 | }; | 165 | }; |
| 160 | } | 166 | } |
| @@ -167,7 +173,7 @@ fn testNoErr(params: []const clap.Param(u8), args_strings: []const []const u8, r | |||
| 167 | }; | 173 | }; |
| 168 | 174 | ||
| 169 | for (results) |res| { | 175 | for (results) |res| { |
| 170 | const arg = (c.next() catch unreachable) orelse unreachable; | 176 | const arg = (c.next(null) catch unreachable) orelse unreachable; |
| 171 | testing.expectEqual(res.param, arg.param); | 177 | testing.expectEqual(res.param, arg.param); |
| 172 | const expected_value = res.value orelse { | 178 | const expected_value = res.value orelse { |
| 173 | testing.expectEqual(@as(@TypeOf(arg.value), null), arg.value); | 179 | testing.expectEqual(@as(@TypeOf(arg.value), null), arg.value); |
| @@ -177,7 +183,7 @@ fn testNoErr(params: []const clap.Param(u8), args_strings: []const []const u8, r | |||
| 177 | testing.expectEqualSlices(u8, expected_value, actual_value); | 183 | testing.expectEqualSlices(u8, expected_value, actual_value); |
| 178 | } | 184 | } |
| 179 | 185 | ||
| 180 | if (c.next() catch unreachable) |_| | 186 | if (c.next(null) catch unreachable) |_| |
| 181 | unreachable; | 187 | unreachable; |
| 182 | } | 188 | } |
| 183 | 189 | ||
diff --git a/example/comptime-clap.zig b/example/comptime-clap.zig index d709e48..530c7e6 100644 --- a/example/comptime-clap.zig +++ b/example/comptime-clap.zig | |||
| @@ -16,14 +16,24 @@ pub fn main() !void { | |||
| 16 | .takes_value = .One, | 16 | .takes_value = .One, |
| 17 | }, | 17 | }, |
| 18 | }; | 18 | }; |
| 19 | const Clap = clap.ComptimeClap(clap.Help, clap.args.OsIterator, ¶ms); | ||
| 19 | 20 | ||
| 20 | // We then initialize an argument iterator. We will use the OsIterator as it nicely | 21 | // We then initialize an argument iterator. We will use the OsIterator as it nicely |
| 21 | // wraps iterating over arguments the most efficient way on each os. | 22 | // wraps iterating over arguments the most efficient way on each os. |
| 22 | var iter = try clap.args.OsIterator.init(allocator); | 23 | var iter = try clap.args.OsIterator.init(allocator); |
| 23 | defer iter.deinit(); | 24 | defer iter.deinit(); |
| 24 | 25 | ||
| 26 | // Initalize our diagnostics, which can be used for reporting useful errors. | ||
| 27 | // This is optional. You can also just pass `null` to `parser.next` if you | ||
| 28 | // don't care about the extra information `Diagnostics` provides. | ||
| 29 | var diag: clap.Diagnostic = undefined; | ||
| 30 | |||
| 25 | // Parse the arguments | 31 | // Parse the arguments |
| 26 | var args = try clap.ComptimeClap(clap.Help, ¶ms).parse(allocator, clap.args.OsIterator, &iter); | 32 | var args = Clap.parse(allocator, &iter, &diag) catch |err| { |
| 33 | // Report useful error and exit | ||
| 34 | diag.report(std.io.getStdErr().outStream(), err) catch {}; | ||
| 35 | return err; | ||
| 36 | }; | ||
| 27 | defer args.deinit(); | 37 | defer args.deinit(); |
| 28 | 38 | ||
| 29 | if (args.flag("--help")) | 39 | if (args.flag("--help")) |
diff --git a/example/simple-error.zig b/example/simple-error.zig index 2c403fc..3c62f0e 100644 --- a/example/simple-error.zig +++ b/example/simple-error.zig | |||
| @@ -2,13 +2,11 @@ const std = @import("std"); | |||
| 2 | const clap = @import("clap"); | 2 | const clap = @import("clap"); |
| 3 | 3 | ||
| 4 | pub fn main() !void { | 4 | pub fn main() !void { |
| 5 | // First we specify what parameters our program can take. | ||
| 6 | // We can use `parseParam` to parse a string to a `Param(Help)` | ||
| 7 | const params = comptime [_]clap.Param(clap.Help){ | 5 | const params = comptime [_]clap.Param(clap.Help){ |
| 8 | clap.parseParam("-h, --help Display this help and exit.") catch unreachable, | 6 | clap.parseParam("-h, --help Display this help and exit.") catch unreachable, |
| 9 | }; | 7 | }; |
| 10 | 8 | ||
| 11 | var args = try clap.parse(clap.Help, ¶ms, std.heap.direct_allocator); | 9 | var args = try clap.parse(clap.Help, ¶ms, std.heap.direct_allocator, null); |
| 12 | defer args.deinit(); | 10 | defer args.deinit(); |
| 13 | 11 | ||
| 14 | _ = args.flag("--helps"); | 12 | _ = args.flag("--helps"); |
diff --git a/example/simple.zig b/example/simple.zig index adea9f9..f7b5953 100644 --- a/example/simple.zig +++ b/example/simple.zig | |||
| @@ -15,7 +15,16 @@ pub fn main() !void { | |||
| 15 | }, | 15 | }, |
| 16 | }; | 16 | }; |
| 17 | 17 | ||
| 18 | var args = try clap.parse(clap.Help, ¶ms, std.heap.page_allocator); | 18 | // Initalize our diagnostics, which can be used for reporting useful errors. |
| 19 | // This is optional. You can also just pass `null` to `parser.next` if you | ||
| 20 | // don't care about the extra information `Diagnostics` provides. | ||
| 21 | var diag: clap.Diagnostic = undefined; | ||
| 22 | |||
| 23 | var args = clap.parse(clap.Help, ¶ms, std.heap.page_allocator, &diag) catch |err| { | ||
| 24 | // Report useful error and exit | ||
| 25 | diag.report(std.io.getStdErr().outStream(), err) catch {}; | ||
| 26 | return err; | ||
| 27 | }; | ||
| 19 | defer args.deinit(); | 28 | defer args.deinit(); |
| 20 | 29 | ||
| 21 | if (args.flag("--help")) | 30 | if (args.flag("--help")) |
diff --git a/example/streaming-clap.zig b/example/streaming-clap.zig index b92a9e6..941070f 100644 --- a/example/streaming-clap.zig +++ b/example/streaming-clap.zig | |||
| @@ -34,8 +34,17 @@ pub fn main() !void { | |||
| 34 | .iter = &iter, | 34 | .iter = &iter, |
| 35 | }; | 35 | }; |
| 36 | 36 | ||
| 37 | // Initalize our diagnostics, which can be used for reporting useful errors. | ||
| 38 | // This is optional. You can also just pass `null` to `parser.next` if you | ||
| 39 | // don't care about the extra information `Diagnostics` provides. | ||
| 40 | var diag: clap.Diagnostic = undefined; | ||
| 41 | |||
| 37 | // Because we use a streaming parser, we have to consume each argument parsed individually. | 42 | // Because we use a streaming parser, we have to consume each argument parsed individually. |
| 38 | while (try parser.next()) |arg| { | 43 | while (parser.next(&diag) catch |err| { |
| 44 | // Report useful error and exit | ||
| 45 | diag.report(std.io.getStdErr().outStream(), err) catch {}; | ||
| 46 | return err; | ||
| 47 | }) |arg| { | ||
| 39 | // arg.param will point to the parameter which matched the argument. | 48 | // arg.param will point to the parameter which matched the argument. |
| 40 | switch (arg.param.id) { | 49 | switch (arg.param.id) { |
| 41 | 'h' => debug.warn("Help!\n", .{}), | 50 | 'h' => debug.warn("Help!\n", .{}), |