summaryrefslogtreecommitdiff
path: root/clap
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--clap.zig128
-rw-r--r--clap/args.zig86
-rw-r--r--clap/comptime.zig70
-rw-r--r--clap/streaming.zig66
4 files changed, 182 insertions, 168 deletions
diff --git a/clap.zig b/clap.zig
index 8d721fa..ee713a7 100644
--- a/clap.zig
+++ b/clap.zig
@@ -122,81 +122,81 @@ fn parseParamRest(line: []const u8) Param(Help) {
122 return .{ .id = .{ .msg = mem.trim(u8, line, " \t") } }; 122 return .{ .id = .{ .msg = mem.trim(u8, line, " \t") } };
123} 123}
124 124
125fn expectParam(expect: Param(Help), actual: Param(Help)) void { 125fn expectParam(expect: Param(Help), actual: Param(Help)) !void {
126 testing.expectEqualStrings(expect.id.msg, actual.id.msg); 126 try testing.expectEqualStrings(expect.id.msg, actual.id.msg);
127 testing.expectEqualStrings(expect.id.value, actual.id.value); 127 try testing.expectEqualStrings(expect.id.value, actual.id.value);
128 testing.expectEqual(expect.names.short, actual.names.short); 128 try testing.expectEqual(expect.names.short, actual.names.short);
129 testing.expectEqual(expect.takes_value, actual.takes_value); 129 try testing.expectEqual(expect.takes_value, actual.takes_value);
130 if (expect.names.long) |long| { 130 if (expect.names.long) |long| {
131 testing.expectEqualStrings(long, actual.names.long.?); 131 try testing.expectEqualStrings(long, actual.names.long.?);
132 } else { 132 } else {
133 testing.expectEqual(@as(?[]const u8, null), actual.names.long); 133 try testing.expectEqual(@as(?[]const u8, null), actual.names.long);
134 } 134 }
135} 135}
136 136
137test "parseParam" { 137test "parseParam" {
138 expectParam(Param(Help){ 138 try expectParam(Param(Help){
139 .id = .{ .msg = "Help text", .value = "value" }, 139 .id = .{ .msg = "Help text", .value = "value" },
140 .names = .{ .short = 's', .long = "long" }, 140 .names = .{ .short = 's', .long = "long" },
141 .takes_value = .one, 141 .takes_value = .one,
142 }, try parseParam("-s, --long <value> Help text")); 142 }, try parseParam("-s, --long <value> Help text"));
143 143
144 expectParam(Param(Help){ 144 try expectParam(Param(Help){
145 .id = .{ .msg = "Help text", .value = "value" }, 145 .id = .{ .msg = "Help text", .value = "value" },
146 .names = .{ .short = 's', .long = "long" }, 146 .names = .{ .short = 's', .long = "long" },
147 .takes_value = .many, 147 .takes_value = .many,
148 }, try parseParam("-s, --long <value>... Help text")); 148 }, try parseParam("-s, --long <value>... Help text"));
149 149
150 expectParam(Param(Help){ 150 try expectParam(Param(Help){
151 .id = .{ .msg = "Help text", .value = "value" }, 151 .id = .{ .msg = "Help text", .value = "value" },
152 .names = .{ .long = "long" }, 152 .names = .{ .long = "long" },
153 .takes_value = .one, 153 .takes_value = .one,
154 }, try parseParam("--long <value> Help text")); 154 }, try parseParam("--long <value> Help text"));
155 155
156 expectParam(Param(Help){ 156 try expectParam(Param(Help){
157 .id = .{ .msg = "Help text", .value = "value" }, 157 .id = .{ .msg = "Help text", .value = "value" },
158 .names = .{ .short = 's' }, 158 .names = .{ .short = 's' },
159 .takes_value = .one, 159 .takes_value = .one,
160 }, try parseParam("-s <value> Help text")); 160 }, try parseParam("-s <value> Help text"));
161 161
162 expectParam(Param(Help){ 162 try expectParam(Param(Help){
163 .id = .{ .msg = "Help text" }, 163 .id = .{ .msg = "Help text" },
164 .names = .{ .short = 's', .long = "long" }, 164 .names = .{ .short = 's', .long = "long" },
165 }, try parseParam("-s, --long Help text")); 165 }, try parseParam("-s, --long Help text"));
166 166
167 expectParam(Param(Help){ 167 try expectParam(Param(Help){
168 .id = .{ .msg = "Help text" }, 168 .id = .{ .msg = "Help text" },
169 .names = .{ .short = 's' }, 169 .names = .{ .short = 's' },
170 }, try parseParam("-s Help text")); 170 }, try parseParam("-s Help text"));
171 171
172 expectParam(Param(Help){ 172 try expectParam(Param(Help){
173 .id = .{ .msg = "Help text" }, 173 .id = .{ .msg = "Help text" },
174 .names = .{ .long = "long" }, 174 .names = .{ .long = "long" },
175 }, try parseParam("--long Help text")); 175 }, try parseParam("--long Help text"));
176 176
177 expectParam(Param(Help){ 177 try expectParam(Param(Help){
178 .id = .{ .msg = "Help text", .value = "A | B" }, 178 .id = .{ .msg = "Help text", .value = "A | B" },
179 .names = .{ .long = "long" }, 179 .names = .{ .long = "long" },
180 .takes_value = .one, 180 .takes_value = .one,
181 }, try parseParam("--long <A | B> Help text")); 181 }, try parseParam("--long <A | B> Help text"));
182 182
183 expectParam(Param(Help){ 183 try expectParam(Param(Help){
184 .id = .{ .msg = "Help text", .value = "A" }, 184 .id = .{ .msg = "Help text", .value = "A" },
185 .names = .{}, 185 .names = .{},
186 .takes_value = .one, 186 .takes_value = .one,
187 }, try parseParam("<A> Help text")); 187 }, try parseParam("<A> Help text"));
188 188
189 expectParam(Param(Help){ 189 try expectParam(Param(Help){
190 .id = .{ .msg = "Help text", .value = "A" }, 190 .id = .{ .msg = "Help text", .value = "A" },
191 .names = .{}, 191 .names = .{},
192 .takes_value = .many, 192 .takes_value = .many,
193 }, try parseParam("<A>... Help text")); 193 }, try parseParam("<A>... Help text"));
194 194
195 testing.expectError(error.TrailingComma, parseParam("--long, Help")); 195 try testing.expectError(error.TrailingComma, parseParam("--long, Help"));
196 testing.expectError(error.TrailingComma, parseParam("-s, Help")); 196 try testing.expectError(error.TrailingComma, parseParam("-s, Help"));
197 testing.expectError(error.InvalidShortParam, parseParam("-ss Help")); 197 try testing.expectError(error.InvalidShortParam, parseParam("-ss Help"));
198 testing.expectError(error.InvalidShortParam, parseParam("-ss <value> Help")); 198 try testing.expectError(error.InvalidShortParam, parseParam("-ss <value> Help"));
199 testing.expectError(error.InvalidShortParam, parseParam("- Help")); 199 try testing.expectError(error.InvalidShortParam, parseParam("- Help"));
200} 200}
201 201
202/// Optional diagnostics used for reporting useful errors 202/// Optional diagnostics used for reporting useful errors
@@ -227,24 +227,24 @@ pub const Diagnostic = struct {
227 } 227 }
228}; 228};
229 229
230fn testDiag(diag: Diagnostic, err: anyerror, expected: []const u8) void { 230fn testDiag(diag: Diagnostic, err: anyerror, expected: []const u8) !void {
231 var buf: [1024]u8 = undefined; 231 var buf: [1024]u8 = undefined;
232 var slice_stream = io.fixedBufferStream(&buf); 232 var slice_stream = io.fixedBufferStream(&buf);
233 diag.report(slice_stream.writer(), err) catch unreachable; 233 diag.report(slice_stream.writer(), err) catch unreachable;
234 testing.expectEqualStrings(expected, slice_stream.getWritten()); 234 try testing.expectEqualStrings(expected, slice_stream.getWritten());
235} 235}
236 236
237test "Diagnostic.report" { 237test "Diagnostic.report" {
238 testDiag(.{ .arg = "c" }, error.InvalidArgument, "Invalid argument 'c'\n"); 238 try testDiag(.{ .arg = "c" }, error.InvalidArgument, "Invalid argument 'c'\n");
239 testDiag(.{ .name = .{ .long = "cc" } }, error.InvalidArgument, "Invalid argument '--cc'\n"); 239 try testDiag(.{ .name = .{ .long = "cc" } }, error.InvalidArgument, "Invalid argument '--cc'\n");
240 testDiag(.{ .name = .{ .short = 'c' } }, error.DoesntTakeValue, "The argument '-c' does not take a value\n"); 240 try testDiag(.{ .name = .{ .short = 'c' } }, error.DoesntTakeValue, "The argument '-c' does not take a value\n");
241 testDiag(.{ .name = .{ .long = "cc" } }, error.DoesntTakeValue, "The argument '--cc' does not take a value\n"); 241 try testDiag(.{ .name = .{ .long = "cc" } }, error.DoesntTakeValue, "The argument '--cc' does not take a value\n");
242 testDiag(.{ .name = .{ .short = 'c' } }, error.MissingValue, "The argument '-c' requires a value but none was supplied\n"); 242 try testDiag(.{ .name = .{ .short = 'c' } }, error.MissingValue, "The argument '-c' requires a value but none was supplied\n");
243 testDiag(.{ .name = .{ .long = "cc" } }, error.MissingValue, "The argument '--cc' requires a value but none was supplied\n"); 243 try testDiag(.{ .name = .{ .long = "cc" } }, error.MissingValue, "The argument '--cc' requires a value but none was supplied\n");
244 testDiag(.{ .name = .{ .short = 'c' } }, error.InvalidArgument, "Invalid argument '-c'\n"); 244 try testDiag(.{ .name = .{ .short = 'c' } }, error.InvalidArgument, "Invalid argument '-c'\n");
245 testDiag(.{ .name = .{ .long = "cc" } }, error.InvalidArgument, "Invalid argument '--cc'\n"); 245 try testDiag(.{ .name = .{ .long = "cc" } }, error.InvalidArgument, "Invalid argument '--cc'\n");
246 testDiag(.{ .name = .{ .short = 'c' } }, error.SomethingElse, "Error while parsing arguments: SomethingElse\n"); 246 try testDiag(.{ .name = .{ .short = 'c' } }, error.SomethingElse, "Error while parsing arguments: SomethingElse\n");
247 testDiag(.{ .name = .{ .long = "cc" } }, error.SomethingElse, "Error while parsing arguments: SomethingElse\n"); 247 try testDiag(.{ .name = .{ .long = "cc" } }, error.SomethingElse, "Error while parsing arguments: SomethingElse\n");
248} 248}
249 249
250pub fn Args(comptime Id: type, comptime params: []const Param(Id)) type { 250pub fn Args(comptime Id: type, comptime params: []const Param(Id)) type {
@@ -338,7 +338,7 @@ pub fn helpFull(
338 const max_spacing = blk: { 338 const max_spacing = blk: {
339 var res: usize = 0; 339 var res: usize = 0;
340 for (params) |param| { 340 for (params) |param| {
341 var cs = io.countingOutStream(io.null_writer); 341 var cs = io.countingWriter(io.null_writer);
342 try printParam(cs.writer(), Id, param, Error, context, valueText); 342 try printParam(cs.writer(), Id, param, Error, context, valueText);
343 if (res < cs.bytes_written) 343 if (res < cs.bytes_written)
344 res = @intCast(usize, cs.bytes_written); 344 res = @intCast(usize, cs.bytes_written);
@@ -355,7 +355,7 @@ pub fn helpFull(
355 try stream.print("\t", .{}); 355 try stream.print("\t", .{});
356 try printParam(cs.writer(), Id, param, Error, context, valueText); 356 try printParam(cs.writer(), Id, param, Error, context, valueText);
357 try stream.writeByteNTimes(' ', max_spacing - @intCast(usize, cs.bytes_written)); 357 try stream.writeByteNTimes(' ', max_spacing - @intCast(usize, cs.bytes_written));
358 try stream.print("\t{}\n", .{try helpText(context, param)}); 358 try stream.print("\t{s}\n", .{try helpText(context, param)});
359 } 359 }
360} 360}
361 361
@@ -450,7 +450,7 @@ test "clap.help" {
450 @setEvalBranchQuota(10000); 450 @setEvalBranchQuota(10000);
451 try help( 451 try help(
452 slice_stream.writer(), 452 slice_stream.writer(),
453 comptime &[_]Param(Help){ 453 comptime &.{
454 parseParam("-a Short flag.") catch unreachable, 454 parseParam("-a Short flag.") catch unreachable,
455 parseParam("-b <V1> Short option.") catch unreachable, 455 parseParam("-b <V1> Short option.") catch unreachable,
456 parseParam("--aa Long flag.") catch unreachable, 456 parseParam("--aa Long flag.") catch unreachable,
@@ -471,7 +471,7 @@ test "clap.help" {
471 "\t-d, --dd <V3> \tBoth option.\n" ++ 471 "\t-d, --dd <V3> \tBoth option.\n" ++
472 "\t-d, --dd <V3>...\tBoth repeated option.\n"; 472 "\t-d, --dd <V3>...\tBoth repeated option.\n";
473 473
474 testing.expectEqualStrings(expected, slice_stream.getWritten()); 474 try testing.expectEqualStrings(expected, slice_stream.getWritten());
475} 475}
476 476
477/// Will print a usage message in the following format: 477/// Will print a usage message in the following format:
@@ -569,40 +569,40 @@ fn testUsage(expected: []const u8, params: []const Param(Help)) !void {
569 var buf: [1024]u8 = undefined; 569 var buf: [1024]u8 = undefined;
570 var fbs = io.fixedBufferStream(&buf); 570 var fbs = io.fixedBufferStream(&buf);
571 try usage(fbs.writer(), params); 571 try usage(fbs.writer(), params);
572 testing.expectEqualStrings(expected, fbs.getWritten()); 572 try testing.expectEqualStrings(expected, fbs.getWritten());
573} 573}
574 574
575test "usage" { 575test "usage" {
576 @setEvalBranchQuota(100000); 576 @setEvalBranchQuota(100000);
577 try testUsage("[-ab]", comptime &[_]Param(Help){ 577 try testUsage("[-ab]", &.{
578 parseParam("-a") catch unreachable, 578 try parseParam("-a"),
579 parseParam("-b") catch unreachable, 579 try parseParam("-b"),
580 }); 580 });
581 try testUsage("[-a <value>] [-b <v>]", comptime &[_]Param(Help){ 581 try testUsage("[-a <value>] [-b <v>]", &.{
582 parseParam("-a <value>") catch unreachable, 582 try parseParam("-a <value>"),
583 parseParam("-b <v>") catch unreachable, 583 try parseParam("-b <v>"),
584 }); 584 });
585 try testUsage("[--a] [--b]", comptime &[_]Param(Help){ 585 try testUsage("[--a] [--b]", &.{
586 parseParam("--a") catch unreachable, 586 try parseParam("--a"),
587 parseParam("--b") catch unreachable, 587 try parseParam("--b"),
588 }); 588 });
589 try testUsage("[--a <value>] [--b <v>]", comptime &[_]Param(Help){ 589 try testUsage("[--a <value>] [--b <v>]", &.{
590 parseParam("--a <value>") catch unreachable, 590 try parseParam("--a <value>"),
591 parseParam("--b <v>") catch unreachable, 591 try parseParam("--b <v>"),
592 }); 592 });
593 try testUsage("<file>", comptime &[_]Param(Help){ 593 try testUsage("<file>", &.{
594 parseParam("<file>") catch unreachable, 594 try parseParam("<file>"),
595 }); 595 });
596 try testUsage("[-ab] [-c <value>] [-d <v>] [--e] [--f] [--g <value>] [--h <v>] [-i <v>...] <file>", comptime &[_]Param(Help){ 596 try testUsage("[-ab] [-c <value>] [-d <v>] [--e] [--f] [--g <value>] [--h <v>] [-i <v>...] <file>", &.{
597 parseParam("-a") catch unreachable, 597 try parseParam("-a"),
598 parseParam("-b") catch unreachable, 598 try parseParam("-b"),
599 parseParam("-c <value>") catch unreachable, 599 try parseParam("-c <value>"),
600 parseParam("-d <v>") catch unreachable, 600 try parseParam("-d <v>"),
601 parseParam("--e") catch unreachable, 601 try parseParam("--e"),
602 parseParam("--f") catch unreachable, 602 try parseParam("--f"),
603 parseParam("--g <value>") catch unreachable, 603 try parseParam("--g <value>"),
604 parseParam("--h <v>") catch unreachable, 604 try parseParam("--h <v>"),
605 parseParam("-i <v>...") catch unreachable, 605 try parseParam("-i <v>..."),
606 parseParam("<file>") catch unreachable, 606 try parseParam("<file>"),
607 }); 607 });
608} 608}
diff --git a/clap/args.zig b/clap/args.zig
index f9ad218..555e8ab 100644
--- a/clap/args.zig
+++ b/clap/args.zig
@@ -34,8 +34,8 @@ pub const SliceIterator = struct {
34}; 34};
35 35
36test "SliceIterator" { 36test "SliceIterator" {
37 const args = &[_][]const u8{ "A", "BB", "CCC" }; 37 const args = [_][]const u8{ "A", "BB", "CCC" };
38 var iter = SliceIterator{ .args = args }; 38 var iter = SliceIterator{ .args = &args };
39 39
40 for (args) |a| { 40 for (args) |a| {
41 const b = try iter.next(); 41 const b = try iter.next();
@@ -266,76 +266,76 @@ pub const ShellIterator = struct {
266 } 266 }
267}; 267};
268 268
269fn testShellIteratorOk(str: []const u8, allocations: usize, expect: []const []const u8) void { 269fn testShellIteratorOk(str: []const u8, allocations: usize, expect: []const []const u8) !void {
270 var allocator = testing.FailingAllocator.init(testing.allocator, allocations); 270 var allocator = testing.FailingAllocator.init(testing.allocator, allocations);
271 var it = ShellIterator.init(&allocator.allocator, str); 271 var it = ShellIterator.init(&allocator.allocator, str);
272 defer it.deinit(); 272 defer it.deinit();
273 273
274 for (expect) |e| { 274 for (expect) |e| {
275 if (it.next()) |actual| { 275 if (it.next()) |actual| {
276 testing.expect(actual != null); 276 try testing.expect(actual != null);
277 testing.expectEqualStrings(e, actual.?); 277 try testing.expectEqualStrings(e, actual.?);
278 } else |err| testing.expectEqual(@as(anyerror![]const u8, e), err); 278 } else |err| try testing.expectEqual(@as(anyerror![]const u8, e), err);
279 } 279 }
280 280
281 if (it.next()) |actual| { 281 if (it.next()) |actual| {
282 testing.expectEqual(@as(?[]const u8, null), actual); 282 try testing.expectEqual(@as(?[]const u8, null), actual);
283 testing.expectEqual(allocations, allocator.allocations); 283 try testing.expectEqual(allocations, allocator.allocations);
284 } else |err| testing.expectEqual(@as(anyerror!void, {}), err); 284 } else |err| try testing.expectEqual(@as(anyerror!void, {}), err);
285} 285}
286 286
287fn testShellIteratorErr(str: []const u8, expect: anyerror) void { 287fn testShellIteratorErr(str: []const u8, expect: anyerror) !void {
288 var it = ShellIterator.init(testing.allocator, str); 288 var it = ShellIterator.init(testing.allocator, str);
289 defer it.deinit(); 289 defer it.deinit();
290 290
291 while (it.next() catch |err| { 291 while (it.next() catch |err| {
292 testing.expectError(expect, @as(anyerror!void, err)); 292 try testing.expectError(expect, @as(anyerror!void, err));
293 return; 293 return;
294 }) |_| {} 294 }) |_| {}
295 295
296 testing.expectError(expect, @as(anyerror!void, {})); 296 try testing.expectError(expect, @as(anyerror!void, {}));
297} 297}
298 298
299test "ShellIterator" { 299test "ShellIterator" {
300 testShellIteratorOk("a", 0, &[_][]const u8{"a"}); 300 try testShellIteratorOk("a", 0, &.{"a"});
301 testShellIteratorOk("'a'", 0, &[_][]const u8{"a"}); 301 try testShellIteratorOk("'a'", 0, &.{"a"});
302 testShellIteratorOk("\"a\"", 0, &[_][]const u8{"a"}); 302 try testShellIteratorOk("\"a\"", 0, &.{"a"});
303 testShellIteratorOk("a b", 0, &[_][]const u8{ "a", "b" }); 303 try testShellIteratorOk("a b", 0, &.{ "a", "b" });
304 testShellIteratorOk("'a' b", 0, &[_][]const u8{ "a", "b" }); 304 try testShellIteratorOk("'a' b", 0, &.{ "a", "b" });
305 testShellIteratorOk("\"a\" b", 0, &[_][]const u8{ "a", "b" }); 305 try testShellIteratorOk("\"a\" b", 0, &.{ "a", "b" });
306 testShellIteratorOk("a 'b'", 0, &[_][]const u8{ "a", "b" }); 306 try testShellIteratorOk("a 'b'", 0, &.{ "a", "b" });
307 testShellIteratorOk("a \"b\"", 0, &[_][]const u8{ "a", "b" }); 307 try testShellIteratorOk("a \"b\"", 0, &.{ "a", "b" });
308 testShellIteratorOk("'a b'", 0, &[_][]const u8{"a b"}); 308 try testShellIteratorOk("'a b'", 0, &.{"a b"});
309 testShellIteratorOk("\"a b\"", 0, &[_][]const u8{"a b"}); 309 try testShellIteratorOk("\"a b\"", 0, &.{"a b"});
310 testShellIteratorOk("\"a\"\"b\"", 1, &[_][]const u8{"ab"}); 310 try testShellIteratorOk("\"a\"\"b\"", 1, &.{"ab"});
311 testShellIteratorOk("'a''b'", 1, &[_][]const u8{"ab"}); 311 try testShellIteratorOk("'a''b'", 1, &.{"ab"});
312 testShellIteratorOk("'a'b", 1, &[_][]const u8{"ab"}); 312 try testShellIteratorOk("'a'b", 1, &.{"ab"});
313 testShellIteratorOk("a'b'", 1, &[_][]const u8{"ab"}); 313 try testShellIteratorOk("a'b'", 1, &.{"ab"});
314 testShellIteratorOk("a\\ b", 1, &[_][]const u8{"a b"}); 314 try testShellIteratorOk("a\\ b", 1, &.{"a b"});
315 testShellIteratorOk("\"a\\ b\"", 1, &[_][]const u8{"a b"}); 315 try testShellIteratorOk("\"a\\ b\"", 1, &.{"a b"});
316 testShellIteratorOk("'a\\ b'", 0, &[_][]const u8{"a\\ b"}); 316 try testShellIteratorOk("'a\\ b'", 0, &.{"a\\ b"});
317 testShellIteratorOk(" a b ", 0, &[_][]const u8{ "a", "b" }); 317 try testShellIteratorOk(" a b ", 0, &.{ "a", "b" });
318 testShellIteratorOk("\\ \\ ", 0, &[_][]const u8{ " ", " " }); 318 try testShellIteratorOk("\\ \\ ", 0, &.{ " ", " " });
319 319
320 testShellIteratorOk( 320 try testShellIteratorOk(
321 \\printf 'run\nuninstall\n' 321 \\printf 'run\nuninstall\n'
322 , 0, &[_][]const u8{ "printf", "run\\nuninstall\\n" }); 322 , 0, &.{ "printf", "run\\nuninstall\\n" });
323 testShellIteratorOk( 323 try testShellIteratorOk(
324 \\setsid -f steam "steam://$action/$id" 324 \\setsid -f steam "steam://$action/$id"
325 , 0, &[_][]const u8{ "setsid", "-f", "steam", "steam://$action/$id" }); 325 , 0, &.{ "setsid", "-f", "steam", "steam://$action/$id" });
326 testShellIteratorOk( 326 try testShellIteratorOk(
327 \\xargs -I% rg --no-heading --no-line-number --only-matching 327 \\xargs -I% rg --no-heading --no-line-number --only-matching
328 \\ --case-sensitive --multiline --text --byte-offset '(?-u)%' $@ 328 \\ --case-sensitive --multiline --text --byte-offset '(?-u)%' $@
329 \\ 329 \\
330 , 0, &[_][]const u8{ 330 , 0, &.{
331 "xargs", "-I%", "rg", "--no-heading", 331 "xargs", "-I%", "rg", "--no-heading",
332 "--no-line-number", "--only-matching", "--case-sensitive", "--multiline", 332 "--no-line-number", "--only-matching", "--case-sensitive", "--multiline",
333 "--text", "--byte-offset", "(?-u)%", "$@", 333 "--text", "--byte-offset", "(?-u)%", "$@",
334 }); 334 });
335 335
336 testShellIteratorErr("'a", error.QuoteNotClosed); 336 try testShellIteratorErr("'a", error.QuoteNotClosed);
337 testShellIteratorErr("'a\\", error.QuoteNotClosed); 337 try testShellIteratorErr("'a\\", error.QuoteNotClosed);
338 testShellIteratorErr("\"a", error.QuoteNotClosed); 338 try testShellIteratorErr("\"a", error.QuoteNotClosed);
339 testShellIteratorErr("\"a\\", error.QuoteNotClosed); 339 try testShellIteratorErr("\"a\\", error.QuoteNotClosed);
340 testShellIteratorErr("a\\", error.DanglingEscape); 340 try testShellIteratorErr("a\\", error.DanglingEscape);
341} 341}
diff --git a/clap/comptime.zig b/clap/comptime.zig
index 122ff16..7808f52 100644
--- a/clap/comptime.zig
+++ b/clap/comptime.zig
@@ -14,7 +14,7 @@ pub fn ComptimeClap(
14 var flags: usize = 0; 14 var flags: usize = 0;
15 var single_options: usize = 0; 15 var single_options: usize = 0;
16 var multi_options: usize = 0; 16 var multi_options: usize = 0;
17 var converted_params: []const clap.Param(usize) = &[_]clap.Param(usize){}; 17 var converted_params: []const clap.Param(usize) = &.{};
18 for (params) |param| { 18 for (params) |param| {
19 var index: usize = 0; 19 var index: usize = 0;
20 if (param.names.long != null or param.names.short != null) { 20 if (param.names.long != null or param.names.short != null) {
@@ -27,18 +27,18 @@ pub fn ComptimeClap(
27 ptr.* += 1; 27 ptr.* += 1;
28 } 28 }
29 29
30 const converted = clap.Param(usize){ 30 converted_params = converted_params ++ [_]clap.Param(usize){.{
31 .id = index, 31 .id = index,
32 .names = param.names, 32 .names = param.names,
33 .takes_value = param.takes_value, 33 .takes_value = param.takes_value,
34 }; 34 }};
35 converted_params = converted_params ++ [_]clap.Param(usize){converted};
36 } 35 }
37 36
38 return struct { 37 return struct {
39 single_options: [single_options]?[]const u8,
40 multi_options: [multi_options][]const []const u8, 38 multi_options: [multi_options][]const []const u8,
41 flags: [flags]bool, 39 single_options: [single_options][]const u8,
40 single_options_is_set: std.PackedIntArray(u1, single_options),
41 flags: std.PackedIntArray(u1, flags),
42 pos: []const []const u8, 42 pos: []const []const u8,
43 allocator: *mem.Allocator, 43 allocator: *mem.Allocator,
44 44
@@ -52,9 +52,12 @@ pub fn ComptimeClap(
52 var pos = std.ArrayList([]const u8).init(allocator); 52 var pos = std.ArrayList([]const u8).init(allocator);
53 53
54 var res = @This(){ 54 var res = @This(){
55 .single_options = [_]?[]const u8{null} ** single_options, 55 .multi_options = .{undefined} ** multi_options,
56 .multi_options = [_][]const []const u8{undefined} ** multi_options, 56 .single_options = .{undefined} ** single_options,
57 .flags = [_]bool{false} ** flags, 57 .single_options_is_set = std.PackedIntArray(u1, single_options).init(
58 .{0} ** single_options,
59 ),
60 .flags = std.PackedIntArray(u1, flags).init(.{0} ** flags),
58 .pos = undefined, 61 .pos = undefined,
59 .allocator = allocator, 62 .allocator = allocator,
60 }; 63 };
@@ -69,16 +72,18 @@ pub fn ComptimeClap(
69 try pos.append(arg.value.?); 72 try pos.append(arg.value.?);
70 } else if (param.takes_value == .one) { 73 } else if (param.takes_value == .one) {
71 debug.assert(res.single_options.len != 0); 74 debug.assert(res.single_options.len != 0);
72 if (res.single_options.len != 0) 75 if (res.single_options.len != 0) {
73 res.single_options[param.id] = arg.value.?; 76 res.single_options[param.id] = arg.value.?;
77 res.single_options_is_set.set(param.id, 1);
78 }
74 } else if (param.takes_value == .many) { 79 } else if (param.takes_value == .many) {
75 debug.assert(multis.len != 0); 80 debug.assert(multis.len != 0);
76 if (multis.len != 0) 81 if (multis.len != 0)
77 try multis[param.id].append(arg.value.?); 82 try multis[param.id].append(arg.value.?);
78 } else { 83 } else {
79 debug.assert(res.flags.len != 0); 84 debug.assert(res.flags.len() != 0);
80 if (res.flags.len != 0) 85 if (res.flags.len() != 0)
81 res.flags[param.id] = true; 86 res.flags.set(param.id, 1);
82 } 87 }
83 } 88 }
84 89
@@ -100,7 +105,7 @@ pub fn ComptimeClap(
100 if (param.takes_value != .none) 105 if (param.takes_value != .none)
101 @compileError(name ++ " is an option and not a flag."); 106 @compileError(name ++ " is an option and not a flag.");
102 107
103 return parser.flags[param.id]; 108 return parser.flags.get(param.id) != 0;
104 } 109 }
105 110
106 pub fn option(parser: @This(), comptime name: []const u8) ?[]const u8 { 111 pub fn option(parser: @This(), comptime name: []const u8) ?[]const u8 {
@@ -109,6 +114,8 @@ pub fn ComptimeClap(
109 @compileError(name ++ " is a flag and not an option."); 114 @compileError(name ++ " is a flag and not an option.");
110 if (param.takes_value == .many) 115 if (param.takes_value == .many)
111 @compileError(name ++ " takes many options, not one."); 116 @compileError(name ++ " takes many options, not one.");
117 if (parser.single_options_is_set.get(param.id) == 0)
118 return null;
112 return parser.single_options[param.id]; 119 return parser.single_options[param.id];
113 } 120 }
114 121
@@ -146,30 +153,37 @@ pub fn ComptimeClap(
146} 153}
147 154
148test "" { 155test "" {
149 const Clap = ComptimeClap(clap.Help, comptime &[_]clap.Param(clap.Help){ 156 const Clap = ComptimeClap(clap.Help, comptime &.{
150 clap.parseParam("-a, --aa ") catch unreachable, 157 clap.parseParam("-a, --aa") catch unreachable,
151 clap.parseParam("-b, --bb ") catch unreachable, 158 clap.parseParam("-b, --bb") catch unreachable,
152 clap.parseParam("-c, --cc <V>") catch unreachable, 159 clap.parseParam("-c, --cc <V>") catch unreachable,
153 clap.parseParam("-d, --dd <V>...") catch unreachable, 160 clap.parseParam("-d, --dd <V>...") catch unreachable,
154 clap.parseParam("<P>") catch unreachable, 161 clap.parseParam("<P>") catch unreachable,
155 }); 162 });
156 163
157 var iter = clap.args.SliceIterator{ 164 var iter = clap.args.SliceIterator{
158 .args = &[_][]const u8{ 165 .args = &.{
159 "-a", "-c", "0", "something", "-d", "a", "--dd", "b", 166 "-a", "-c", "0", "something", "-d", "a", "--dd", "b",
160 }, 167 },
161 }; 168 };
162 var args = try Clap.parse(&iter, .{ .allocator = testing.allocator }); 169 var args = try Clap.parse(&iter, .{ .allocator = testing.allocator });
163 defer args.deinit(); 170 defer args.deinit();
164 171
165 testing.expect(args.flag("-a")); 172 try testing.expect(args.flag("-a"));
166 testing.expect(args.flag("--aa")); 173 try testing.expect(args.flag("--aa"));
167 testing.expect(!args.flag("-b")); 174 try testing.expect(!args.flag("-b"));
168 testing.expect(!args.flag("--bb")); 175 try testing.expect(!args.flag("--bb"));
169 testing.expectEqualStrings("0", args.option("-c").?); 176 try testing.expectEqualStrings("0", args.option("-c").?);
170 testing.expectEqualStrings("0", args.option("--cc").?); 177 try testing.expectEqualStrings("0", args.option("--cc").?);
171 testing.expectEqual(@as(usize, 1), args.positionals().len); 178 try testing.expectEqual(@as(usize, 1), args.positionals().len);
172 testing.expectEqualStrings("something", args.positionals()[0]); 179 try testing.expectEqualStrings("something", args.positionals()[0]);
173 testing.expectEqualSlices([]const u8, &[_][]const u8{ "a", "b" }, args.options("-d")); 180 try testing.expectEqualSlices([]const u8, &.{ "a", "b" }, args.options("-d"));
174 testing.expectEqualSlices([]const u8, &[_][]const u8{ "a", "b" }, args.options("--dd")); 181 try testing.expectEqualSlices([]const u8, &.{ "a", "b" }, args.options("--dd"));
182}
183
184test "empty" {
185 const Clap = ComptimeClap(clap.Help, comptime &.{});
186 var iter = clap.args.SliceIterator{ .args = &.{} };
187 var args = try Clap.parse(&iter, .{ .allocator = testing.allocator });
188 defer args.deinit();
175} 189}
diff --git a/clap/streaming.zig b/clap/streaming.zig
index a2a0ca8..e2f1311 100644
--- a/clap/streaming.zig
+++ b/clap/streaming.zig
@@ -203,7 +203,7 @@ pub fn StreamingClap(comptime Id: type, comptime ArgIterator: type) type {
203 }; 203 };
204} 204}
205 205
206fn testNoErr(params: []const clap.Param(u8), args_strings: []const []const u8, results: []const Arg(u8)) void { 206fn testNoErr(params: []const clap.Param(u8), args_strings: []const []const u8, results: []const Arg(u8)) !void {
207 var iter = args.SliceIterator{ .args = args_strings }; 207 var iter = args.SliceIterator{ .args = args_strings };
208 var c = StreamingClap(u8, args.SliceIterator){ 208 var c = StreamingClap(u8, args.SliceIterator){
209 .params = params, 209 .params = params,
@@ -211,22 +211,22 @@ fn testNoErr(params: []const clap.Param(u8), args_strings: []const []const u8, r
211 }; 211 };
212 212
213 for (results) |res| { 213 for (results) |res| {
214 const arg = (c.next() catch unreachable) orelse unreachable; 214 const arg = (try c.next()) orelse return error.TestFailed;
215 testing.expectEqual(res.param, arg.param); 215 try testing.expectEqual(res.param, arg.param);
216 const expected_value = res.value orelse { 216 const expected_value = res.value orelse {
217 testing.expectEqual(@as(@TypeOf(arg.value), null), arg.value); 217 try testing.expectEqual(@as(@TypeOf(arg.value), null), arg.value);
218 continue; 218 continue;
219 }; 219 };
220 const actual_value = arg.value orelse unreachable; 220 const actual_value = arg.value orelse return error.TestFailed;
221 testing.expectEqualSlices(u8, expected_value, actual_value); 221 try testing.expectEqualSlices(u8, expected_value, actual_value);
222 } 222 }
223 223
224 if (c.next() catch unreachable) |_| 224 if (try c.next()) |_|
225 unreachable; 225 return error.TestFailed;
226} 226}
227 227
228fn testErr(params: []const clap.Param(u8), args_strings: []const []const u8, expected: []const u8) void { 228fn testErr(params: []const clap.Param(u8), args_strings: []const []const u8, expected: []const u8) !void {
229 var diag = clap.Diagnostic{}; 229 var diag: clap.Diagnostic = undefined;
230 var iter = args.SliceIterator{ .args = args_strings }; 230 var iter = args.SliceIterator{ .args = args_strings };
231 var c = StreamingClap(u8, args.SliceIterator){ 231 var c = StreamingClap(u8, args.SliceIterator){
232 .params = params, 232 .params = params,
@@ -236,12 +236,12 @@ fn testErr(params: []const clap.Param(u8), args_strings: []const []const u8, exp
236 while (c.next() catch |err| { 236 while (c.next() catch |err| {
237 var buf: [1024]u8 = undefined; 237 var buf: [1024]u8 = undefined;
238 var fbs = io.fixedBufferStream(&buf); 238 var fbs = io.fixedBufferStream(&buf);
239 diag.report(fbs.writer(), err) catch unreachable; 239 diag.report(fbs.writer(), err) catch return error.TestFailed;
240 testing.expectEqualStrings(expected, fbs.getWritten()); 240 try testing.expectEqualStrings(expected, fbs.getWritten());
241 return; 241 return;
242 }) |_| {} 242 }) |_| {}
243 243
244 testing.expect(false); 244 try testing.expect(false);
245} 245}
246 246
247test "short params" { 247test "short params" {
@@ -265,14 +265,14 @@ test "short params" {
265 const c = &params[2]; 265 const c = &params[2];
266 const d = &params[3]; 266 const d = &params[3];
267 267
268 testNoErr( 268 try testNoErr(
269 &params, 269 &params,
270 &[_][]const u8{ 270 &.{
271 "-a", "-b", "-ab", "-ba", 271 "-a", "-b", "-ab", "-ba",
272 "-c", "0", "-c=0", "-ac", 272 "-c", "0", "-c=0", "-ac",
273 "0", "-ac=0", "-d=0", 273 "0", "-ac=0", "-d=0",
274 }, 274 },
275 &[_]Arg(u8){ 275 &.{
276 .{ .param = a }, 276 .{ .param = a },
277 .{ .param = b }, 277 .{ .param = b },
278 .{ .param = a }, 278 .{ .param = a },
@@ -311,14 +311,14 @@ test "long params" {
311 const cc = &params[2]; 311 const cc = &params[2];
312 const dd = &params[3]; 312 const dd = &params[3];
313 313
314 testNoErr( 314 try testNoErr(
315 &params, 315 &params,
316 &[_][]const u8{ 316 &.{
317 "--aa", "--bb", 317 "--aa", "--bb",
318 "--cc", "0", 318 "--cc", "0",
319 "--cc=0", "--dd=0", 319 "--cc=0", "--dd=0",
320 }, 320 },
321 &[_]Arg(u8){ 321 &.{
322 .{ .param = aa }, 322 .{ .param = aa },
323 .{ .param = bb }, 323 .{ .param = bb },
324 .{ .param = cc, .value = "0" }, 324 .{ .param = cc, .value = "0" },
@@ -334,10 +334,10 @@ test "positional params" {
334 .takes_value = .one, 334 .takes_value = .one,
335 }}; 335 }};
336 336
337 testNoErr( 337 try testNoErr(
338 &params, 338 &params,
339 &[_][]const u8{ "aa", "bb" }, 339 &.{ "aa", "bb" },
340 &[_]Arg(u8){ 340 &.{
341 .{ .param = &params[0], .value = "aa" }, 341 .{ .param = &params[0], .value = "aa" },
342 .{ .param = &params[0], .value = "bb" }, 342 .{ .param = &params[0], .value = "bb" },
343 }, 343 },
@@ -367,16 +367,16 @@ test "all params" {
367 const cc = &params[2]; 367 const cc = &params[2];
368 const positional = &params[3]; 368 const positional = &params[3];
369 369
370 testNoErr( 370 try testNoErr(
371 &params, 371 &params,
372 &[_][]const u8{ 372 &.{
373 "-a", "-b", "-ab", "-ba", 373 "-a", "-b", "-ab", "-ba",
374 "-c", "0", "-c=0", "-ac", 374 "-c", "0", "-c=0", "-ac",
375 "0", "-ac=0", "--aa", "--bb", 375 "0", "-ac=0", "--aa", "--bb",
376 "--cc", "0", "--cc=0", "something", 376 "--cc", "0", "--cc=0", "something",
377 "-", "--", "--cc=0", "-a", 377 "-", "--", "--cc=0", "-a",
378 }, 378 },
379 &[_]Arg(u8){ 379 &.{
380 .{ .param = aa }, 380 .{ .param = aa },
381 .{ .param = bb }, 381 .{ .param = bb },
382 .{ .param = aa }, 382 .{ .param = aa },
@@ -413,12 +413,12 @@ test "errors" {
413 .takes_value = .one, 413 .takes_value = .one,
414 }, 414 },
415 }; 415 };
416 testErr(&params, &[_][]const u8{"q"}, "Invalid argument 'q'\n"); 416 try testErr(&params, &.{"q"}, "Invalid argument 'q'\n");
417 testErr(&params, &[_][]const u8{"-q"}, "Invalid argument '-q'\n"); 417 try testErr(&params, &.{"-q"}, "Invalid argument '-q'\n");
418 testErr(&params, &[_][]const u8{"--q"}, "Invalid argument '--q'\n"); 418 try testErr(&params, &.{"--q"}, "Invalid argument '--q'\n");
419 testErr(&params, &[_][]const u8{"--q=1"}, "Invalid argument '--q'\n"); 419 try testErr(&params, &.{"--q=1"}, "Invalid argument '--q'\n");
420 testErr(&params, &[_][]const u8{"-a=1"}, "The argument '-a' does not take a value\n"); 420 try testErr(&params, &.{"-a=1"}, "The argument '-a' does not take a value\n");
421 testErr(&params, &[_][]const u8{"--aa=1"}, "The argument '--aa' does not take a value\n"); 421 try testErr(&params, &.{"--aa=1"}, "The argument '--aa' does not take a value\n");
422 testErr(&params, &[_][]const u8{"-c"}, "The argument '-c' requires a value but none was supplied\n"); 422 try testErr(&params, &.{"-c"}, "The argument '-c' requires a value but none was supplied\n");
423 testErr(&params, &[_][]const u8{"--cc"}, "The argument '--cc' requires a value but none was supplied\n"); 423 try testErr(&params, &.{"--cc"}, "The argument '--cc' requires a value but none was supplied\n");
424} 424}