summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Jimmi Holst Christensen2025-01-06 11:57:46 +0100
committerGravatar Jimmi Holst Christensen2025-01-06 11:57:46 +0100
commit70f6a5b4e9889910a75e49d440cc0c5658ba0c16 (patch)
treed2e9d4ca2cfd1c4251152f3d2fdb627ad2c29384
parentWIP: zig-clap v2 (diff)
downloadzig-clap-70f6a5b4e9889910a75e49d440cc0c5658ba0c16.tar.gz
zig-clap-70f6a5b4e9889910a75e49d440cc0c5658ba0c16.tar.xz
zig-clap-70f6a5b4e9889910a75e49d440cc0c5658ba0c16.zip
-rw-r--r--clap/v2.zig483
1 files changed, 219 insertions, 264 deletions
diff --git a/clap/v2.zig b/clap/v2.zig
index e427d6a..46f99cf 100644
--- a/clap/v2.zig
+++ b/clap/v2.zig
@@ -1,4 +1,4 @@
1pub fn Params(comptime T: type) type { 1pub fn Parameters(comptime T: type) type {
2 const info = @typeInfo(T).@"struct"; 2 const info = @typeInfo(T).@"struct";
3 3
4 var params: [info.fields.len + 2]std.builtin.Type.StructField = undefined; 4 var params: [info.fields.len + 2]std.builtin.Type.StructField = undefined;
@@ -34,7 +34,7 @@ pub fn Params(comptime T: type) type {
34 .@"union" => |un| blk: { 34 .@"union" => |un| blk: {
35 var cmd_fields: [un.fields.len]std.builtin.Type.StructField = undefined; 35 var cmd_fields: [un.fields.len]std.builtin.Type.StructField = undefined;
36 for (un.fields, &cmd_fields) |un_field, *cmd_field| { 36 for (un.fields, &cmd_fields) |un_field, *cmd_field| {
37 const CmdParam = Params(un_field.type); 37 const CmdParam = Parameters(un_field.type);
38 const cmd_default_value = CmdParam{}; 38 const cmd_default_value = CmdParam{};
39 cmd_field.* = .{ 39 cmd_field.* = .{
40 .name = un_field.name, 40 .name = un_field.name,
@@ -275,48 +275,51 @@ fn defaultParseInto(comptime T: type) ParseInto(T) {
275 } 275 }
276} 276}
277 277
278fn validateParams(comptime T: type, name: []const u8, opt: ParseOptions(T)) !void { 278fn validateParameters(
279 const stderr_writer = std.io.getStdErr().writer(); 279 writer: anytype,
280 const stderr = opt.stderr orelse stderr_writer.any(); 280 gpa: std.mem.Allocator,
281 281 name: []const u8,
282 comptime T: type,
283 params: Parameters(T),
284) !void {
282 var res: anyerror!void = {}; 285 var res: anyerror!void = {};
283 var first_command: ?[]const u8 = null; 286 var first_command: ?[]const u8 = null;
284 var first_positionals: ?[]const u8 = null; 287 var first_positionals: ?[]const u8 = null;
285 const fields = @typeInfo(T).@"struct".fields; 288 const fields = @typeInfo(T).@"struct".fields;
286 inline for (fields) |field| switch (@field(opt.params, field.name).kind) { 289 inline for (fields) |field| switch (@field(params, field.name).kind) {
287 .flag, .option, .positional, .positionals => { 290 .flag, .option, .positional, .positionals => {
288 const param = @field(opt.params, field.name); 291 const param = @field(params, field.name);
289 if (param.init == null) { 292 if (param.init == null) {
290 try stderr.print("error: '{s}.{s}.init' is null\n", .{ name, field.name }); 293 try writer.print("error: '{s}.{s}.init' is null\n", .{ name, field.name });
291 try stderr.print("note: could not infer 'init' for type '{s}'\n", .{@typeName(field.type)}); 294 try writer.print("note: could not infer 'init' for type '{s}'\n", .{@typeName(field.type)});
292 try stderr.print("note: or it was set to null by the caller (don't do that)\n\n", .{}); 295 try writer.print("note: or it was set to null by the caller (don't do that)\n\n", .{});
293 res = error.InvalidParameter; 296 res = error.InvalidParameter;
294 } 297 }
295 if (param.kind == .flag and param.next == null) { 298 if (param.kind == .flag and param.next == null) {
296 try stderr.print("error: '{s}.{s}.next' is null\n", .{ name, field.name }); 299 try writer.print("error: '{s}.{s}.next' is null\n", .{ name, field.name });
297 try stderr.print("note: could not infer 'next' for type '{s}'\n", .{@typeName(field.type)}); 300 try writer.print("note: could not infer 'next' for type '{s}'\n", .{@typeName(field.type)});
298 try stderr.print("note: or it was set to null by the caller (don't do that)\n\n", .{}); 301 try writer.print("note: or it was set to null by the caller (don't do that)\n\n", .{});
299 res = error.InvalidParameter; 302 res = error.InvalidParameter;
300 } 303 }
301 if (param.kind != .flag and param.parse == null) { 304 if (param.kind != .flag and param.parse == null) {
302 try stderr.print("error: '{s}.{s}.parse' is null\n", .{ name, field.name }); 305 try writer.print("error: '{s}.{s}.parse' is null\n", .{ name, field.name });
303 try stderr.print("note: could not infer 'parse' for type '{s}'\n", .{@typeName(field.type)}); 306 try writer.print("note: could not infer 'parse' for type '{s}'\n", .{@typeName(field.type)});
304 try stderr.print("note: or it was set to null by the caller (don't do that)\n\n", .{}); 307 try writer.print("note: or it was set to null by the caller (don't do that)\n\n", .{});
305 res = error.InvalidParameter; 308 res = error.InvalidParameter;
306 } 309 }
307 if (first_command) |command| { 310 if (first_command) |command| {
308 if (param.kind == .positional or param.kind == .positionals) { 311 if (param.kind == .positional or param.kind == .positionals) {
309 try stderr.print("error: cannot have positionals after a command\n", .{}); 312 try writer.print("error: cannot have positionals after a command\n", .{});
310 try stderr.print("note: '{s}.{s}' is the command\n", .{ name, command }); 313 try writer.print("note: '{s}.{s}' is the command\n", .{ name, command });
311 try stderr.print("note: '{s}.{s}' is the positional\n\n", .{ name, field.name }); 314 try writer.print("note: '{s}.{s}' is the positional\n\n", .{ name, field.name });
312 res = error.InvalidParameter; 315 res = error.InvalidParameter;
313 } 316 }
314 } 317 }
315 if (first_positionals) |positional| { 318 if (first_positionals) |positional| {
316 if (param.kind == .positional or param.kind == .positionals) { 319 if (param.kind == .positional or param.kind == .positionals) {
317 try stderr.print("error: cannot have positionals after a positional taking many values\n", .{}); 320 try writer.print("error: cannot have positionals after a positional taking many values\n", .{});
318 try stderr.print("note: '{s}.{s}' is the positional taking many values\n", .{ name, positional }); 321 try writer.print("note: '{s}.{s}' is the positional taking many values\n", .{ name, positional });
319 try stderr.print("note: '{s}.{s}' is the positional after it\n\n", .{ name, field.name }); 322 try writer.print("note: '{s}.{s}' is the positional after it\n\n", .{ name, field.name });
320 res = error.InvalidParameter; 323 res = error.InvalidParameter;
321 } 324 }
322 } 325 }
@@ -326,16 +329,16 @@ fn validateParams(comptime T: type, name: []const u8, opt: ParseOptions(T)) !voi
326 }, 329 },
327 .command => case: { 330 .command => case: {
328 if (first_positionals) |positional| { 331 if (first_positionals) |positional| {
329 try stderr.print("error: cannot have command after a positional taking many values\n", .{}); 332 try writer.print("error: cannot have command after a positional taking many values\n", .{});
330 try stderr.print("note: '{s}.{s}' is the positional\n", .{ name, positional }); 333 try writer.print("note: '{s}.{s}' is the positional\n", .{ name, positional });
331 try stderr.print("note: '{s}.{s}' is the command\n\n", .{ name, field.name }); 334 try writer.print("note: '{s}.{s}' is the command\n\n", .{ name, field.name });
332 res = error.InvalidParameter; 335 res = error.InvalidParameter;
333 } 336 }
334 337
335 const param = @field(opt.params, field.name); 338 const param = @field(params, field.name);
336 const union_info = @typeInfo(field.type); 339 const union_info = @typeInfo(field.type);
337 if (union_info != .@"union" or union_info.@"union".tag_type == null) { 340 if (union_info != .@"union" or union_info.@"union".tag_type == null) {
338 try stderr.print( 341 try writer.print(
339 "error: expected command '{s}.{s}' to be a tagged union, but found '{s}'\n\n", 342 "error: expected command '{s}.{s}' to be a tagged union, but found '{s}'\n\n",
340 .{ name, field.name, @typeName(field.type) }, 343 .{ name, field.name, @typeName(field.type) },
341 ); 344 );
@@ -344,8 +347,8 @@ fn validateParams(comptime T: type, name: []const u8, opt: ParseOptions(T)) !voi
344 } 347 }
345 348
346 if (first_command) |command| { 349 if (first_command) |command| {
347 try stderr.print("error: only one field can be a command\n", .{}); 350 try writer.print("error: only one field can be a command\n", .{});
348 try stderr.print("note: both '{s}.{s}' and '{s}.{s}' are commands\n\n", .{ name, command, name, field.name }); 351 try writer.print("note: both '{s}.{s}' and '{s}.{s}' are commands\n\n", .{ name, command, name, field.name });
349 res = error.InvalidParameter; 352 res = error.InvalidParameter;
350 break :case; 353 break :case;
351 } else { 354 } else {
@@ -355,15 +358,13 @@ fn validateParams(comptime T: type, name: []const u8, opt: ParseOptions(T)) !voi
355 const union_field = union_info.@"union".fields; 358 const union_field = union_info.@"union".fields;
356 inline for (union_field) |cmd_field| { 359 inline for (union_field) |cmd_field| {
357 const cmd_params = @field(param.command, cmd_field.name); 360 const cmd_params = @field(param.command, cmd_field.name);
358 const cmd_opt = opt.withNewParams(cmd_field.type, cmd_params); 361 const new_name = try std.fmt.allocPrint(gpa, "{s}.{s}", .{
359
360 const new_name = try std.fmt.allocPrint(opt.gpa, "{s}.{s}", .{
361 name, 362 name,
362 cmd_field.name, 363 cmd_field.name,
363 }); 364 });
364 defer opt.gpa.free(new_name); 365 defer gpa.free(new_name);
365 366
366 validateParams(cmd_field.type, new_name, cmd_opt) catch |err| { 367 validateParameters(writer, gpa, new_name, cmd_field.type, cmd_params) catch |err| {
367 res = err; 368 res = err;
368 }; 369 };
369 } 370 }
@@ -373,8 +374,20 @@ fn validateParams(comptime T: type, name: []const u8, opt: ParseOptions(T)) !voi
373 return res; 374 return res;
374} 375}
375 376
376fn testValidateParams(comptime T: type, opt: struct { 377fn validateParametersComptime(comptime T: type, comptime params: Parameters(T)) void {
377 params: Params(T), 378 comptime {
379 var error_buf: [1024 * 4]u8 = undefined;
380 var alloc_buf: [1024 * 4]u8 = undefined;
381
382 var error_fbs = std.io.fixedBufferStream(&error_buf);
383 var fba = std.heap.FixedBufferAllocator.init(&alloc_buf);
384 validateParameters(error_fbs.writer(), fba.allocator(), "", T, params) catch
385 @compileError(error_fbs.getWritten());
386 }
387}
388
389fn testValidateParameters(comptime T: type, opt: struct {
390 params: Parameters(T),
378 expected: anyerror!void, 391 expected: anyerror!void,
379 expected_err: []const u8, 392 expected_err: []const u8,
380}) !void { 393}) !void {
@@ -384,18 +397,13 @@ fn testValidateParams(comptime T: type, opt: struct {
384 const err_writer = err.writer(); 397 const err_writer = err.writer();
385 defer err.deinit(); 398 defer err.deinit();
386 399
387 const actual = validateParams(T, "", .{ 400 const actual = validateParameters(err_writer, gpa, "", T, opt.params);
388 .gpa = gpa,
389 .params = opt.params,
390 .stderr = err_writer.any(),
391 });
392
393 try std.testing.expectEqualStrings(opt.expected_err, err.items); 401 try std.testing.expectEqualStrings(opt.expected_err, err.items);
394 try std.testing.expectEqualDeep(opt.expected, actual); 402 try std.testing.expectEqualDeep(opt.expected, actual);
395} 403}
396 404
397test validateParams { 405test validateParameters {
398 try testValidateParams(struct { a: *const void }, .{ 406 try testValidateParameters(struct { a: *const void }, .{
399 .params = .{ .a = .{ .kind = .flag } }, 407 .params = .{ .a = .{ .kind = .flag } },
400 .expected = error.InvalidParameter, 408 .expected = error.InvalidParameter,
401 .expected_err = 409 .expected_err =
@@ -410,7 +418,7 @@ test validateParams {
410 \\ 418 \\
411 , 419 ,
412 }); 420 });
413 try testValidateParams(struct { a: *const void }, .{ 421 try testValidateParameters(struct { a: *const void }, .{
414 .params = .{ .a = .{ .kind = .option } }, 422 .params = .{ .a = .{ .kind = .option } },
415 .expected = error.InvalidParameter, 423 .expected = error.InvalidParameter,
416 .expected_err = 424 .expected_err =
@@ -425,7 +433,7 @@ test validateParams {
425 \\ 433 \\
426 , 434 ,
427 }); 435 });
428 try testValidateParams(struct { a: *const void }, .{ 436 try testValidateParameters(struct { a: *const void }, .{
429 .params = .{ .a = .{ .kind = .positional } }, 437 .params = .{ .a = .{ .kind = .positional } },
430 .expected = error.InvalidParameter, 438 .expected = error.InvalidParameter,
431 .expected_err = 439 .expected_err =
@@ -440,7 +448,7 @@ test validateParams {
440 \\ 448 \\
441 , 449 ,
442 }); 450 });
443 try testValidateParams(struct { a: *const void }, .{ 451 try testValidateParameters(struct { a: *const void }, .{
444 .params = .{ .a = .{ .kind = .positionals } }, 452 .params = .{ .a = .{ .kind = .positionals } },
445 .expected = error.InvalidParameter, 453 .expected = error.InvalidParameter,
446 .expected_err = 454 .expected_err =
@@ -455,7 +463,7 @@ test validateParams {
455 \\ 463 \\
456 , 464 ,
457 }); 465 });
458 try testValidateParams(struct { a: *const void }, .{ 466 try testValidateParameters(struct { a: *const void }, .{
459 .params = .{ .a = .{ .kind = .command } }, 467 .params = .{ .a = .{ .kind = .command } },
460 .expected = error.InvalidParameter, 468 .expected = error.InvalidParameter,
461 .expected_err = 469 .expected_err =
@@ -464,7 +472,7 @@ test validateParams {
464 \\ 472 \\
465 , 473 ,
466 }); 474 });
467 try testValidateParams(struct { a: union(enum) {}, b: union(enum) {} }, .{ 475 try testValidateParameters(struct { a: union(enum) {}, b: union(enum) {} }, .{
468 .params = .{ 476 .params = .{
469 .a = .{ .kind = .command }, 477 .a = .{ .kind = .command },
470 .b = .{ .kind = .command }, 478 .b = .{ .kind = .command },
@@ -477,7 +485,7 @@ test validateParams {
477 \\ 485 \\
478 , 486 ,
479 }); 487 });
480 try testValidateParams(struct { a: union(enum) {}, b: u8 }, .{ 488 try testValidateParameters(struct { a: union(enum) {}, b: u8 }, .{
481 .params = .{ 489 .params = .{
482 .a = .{ .kind = .command }, 490 .a = .{ .kind = .command },
483 .b = .{ .kind = .positional }, 491 .b = .{ .kind = .positional },
@@ -491,7 +499,7 @@ test validateParams {
491 \\ 499 \\
492 , 500 ,
493 }); 501 });
494 try testValidateParams(struct { a: union(enum) {}, b: u8 }, .{ 502 try testValidateParameters(struct { a: union(enum) {}, b: u8 }, .{
495 .params = .{ 503 .params = .{
496 .a = .{ .kind = .command }, 504 .a = .{ .kind = .command },
497 .b = .{ .kind = .positionals }, 505 .b = .{ .kind = .positionals },
@@ -505,7 +513,7 @@ test validateParams {
505 \\ 513 \\
506 , 514 ,
507 }); 515 });
508 try testValidateParams(struct { a: u8, b: union(enum) {} }, .{ 516 try testValidateParameters(struct { a: u8, b: union(enum) {} }, .{
509 .params = .{ 517 .params = .{
510 .a = .{ .kind = .positionals }, 518 .a = .{ .kind = .positionals },
511 .b = .{ .kind = .command }, 519 .b = .{ .kind = .command },
@@ -519,7 +527,7 @@ test validateParams {
519 \\ 527 \\
520 , 528 ,
521 }); 529 });
522 try testValidateParams(struct { a: u8, b: u8 }, .{ 530 try testValidateParameters(struct { a: u8, b: u8 }, .{
523 .params = .{ 531 .params = .{
524 .a = .{ .kind = .positionals }, 532 .a = .{ .kind = .positionals },
525 .b = .{ .kind = .positional }, 533 .b = .{ .kind = .positional },
@@ -533,7 +541,7 @@ test validateParams {
533 \\ 541 \\
534 , 542 ,
535 }); 543 });
536 try testValidateParams(struct { a: u8, b: u8 }, .{ 544 try testValidateParameters(struct { a: u8, b: u8 }, .{
537 .params = .{ 545 .params = .{
538 .a = .{ .kind = .positionals }, 546 .a = .{ .kind = .positionals },
539 .b = .{ .kind = .positionals }, 547 .b = .{ .kind = .positionals },
@@ -548,7 +556,7 @@ test validateParams {
548 , 556 ,
549 }); 557 });
550 558
551 try testValidateParams(struct { a: union(enum) { 559 try testValidateParameters(struct { a: union(enum) {
552 a: struct { a: *const void }, 560 a: struct { a: *const void },
553 b: struct { a: *const void }, 561 b: struct { a: *const void },
554 c: struct { a: *const void }, 562 c: struct { a: *const void },
@@ -622,76 +630,63 @@ pub const VersionParam = struct {
622 description: []const u8 = "Print version", 630 description: []const u8 = "Print version",
623}; 631};
624 632
625pub const ExtraParams = struct { 633pub const ExtraParameters = struct {
626 help: HelpParam = .{}, 634 help: HelpParam = .{},
627 version: VersionParam = .{}, 635 version: VersionParam = .{},
628}; 636};
629 637
630pub fn ParseOptions(comptime T: type) type { 638pub const ParseOptions = struct {
631 return struct { 639 gpa: std.mem.Allocator,
632 gpa: std.mem.Allocator, 640 extra_params: ExtraParameters = .{},
633 params: Params(T) = .{},
634 extra_params: ExtraParams = .{},
635
636 assignment_separators: []const u8 = "=",
637 641
638 /// The Writer used to write expected output like the help message when `-h` is passed. If 642 assignment_separators: []const u8 = "=",
639 /// `null`, `std.io.getStdOut` will be used
640 stdout: ?std.io.AnyWriter = null,
641 643
642 /// The Writer used to write errors. `std.io.getStdErr` will be used. If `null`, 644 /// The Writer used to write expected output like the help message when `-h` is passed. If
643 /// `std.io.getStdOut` will be used 645 /// `null`, `std.io.getStdOut` will be used
644 stderr: ?std.io.AnyWriter = null, 646 stdout: ?std.io.AnyWriter = null,
645 647
646 pub fn withNewParams(opt: @This(), comptime T2: type, params: Params(T2)) ParseOptions(T2) { 648 /// The Writer used to write errors. `std.io.getStdErr` will be used. If `null`,
647 var res: ParseOptions(T2) = undefined; 649 /// `std.io.getStdOut` will be used
648 res.params = params; 650 stderr: ?std.io.AnyWriter = null,
649 inline for (@typeInfo(@This()).@"struct".fields) |field| { 651};
650 if (comptime std.mem.eql(u8, field.name, "params"))
651 continue;
652
653 @field(res, field.name) = @field(opt, field.name);
654 }
655
656 return res;
657 }
658 };
659}
660 652
661pub const ParseError = error{ 653pub const ParseError = error{
662 ParsingInterrupted, 654 ParsingInterrupted,
663 ParsingFailed, 655 ParsingFailed,
664} || std.mem.Allocator.Error; 656} || std.mem.Allocator.Error;
665 657
666pub fn parseIter(it: anytype, comptime T: type, opt: ParseOptions(T)) ParseError!T { 658pub fn parseIter(it: anytype, comptime T: type, opt: ParseOptions) ParseError!T {
667 switch (@import("builtin").mode) { 659 const params: Parameters(T) = if (@hasDecl(T, "parameters")) T.parameters else .{};
668 .Debug, .ReleaseSafe => { 660 return parseIterParameters(it, T, params, opt);
669 validateParams(T, "", opt) catch @panic("Invalid parameters. See errors above."); 661}
670 },
671 .ReleaseFast, .ReleaseSmall => {},
672 }
673 662
674 var parser = try Parser(@TypeOf(it), T).init(it, opt); 663pub fn parseIterParameters(
664 it: anytype,
665 comptime T: type,
666 comptime params: Parameters(T),
667 opt: ParseOptions,
668) ParseError!T {
669 validateParametersComptime(T, params);
670 var parser = try Parser(@TypeOf(it), T, params).init(it, opt);
675 return parser.parse(); 671 return parser.parse();
676} 672}
677 673
678fn Parser(comptime Iter: type, comptime T: type) type { 674fn Parser(comptime Iter: type, comptime T: type, comptime params: Parameters(T)) type {
679 return struct { 675 return struct {
680 it: Iter, 676 it: Iter,
681 opt: Options,
682 result: T, 677 result: T,
678 opt: ParseOptions,
683 has_been_set: HasBeenSet, 679 has_been_set: HasBeenSet,
684 current_positional: usize, 680 current_positional: usize,
685 681
686 stdout_writer: std.fs.File.Writer, 682 stdout_writer: std.fs.File.Writer,
687 stderr_writer: std.fs.File.Writer, 683 stderr_writer: std.fs.File.Writer,
688 684
689 const Options = ParseOptions(T);
690 const Field = std.meta.FieldEnum(T); 685 const Field = std.meta.FieldEnum(T);
691 const HasBeenSet = std.EnumSet(Field); 686 const HasBeenSet = std.EnumSet(Field);
692 const fields = @typeInfo(T).@"struct".fields; 687 const fields = @typeInfo(T).@"struct".fields;
693 688
694 fn init(it: Iter, opt: Options) ParseError!@This() { 689 fn init(it: Iter, opt: ParseOptions) ParseError!@This() {
695 var res = @This(){ 690 var res = @This(){
696 .it = it, 691 .it = it,
697 .opt = opt, 692 .opt = opt,
@@ -703,9 +698,9 @@ fn Parser(comptime Iter: type, comptime T: type) type {
703 .stderr_writer = std.io.getStdErr().writer(), 698 .stderr_writer = std.io.getStdErr().writer(),
704 }; 699 };
705 inline for (fields) |field| { 700 inline for (fields) |field| {
706 const param = @field(opt.params, field.name); 701 const param = @field(params, field.name);
707 if (!param.required) { 702 if (!param.required) {
708 const initValue = param.init orelse unreachable; // Shouldn't happen (validateParams) 703 const initValue = param.init orelse unreachable; // Shouldn't happen (validateParameters)
709 @field(res.result, field.name) = try initValue(opt.gpa); 704 @field(res.result, field.name) = try initValue(opt.gpa);
710 res.has_been_set.insert(@field(Field, field.name)); 705 res.has_been_set.insert(@field(Field, field.name));
711 } 706 }
@@ -718,7 +713,7 @@ fn Parser(comptime Iter: type, comptime T: type) type {
718 errdefer { 713 errdefer {
719 // If we fail, deinit fields that can be deinited 714 // If we fail, deinit fields that can be deinited
720 inline for (fields) |field| continue_field_loop: { 715 inline for (fields) |field| continue_field_loop: {
721 const param = @field(parser.opt.params, field.name); 716 const param = @field(params, field.name);
722 const deinit = param.deinit orelse break :continue_field_loop; 717 const deinit = param.deinit orelse break :continue_field_loop;
723 if (parser.has_been_set.contains(@field(Field, field.name))) 718 if (parser.has_been_set.contains(@field(Field, field.name)))
724 deinit(&@field(parser.result, field.name), parser.opt.gpa); 719 deinit(&@field(parser.result, field.name), parser.opt.gpa);
@@ -744,7 +739,7 @@ fn Parser(comptime Iter: type, comptime T: type) type {
744 try parser.parsePositional(arg); 739 try parser.parsePositional(arg);
745 740
746 inline for (fields) |field| { 741 inline for (fields) |field| {
747 const param = @field(parser.opt.params, field.name); 742 const param = @field(params, field.name);
748 _ = param; 743 _ = param;
749 if (!parser.has_been_set.contains(@field(Field, field.name))) { 744 if (!parser.has_been_set.contains(@field(Field, field.name))) {
750 // TODO: Proper error. Required argument not specified 745 // TODO: Proper error. Required argument not specified
@@ -763,9 +758,9 @@ fn Parser(comptime Iter: type, comptime T: type) type {
763 if (std.mem.eql(u8, name, v)) 758 if (std.mem.eql(u8, name, v))
764 return parser.printVersion(); 759 return parser.printVersion();
765 760
766 inline for (fields) |field| switch (@field(parser.opt.params, field.name).kind) { 761 inline for (fields) |field| switch (@field(params, field.name).kind) {
767 .flag => switch_case: { 762 .flag => switch_case: {
768 const param = @field(parser.opt.params, field.name); 763 const param = @field(params, field.name);
769 const long_name = param.long orelse break :switch_case; 764 const long_name = param.long orelse break :switch_case;
770 if (!std.mem.eql(u8, name, long_name)) 765 if (!std.mem.eql(u8, name, long_name))
771 break :switch_case; 766 break :switch_case;
@@ -773,7 +768,7 @@ fn Parser(comptime Iter: type, comptime T: type) type {
773 return parser.parseNext(@field(Field, field.name)); 768 return parser.parseNext(@field(Field, field.name));
774 }, 769 },
775 .option => switch_case: { 770 .option => switch_case: {
776 const param = @field(parser.opt.params, field.name); 771 const param = @field(params, field.name);
777 const long_name = param.long orelse break :switch_case; 772 const long_name = param.long orelse break :switch_case;
778 if (!std.mem.startsWith(u8, name, long_name)) 773 if (!std.mem.startsWith(u8, name, long_name))
779 break :switch_case; 774 break :switch_case;
@@ -812,9 +807,9 @@ fn Parser(comptime Iter: type, comptime T: type) type {
812 if (shorts[pos] == v) 807 if (shorts[pos] == v)
813 return parser.printVersion(); 808 return parser.printVersion();
814 809
815 inline for (fields) |field| switch (@field(parser.opt.params, field.name).kind) { 810 inline for (fields) |field| switch (@field(params, field.name).kind) {
816 .flag => switch_case: { 811 .flag => switch_case: {
817 const param = @field(parser.opt.params, field.name); 812 const param = @field(params, field.name);
818 const short_name = param.short orelse break :switch_case; 813 const short_name = param.short orelse break :switch_case;
819 if (shorts[pos] != short_name) 814 if (shorts[pos] != short_name)
820 break :switch_case; 815 break :switch_case;
@@ -823,7 +818,7 @@ fn Parser(comptime Iter: type, comptime T: type) type {
823 return pos + 1; 818 return pos + 1;
824 }, 819 },
825 .option => switch_case: { 820 .option => switch_case: {
826 const param = @field(parser.opt.params, field.name); 821 const param = @field(params, field.name);
827 const short_name = param.short orelse break :switch_case; 822 const short_name = param.short orelse break :switch_case;
828 if (shorts[pos] != short_name) 823 if (shorts[pos] != short_name)
829 break :switch_case; 824 break :switch_case;
@@ -863,7 +858,7 @@ fn Parser(comptime Iter: type, comptime T: type) type {
863 .@"union" => |u| u.fields, 858 .@"union" => |u| u.fields,
864 else => continue, 859 else => continue,
865 }; 860 };
866 const param = @field(parser.opt.params, field.name); 861 const param = @field(params, field.name);
867 if (param.kind != .command) 862 if (param.kind != .command)
868 break :continue_field_loop; 863 break :continue_field_loop;
869 864
@@ -872,10 +867,8 @@ fn Parser(comptime Iter: type, comptime T: type) type {
872 if (!std.mem.eql(u8, arg, cmd_params.name orelse cmd_field.name)) 867 if (!std.mem.eql(u8, arg, cmd_params.name orelse cmd_field.name))
873 break :continue_cmd_field_loop; 868 break :continue_cmd_field_loop;
874 869
875 var cmd_parser = try Parser(Iter, cmd_field.type).init( 870 const P = Parser(Iter, cmd_field.type, cmd_params);
876 parser.it, 871 var cmd_parser = try P.init(parser.it, parser.opt);
877 parser.opt.withNewParams(cmd_field.type, cmd_params),
878 );
879 872
880 const cmd_result = try cmd_parser.parse(); 873 const cmd_result = try cmd_parser.parse();
881 const cmd_union = @unionInit(field.type, cmd_field.name, cmd_result); 874 const cmd_union = @unionInit(field.type, cmd_field.name, cmd_result);
@@ -891,7 +884,7 @@ fn Parser(comptime Iter: type, comptime T: type) type {
891 fn parsePositional(parser: *@This(), arg: []const u8) ParseError!void { 884 fn parsePositional(parser: *@This(), arg: []const u8) ParseError!void {
892 var i: usize = 0; 885 var i: usize = 0;
893 inline for (fields) |field| continue_field_loop: { 886 inline for (fields) |field| continue_field_loop: {
894 const param = @field(parser.opt.params, field.name); 887 const param = @field(params, field.name);
895 const next_positional = switch (param.kind) { 888 const next_positional = switch (param.kind) {
896 .positional => parser.current_positional + 1, 889 .positional => parser.current_positional + 1,
897 .positionals => parser.current_positional, 890 .positionals => parser.current_positional,
@@ -913,14 +906,14 @@ fn Parser(comptime Iter: type, comptime T: type) type {
913 906
914 fn parseNext(parser: *@This(), comptime field: Field) ParseError!void { 907 fn parseNext(parser: *@This(), comptime field: Field) ParseError!void {
915 const field_name = @tagName(field); 908 const field_name = @tagName(field);
916 const param = @field(parser.opt.params, field_name); 909 const param = @field(params, field_name);
917 910
918 if (!parser.has_been_set.contains(field)) { 911 if (!parser.has_been_set.contains(field)) {
919 const initValue = param.init orelse unreachable; // Shouldn't happen (validateParams) 912 const initValue = param.init orelse unreachable; // Shouldn't happen (validateParameters)
920 @field(parser.result, field_name) = try initValue(parser.opt.gpa); 913 @field(parser.result, field_name) = try initValue(parser.opt.gpa);
921 } 914 }
922 915
923 const next = param.next orelse unreachable; // Shouldn't happen (validateParams) 916 const next = param.next orelse unreachable; // Shouldn't happen (validateParameters)
924 const field_ptr = &@field(parser.result, field_name); 917 const field_ptr = &@field(parser.result, field_name);
925 field_ptr.* = try next(field_ptr.*); 918 field_ptr.* = try next(field_ptr.*);
926 parser.has_been_set.insert(field); 919 parser.has_been_set.insert(field);
@@ -928,21 +921,20 @@ fn Parser(comptime Iter: type, comptime T: type) type {
928 921
929 fn parseValue(parser: *@This(), comptime field: Field, value: []const u8) ParseError!void { 922 fn parseValue(parser: *@This(), comptime field: Field, value: []const u8) ParseError!void {
930 const field_name = @tagName(field); 923 const field_name = @tagName(field);
931 const param = @field(parser.opt.params, field_name); 924 const param = @field(params, field_name);
932 925
933 if (!parser.has_been_set.contains(field)) { 926 if (!parser.has_been_set.contains(field)) {
934 const initValue = param.init orelse unreachable; // Shouldn't happen (validateParams) 927 const initValue = param.init orelse unreachable; // Shouldn't happen (validateParameters)
935 @field(parser.result, field_name) = try initValue(parser.opt.gpa); 928 @field(parser.result, field_name) = try initValue(parser.opt.gpa);
936 } 929 }
937 930
938 const parseInto = param.parse orelse unreachable; // Shouldn't happen (validateParams) 931 const parseInto = param.parse orelse unreachable; // Shouldn't happen (validateParameters)
939 try parseInto(&@field(parser.result, field_name), parser.opt.gpa, value); 932 try parseInto(&@field(parser.result, field_name), parser.opt.gpa, value);
940 parser.has_been_set.insert(field); 933 parser.has_been_set.insert(field);
941 } 934 }
942 935
943 fn printHelp(parser: *@This()) ParseError { 936 fn printHelp(parser: *@This()) ParseError {
944 help(parser.stdout(), T, .{ 937 helpParameters(parser.stdout(), T, params, .{
945 .params = parser.opt.params,
946 .extra_params = parser.opt.extra_params, 938 .extra_params = parser.opt.extra_params,
947 }) catch {}; 939 }) catch {};
948 return error.ParsingInterrupted; 940 return error.ParsingInterrupted;
@@ -963,9 +955,8 @@ fn Parser(comptime Iter: type, comptime T: type) type {
963 }; 955 };
964} 956}
965 957
966fn testParseIter(comptime T: type, opt: struct { 958fn testParseIter(comptime T: type, comptime params: Parameters(T), opt: struct {
967 args: []const u8, 959 args: []const u8,
968 params: Params(T) = .{},
969 960
970 expected: anyerror!T, 961 expected: anyerror!T,
971 expected_out: []const u8 = "", 962 expected_out: []const u8 = "",
@@ -983,9 +974,8 @@ fn testParseIter(comptime T: type, opt: struct {
983 const err_writer = err.writer(); 974 const err_writer = err.writer();
984 defer err.deinit(); 975 defer err.deinit();
985 976
986 const actual = parseIter(&it, T, .{ 977 const actual = parseIterParameters(&it, T, params, .{
987 .gpa = gpa, 978 .gpa = gpa,
988 .params = opt.params,
989 .stdout = out_writer.any(), 979 .stdout = out_writer.any(),
990 .stderr = err_writer.any(), 980 .stderr = err_writer.any(),
991 }); 981 });
@@ -1000,7 +990,7 @@ fn testParseIter(comptime T: type, opt: struct {
1000 try std.testing.expectEqualStrings(opt.expected_err, err.items); 990 try std.testing.expectEqualStrings(opt.expected_err, err.items);
1001} 991}
1002 992
1003test "parseIterParams" { 993test "parseIterParameters" {
1004 const S = struct { 994 const S = struct {
1005 a: bool = false, 995 a: bool = false,
1006 b: u8 = 0, 996 b: u8 = 0,
@@ -1013,105 +1003,100 @@ test "parseIterParams" {
1013 } 1003 }
1014 }; 1004 };
1015 1005
1016 try testParseIter(S, .{ 1006 try testParseIter(S, .{}, .{
1017 .args = "--a", 1007 .args = "--a",
1018 .expected = .{ .a = true }, 1008 .expected = .{ .a = true },
1019 }); 1009 });
1020 try testParseIter(S, .{ 1010 try testParseIter(S, .{}, .{
1021 .args = "-a", 1011 .args = "-a",
1022 .expected = .{ .a = true }, 1012 .expected = .{ .a = true },
1023 }); 1013 });
1024 1014
1025 try testParseIter(S, .{ 1015 try testParseIter(S, .{ .b = .{ .kind = .flag } }, .{
1026 .args = "--b", 1016 .args = "--b",
1027 .expected = .{ .b = 1 }, 1017 .expected = .{ .b = 1 },
1028 .params = .{ .b = .{ .kind = .flag } },
1029 }); 1018 });
1030 try testParseIter(S, .{ 1019 try testParseIter(S, .{ .b = .{ .kind = .flag } }, .{
1031 .args = "-b", 1020 .args = "-b",
1032 .expected = .{ .b = 1 }, 1021 .expected = .{ .b = 1 },
1033 .params = .{ .b = .{ .kind = .flag } },
1034 }); 1022 });
1035 try testParseIter(S, .{ 1023 try testParseIter(S, .{ .b = .{ .kind = .flag } }, .{
1036 .args = "-bb", 1024 .args = "-bb",
1037 .expected = .{ .b = 2 }, 1025 .expected = .{ .b = 2 },
1038 .params = .{ .b = .{ .kind = .flag } },
1039 }); 1026 });
1040 1027
1041 try testParseIter(S, .{ 1028 try testParseIter(S, .{ .b = .{ .kind = .flag } }, .{
1042 .args = "-aabb", 1029 .args = "-aabb",
1043 .expected = .{ .a = true, .b = 2 }, 1030 .expected = .{ .a = true, .b = 2 },
1044 .params = .{ .b = .{ .kind = .flag } },
1045 }); 1031 });
1046 1032
1047 try testParseIter(S, .{ 1033 try testParseIter(S, .{}, .{
1048 .args = "--b 1", 1034 .args = "--b 1",
1049 .expected = .{ .b = 1 }, 1035 .expected = .{ .b = 1 },
1050 }); 1036 });
1051 try testParseIter(S, .{ 1037 try testParseIter(S, .{}, .{
1052 .args = "--b=2", 1038 .args = "--b=2",
1053 .expected = .{ .b = 2 }, 1039 .expected = .{ .b = 2 },
1054 }); 1040 });
1055 1041
1056 try testParseIter(S, .{ 1042 try testParseIter(S, .{}, .{
1057 .args = "-b 1", 1043 .args = "-b 1",
1058 .expected = .{ .b = 1 }, 1044 .expected = .{ .b = 1 },
1059 }); 1045 });
1060 try testParseIter(S, .{ 1046 try testParseIter(S, .{}, .{
1061 .args = "-b=2", 1047 .args = "-b=2",
1062 .expected = .{ .b = 2 }, 1048 .expected = .{ .b = 2 },
1063 }); 1049 });
1064 try testParseIter(S, .{ 1050 try testParseIter(S, .{}, .{
1065 .args = "-b3", 1051 .args = "-b3",
1066 .expected = .{ .b = 3 }, 1052 .expected = .{ .b = 3 },
1067 }); 1053 });
1068 1054
1069 try testParseIter(S, .{ 1055 try testParseIter(S, .{}, .{
1070 .args = "-aab4", 1056 .args = "-aab4",
1071 .expected = .{ .a = true, .b = 4 }, 1057 .expected = .{ .a = true, .b = 4 },
1072 }); 1058 });
1073 1059
1074 try testParseIter(S, .{ 1060 try testParseIter(S, .{}, .{
1075 .args = "--c b", 1061 .args = "--c b",
1076 .expected = .{ .c = .b }, 1062 .expected = .{ .c = .b },
1077 }); 1063 });
1078 try testParseIter(S, .{ 1064 try testParseIter(S, .{}, .{
1079 .args = "--c=c", 1065 .args = "--c=c",
1080 .expected = .{ .c = .c }, 1066 .expected = .{ .c = .c },
1081 }); 1067 });
1082 1068
1083 try testParseIter(S, .{ 1069 try testParseIter(S, .{}, .{
1084 .args = "-c b", 1070 .args = "-c b",
1085 .expected = .{ .c = .b }, 1071 .expected = .{ .c = .b },
1086 }); 1072 });
1087 try testParseIter(S, .{ 1073 try testParseIter(S, .{}, .{
1088 .args = "-c=c", 1074 .args = "-c=c",
1089 .expected = .{ .c = .c }, 1075 .expected = .{ .c = .c },
1090 }); 1076 });
1091 try testParseIter(S, .{ 1077 try testParseIter(S, .{}, .{
1092 .args = "-cd", 1078 .args = "-cd",
1093 .expected = .{ .c = .d }, 1079 .expected = .{ .c = .d },
1094 }); 1080 });
1095 1081
1096 try testParseIter(S, .{ 1082 try testParseIter(S, .{ .b = .{ .kind = .flag } }, .{
1097 .args = "-bbcd", 1083 .args = "-bbcd",
1098 .expected = .{ .b = 2, .c = .d }, 1084 .expected = .{ .b = 2, .c = .d },
1099 .params = .{ .b = .{ .kind = .flag } },
1100 }); 1085 });
1101 1086
1102 var expected_items = [_]usize{ 0, 1, 2 }; 1087 var expected_items = [_]usize{ 0, 1, 2 };
1103 try testParseIter(S, .{ 1088 try testParseIter(S, .{}, .{
1104 .args = "-d 0 -d 1 -d 2", 1089 .args = "-d 0 -d 1 -d 2",
1105 .expected = .{ .d = .{ .items = &expected_items, .capacity = 8 } }, 1090 .expected = .{ .d = .{ .items = &expected_items, .capacity = 8 } },
1106 }); 1091 });
1107 1092
1108 try testParseIter(S, .{ 1093 try testParseIter(S, .{}, .{
1109 .args = "-e 2", 1094 .args = "-e 2",
1110 .expected = .{ .e = 2 }, 1095 .expected = .{ .e = 2 },
1111 }); 1096 });
1112 1097
1113 // Tests that `d` is not leaked when an error occurs 1098 // Tests that `d` is not leaked when an error occurs
1114 try testParseIter(S, .{ 1099 try testParseIter(S, .{}, .{
1115 .args = "-d 0 -d 1 -d 2 -qqqq", 1100 .args = "-d 0 -d 1 -d 2 -qqqq",
1116 .expected = error.ParsingFailed, 1101 .expected = error.ParsingFailed,
1117 }); 1102 });
@@ -1123,33 +1108,29 @@ test "parseIterRequired" {
1123 b: bool, 1108 b: bool,
1124 }; 1109 };
1125 1110
1126 try testParseIter(S, .{ 1111 try testParseIter(S, .{}, .{
1127 .args = "", 1112 .args = "",
1128 .expected = error.ParsingFailed, 1113 .expected = error.ParsingFailed,
1129 }); 1114 });
1130 try testParseIter(S, .{ 1115 try testParseIter(S, .{}, .{
1131 .args = "-b", 1116 .args = "-b",
1132 .expected = .{ .b = true }, 1117 .expected = .{ .b = true },
1133 }); 1118 });
1134 try testParseIter(S, .{ 1119 try testParseIter(S, .{ .a = .{ .required = true } }, .{
1135 .args = "", 1120 .args = "",
1136 .expected = error.ParsingFailed, 1121 .expected = error.ParsingFailed,
1137 .params = .{ .a = .{ .required = true } },
1138 }); 1122 });
1139 try testParseIter(S, .{ 1123 try testParseIter(S, .{ .a = .{ .required = true } }, .{
1140 .args = "-a", 1124 .args = "-a",
1141 .expected = error.ParsingFailed, 1125 .expected = error.ParsingFailed,
1142 .params = .{ .a = .{ .required = true } },
1143 }); 1126 });
1144 try testParseIter(S, .{ 1127 try testParseIter(S, .{ .a = .{ .required = true } }, .{
1145 .args = "-b", 1128 .args = "-b",
1146 .expected = error.ParsingFailed, 1129 .expected = error.ParsingFailed,
1147 .params = .{ .a = .{ .required = true } },
1148 }); 1130 });
1149 try testParseIter(S, .{ 1131 try testParseIter(S, .{ .a = .{ .required = true } }, .{
1150 .args = "-a -b", 1132 .args = "-a -b",
1151 .expected = .{ .a = true, .b = true }, 1133 .expected = .{ .a = true, .b = true },
1152 .params = .{ .a = .{ .required = true } },
1153 }); 1134 });
1154} 1135}
1155 1136
@@ -1160,104 +1141,88 @@ test "parseIterPositional" {
1160 c: enum { a, b, c, d } = .a, 1141 c: enum { a, b, c, d } = .a,
1161 }; 1142 };
1162 1143
1163 try testParseIter(S, .{ 1144 try testParseIter(S, .{ .a = .{ .kind = .positional } }, .{
1164 .args = "true", 1145 .args = "true",
1165 .expected = .{ .a = true }, 1146 .expected = .{ .a = true },
1166 .params = .{ .a = .{ .kind = .positional } },
1167 }); 1147 });
1168 try testParseIter(S, .{ 1148 try testParseIter(S, .{ .a = .{ .kind = .positional } }, .{
1169 .args = "false", 1149 .args = "false",
1170 .expected = .{ .a = false }, 1150 .expected = .{ .a = false },
1171 .params = .{ .a = .{ .kind = .positional } },
1172 }); 1151 });
1173 try testParseIter(S, .{ 1152 try testParseIter(S, .{ .b = .{ .kind = .positional } }, .{
1174 .args = "0", 1153 .args = "0",
1175 .expected = .{ .b = 0 }, 1154 .expected = .{ .b = 0 },
1176 .params = .{ .b = .{ .kind = .positional } },
1177 }); 1155 });
1178 try testParseIter(S, .{ 1156 try testParseIter(S, .{ .b = .{ .kind = .positional } }, .{
1179 .args = "2", 1157 .args = "2",
1180 .expected = .{ .b = 2 }, 1158 .expected = .{ .b = 2 },
1181 .params = .{ .b = .{ .kind = .positional } },
1182 }); 1159 });
1183 try testParseIter(S, .{ 1160 try testParseIter(S, .{ .c = .{ .kind = .positional } }, .{
1184 .args = "a", 1161 .args = "a",
1185 .expected = .{ .c = .a }, 1162 .expected = .{ .c = .a },
1186 .params = .{ .c = .{ .kind = .positional } },
1187 }); 1163 });
1188 try testParseIter(S, .{ 1164 try testParseIter(S, .{ .c = .{ .kind = .positional } }, .{
1189 .args = "c", 1165 .args = "c",
1190 .expected = .{ .c = .c }, 1166 .expected = .{ .c = .c },
1191 .params = .{ .c = .{ .kind = .positional } },
1192 }); 1167 });
1193 1168
1194 try testParseIter(S, .{ 1169 try testParseIter(S, .{
1170 .a = .{ .kind = .positional },
1171 .b = .{ .kind = .positional },
1172 .c = .{ .kind = .positional },
1173 }, .{
1195 .args = "true 2 d", 1174 .args = "true 2 d",
1196 .expected = .{ .a = true, .b = 2, .c = .d }, 1175 .expected = .{ .a = true, .b = 2, .c = .d },
1197 .params = .{
1198 .a = .{ .kind = .positional },
1199 .b = .{ .kind = .positional },
1200 .c = .{ .kind = .positional },
1201 },
1202 }); 1176 });
1203 try testParseIter(S, .{ 1177 try testParseIter(S, .{
1178 .a = .{ .kind = .positional },
1179 .b = .{ .kind = .positional },
1180 .c = .{ .kind = .positional },
1181 }, .{
1204 .args = "false 4 c", 1182 .args = "false 4 c",
1205 .expected = .{ .a = false, .b = 4, .c = .c }, 1183 .expected = .{ .a = false, .b = 4, .c = .c },
1206 .params = .{
1207 .a = .{ .kind = .positional },
1208 .b = .{ .kind = .positional },
1209 .c = .{ .kind = .positional },
1210 },
1211 }); 1184 });
1212 1185
1213 try testParseIter(S, .{ 1186 try testParseIter(S, .{ .a = .{ .kind = .positional } }, .{
1214 .args = "false true", 1187 .args = "false true",
1215 .expected = error.ParsingFailed, 1188 .expected = error.ParsingFailed,
1216 .params = .{ .a = .{ .kind = .positional } },
1217 }); 1189 });
1218 try testParseIter(S, .{ 1190 try testParseIter(S, .{ .a = .{ .kind = .positionals } }, .{
1219 .args = "false true", 1191 .args = "false true",
1220 .expected = .{ .a = true }, 1192 .expected = .{ .a = true },
1221 .params = .{ .a = .{ .kind = .positionals } },
1222 }); 1193 });
1223 try testParseIter(S, .{ 1194 try testParseIter(S, .{ .b = .{ .kind = .positional } }, .{
1224 .args = "2 3", 1195 .args = "2 3",
1225 .expected = error.ParsingFailed, 1196 .expected = error.ParsingFailed,
1226 .params = .{ .b = .{ .kind = .positional } },
1227 }); 1197 });
1228 try testParseIter(S, .{ 1198 try testParseIter(S, .{ .b = .{ .kind = .positionals } }, .{
1229 .args = "2 3", 1199 .args = "2 3",
1230 .expected = .{ .b = 3 }, 1200 .expected = .{ .b = 3 },
1231 .params = .{ .b = .{ .kind = .positionals } },
1232 }); 1201 });
1233 try testParseIter(S, .{ 1202 try testParseIter(S, .{ .c = .{ .kind = .positional } }, .{
1234 .args = "c d", 1203 .args = "c d",
1235 .expected = error.ParsingFailed, 1204 .expected = error.ParsingFailed,
1236 .params = .{ .c = .{ .kind = .positional } },
1237 }); 1205 });
1238 try testParseIter(S, .{ 1206 try testParseIter(S, .{ .c = .{ .kind = .positionals } }, .{
1239 .args = "c d", 1207 .args = "c d",
1240 .expected = .{ .c = .d }, 1208 .expected = .{ .c = .d },
1241 .params = .{ .c = .{ .kind = .positionals } },
1242 }); 1209 });
1243 1210
1244 try testParseIter(S, .{ 1211 try testParseIter(S, .{
1212 .a = .{ .kind = .positional },
1213 .b = .{ .kind = .positional },
1214 .c = .{ .kind = .positional },
1215 }, .{
1245 .args = "true 2 d d", 1216 .args = "true 2 d d",
1246 .expected = error.ParsingFailed, 1217 .expected = error.ParsingFailed,
1247 .params = .{
1248 .a = .{ .kind = .positional },
1249 .b = .{ .kind = .positional },
1250 .c = .{ .kind = .positional },
1251 },
1252 }); 1218 });
1253 try testParseIter(S, .{ 1219 try testParseIter(S, .{
1220 .a = .{ .kind = .positional },
1221 .b = .{ .kind = .positional },
1222 .c = .{ .kind = .positionals },
1223 }, .{
1254 .args = "true 2 c d", 1224 .args = "true 2 c d",
1255 .expected = .{ .a = true, .b = 2, .c = .d }, 1225 .expected = .{ .a = true, .b = 2, .c = .d },
1256 .params = .{
1257 .a = .{ .kind = .positional },
1258 .b = .{ .kind = .positional },
1259 .c = .{ .kind = .positionals },
1260 },
1261 }); 1226 });
1262} 1227}
1263 1228
@@ -1271,15 +1236,15 @@ test "parseIterCommand" {
1271 }, 1236 },
1272 }; 1237 };
1273 1238
1274 try testParseIter(S, .{ 1239 try testParseIter(S, .{}, .{
1275 .args = "sub1", 1240 .args = "sub1",
1276 .expected = .{ .command = .{ .sub1 = .{} } }, 1241 .expected = .{ .command = .{ .sub1 = .{} } },
1277 }); 1242 });
1278 try testParseIter(S, .{ 1243 try testParseIter(S, .{}, .{
1279 .args = "sub1 --a", 1244 .args = "sub1 --a",
1280 .expected = .{ .command = .{ .sub1 = .{ .a = true } } }, 1245 .expected = .{ .command = .{ .sub1 = .{ .a = true } } },
1281 }); 1246 });
1282 try testParseIter(S, .{ 1247 try testParseIter(S, .{}, .{
1283 .args = "--a --b sub1 --a", 1248 .args = "--a --b sub1 --a",
1284 .expected = .{ 1249 .expected = .{
1285 .a = true, 1250 .a = true,
@@ -1287,15 +1252,15 @@ test "parseIterCommand" {
1287 .command = .{ .sub1 = .{ .a = true } }, 1252 .command = .{ .sub1 = .{ .a = true } },
1288 }, 1253 },
1289 }); 1254 });
1290 try testParseIter(S, .{ 1255 try testParseIter(S, .{}, .{
1291 .args = "sub2", 1256 .args = "sub2",
1292 .expected = .{ .command = .{ .sub2 = .{} } }, 1257 .expected = .{ .command = .{ .sub2 = .{} } },
1293 }); 1258 });
1294 try testParseIter(S, .{ 1259 try testParseIter(S, .{}, .{
1295 .args = "sub2 --b", 1260 .args = "sub2 --b",
1296 .expected = .{ .command = .{ .sub2 = .{ .b = true } } }, 1261 .expected = .{ .command = .{ .sub2 = .{ .b = true } } },
1297 }); 1262 });
1298 try testParseIter(S, .{ 1263 try testParseIter(S, .{}, .{
1299 .args = "--a --b sub2 --b", 1264 .args = "--a --b sub2 --b",
1300 .expected = .{ 1265 .expected = .{
1301 .a = true, 1266 .a = true,
@@ -1304,20 +1269,18 @@ test "parseIterCommand" {
1304 }, 1269 },
1305 }); 1270 });
1306 1271
1307 try testParseIter(S, .{ 1272 try testParseIter(S, .{ .command = .{ .command = .{
1273 .sub1 = .{ .name = "bob" },
1274 .sub2 = .{ .name = "kurt" },
1275 } } }, .{
1308 .args = "bob", 1276 .args = "bob",
1309 .params = .{ .command = .{ .command = .{
1310 .sub1 = .{ .name = "bob" },
1311 .sub2 = .{ .name = "kurt" },
1312 } } },
1313 .expected = .{ .command = .{ .sub1 = .{} } }, 1277 .expected = .{ .command = .{ .sub1 = .{} } },
1314 }); 1278 });
1315 try testParseIter(S, .{ 1279 try testParseIter(S, .{ .command = .{ .command = .{
1280 .sub1 = .{ .name = "bob" },
1281 .sub2 = .{ .name = "kurt" },
1282 } } }, .{
1316 .args = "kurt", 1283 .args = "kurt",
1317 .params = .{ .command = .{ .command = .{
1318 .sub1 = .{ .name = "bob" },
1319 .sub2 = .{ .name = "kurt" },
1320 } } },
1321 .expected = .{ .command = .{ .sub2 = .{} } }, 1284 .expected = .{ .command = .{ .sub2 = .{} } },
1322 }); 1285 });
1323} 1286}
@@ -1342,9 +1305,8 @@ test "parseIterHelp" {
1342 1305
1343 const help_args = [_][]const u8{ "-h", "--help", "help" }; 1306 const help_args = [_][]const u8{ "-h", "--help", "help" };
1344 for (help_args) |args| { 1307 for (help_args) |args| {
1345 try testParseIter(S, .{ 1308 try testParseIter(S, .{ .name = "testing-program" }, .{
1346 .args = args, 1309 .args = args,
1347 .params = .{ .name = "testing-program" },
1348 .expected = error.ParsingInterrupted, 1310 .expected = error.ParsingInterrupted,
1349 .expected_out = 1311 .expected_out =
1350 \\Usage: testing-program [OPTIONS] [COMMAND] 1312 \\Usage: testing-program [OPTIONS] [COMMAND]
@@ -1366,19 +1328,18 @@ test "parseIterHelp" {
1366 , 1328 ,
1367 }); 1329 });
1368 try testParseIter(S, .{ 1330 try testParseIter(S, .{
1331 .name = "testing-program",
1332 .description = "This is a test",
1333 .alice = .{ .description = "Who is this?" },
1334 .bob = .{ .description = "Bob the builder" },
1335 .ben = .{ .description = "One of the people of all time" },
1336 .kurt = .{ .description = "No fun allowed" },
1337 .command = .{ .command = .{
1338 .cmd1 = .{ .name = "command1", .description = "Command 1" },
1339 .cmd2 = .{ .name = "command2", .description = "Command 2" },
1340 } },
1341 }, .{
1369 .args = args, 1342 .args = args,
1370 .params = .{
1371 .name = "testing-program",
1372 .description = "This is a test",
1373 .alice = .{ .description = "Who is this?" },
1374 .bob = .{ .description = "Bob the builder" },
1375 .ben = .{ .description = "One of the people of all time" },
1376 .kurt = .{ .description = "No fun allowed" },
1377 .command = .{ .command = .{
1378 .cmd1 = .{ .name = "command1", .description = "Command 1" },
1379 .cmd2 = .{ .name = "command2", .description = "Command 2" },
1380 } },
1381 },
1382 .expected = error.ParsingInterrupted, 1343 .expected = error.ParsingInterrupted,
1383 .expected_out = 1344 .expected_out =
1384 \\This is a test 1345 \\This is a test
@@ -1404,29 +1365,36 @@ test "parseIterHelp" {
1404 } 1365 }
1405} 1366}
1406 1367
1407pub fn HelpOptions(comptime T: type) type { 1368pub const HelpOptions = struct {
1408 return struct { 1369 extra_params: ExtraParameters = .{},
1409 params: Params(T) = .{}, 1370};
1410 extra_params: ExtraParams = .{},
1411 };
1412}
1413 1371
1414const help_long_prefix_len = 4; 1372const help_long_prefix_len = 4;
1415const help_value_prefix_len = 3; 1373const help_value_prefix_len = 3;
1416const help_description_spacing = 2; 1374const help_description_spacing = 2;
1417 1375
1418pub fn help(writer: anytype, comptime T: type, opt: HelpOptions(T)) !void { 1376pub fn help(writer: anytype, comptime T: type, opt: HelpOptions) !void {
1377 const params: Parameters(T) = if (@hasDecl(T, "parameters")) T.parameters else .{};
1378 return helpParameters(writer, T, params, opt);
1379}
1380
1381pub fn helpParameters(
1382 writer: anytype,
1383 comptime T: type,
1384 params: Parameters(T),
1385 opt: HelpOptions,
1386) !void {
1419 const fields = @typeInfo(T).@"struct".fields; 1387 const fields = @typeInfo(T).@"struct".fields;
1420 1388
1421 var self_exe_path_buf: [std.fs.max_path_bytes]u8 = undefined; 1389 var self_exe_path_buf: [std.fs.max_path_bytes]u8 = undefined;
1422 const program_name = opt.params.name orelse blk: { 1390 const program_name = params.name orelse blk: {
1423 const self_exe_path = std.fs.selfExePath(&self_exe_path_buf) catch 1391 const self_exe_path = std.fs.selfExePath(&self_exe_path_buf) catch
1424 break :blk "program"; 1392 break :blk "program";
1425 break :blk std.fs.path.basename(self_exe_path); 1393 break :blk std.fs.path.basename(self_exe_path);
1426 }; 1394 };
1427 1395
1428 if (opt.params.description.len != 0) { 1396 if (params.description.len != 0) {
1429 try writer.writeAll(opt.params.description); 1397 try writer.writeAll(params.description);
1430 try writer.writeAll("\n\n"); 1398 try writer.writeAll("\n\n");
1431 } 1399 }
1432 1400
@@ -1440,10 +1408,10 @@ pub fn help(writer: anytype, comptime T: type, opt: HelpOptions(T)) !void {
1440 if (opt.extra_params.version.command) |v| 1408 if (opt.extra_params.version.command) |v|
1441 padding = @max(padding, v.len); 1409 padding = @max(padding, v.len);
1442 1410
1443 inline for (fields) |field| switch (@field(opt.params, field.name).kind) { 1411 inline for (fields) |field| switch (@field(params, field.name).kind) {
1444 .flag, .option, .positional, .positionals => {}, 1412 .flag, .option, .positional, .positionals => {},
1445 .command => { 1413 .command => {
1446 const param = @field(opt.params, field.name); 1414 const param = @field(params, field.name);
1447 inline for (@typeInfo(@TypeOf(param.command)).@"struct".fields) |cmd_field| { 1415 inline for (@typeInfo(@TypeOf(param.command)).@"struct".fields) |cmd_field| {
1448 const cmd_param = @field(param.command, cmd_field.name); 1416 const cmd_param = @field(param.command, cmd_field.name);
1449 padding = @max(padding, (cmd_param.name orelse cmd_field.name).len); 1417 padding = @max(padding, (cmd_param.name orelse cmd_field.name).len);
@@ -1452,10 +1420,10 @@ pub fn help(writer: anytype, comptime T: type, opt: HelpOptions(T)) !void {
1452 }; 1420 };
1453 1421
1454 try writer.writeAll("\n\nCommands:\n"); 1422 try writer.writeAll("\n\nCommands:\n");
1455 inline for (fields) |field| switch (@field(opt.params, field.name).kind) { 1423 inline for (fields) |field| switch (@field(params, field.name).kind) {
1456 .flag, .option, .positional, .positionals => {}, 1424 .flag, .option, .positional, .positionals => {},
1457 .command => { 1425 .command => {
1458 const param = @field(opt.params, field.name); 1426 const param = @field(params, field.name);
1459 inline for (@typeInfo(@TypeOf(param.command)).@"struct".fields) |cmd_field| { 1427 inline for (@typeInfo(@TypeOf(param.command)).@"struct".fields) |cmd_field| {
1460 const cmd_param = @field(param.command, cmd_field.name); 1428 const cmd_param = @field(param.command, cmd_field.name);
1461 try printCommand(writer, padding, .{ 1429 try printCommand(writer, padding, .{
@@ -1482,7 +1450,7 @@ pub fn help(writer: anytype, comptime T: type, opt: HelpOptions(T)) !void {
1482 if (opt.extra_params.version.long) |v| 1450 if (opt.extra_params.version.long) |v|
1483 padding = @max(padding, v.len + help_long_prefix_len); 1451 padding = @max(padding, v.len + help_long_prefix_len);
1484 inline for (fields) |field| { 1452 inline for (fields) |field| {
1485 const param = @field(opt.params, field.name); 1453 const param = @field(params, field.name);
1486 1454
1487 var pad: usize = 0; 1455 var pad: usize = 0;
1488 if (param.long) |long| 1456 if (param.long) |long|
@@ -1494,7 +1462,7 @@ pub fn help(writer: anytype, comptime T: type, opt: HelpOptions(T)) !void {
1494 1462
1495 try writer.writeAll("\nOptions:\n"); 1463 try writer.writeAll("\nOptions:\n");
1496 inline for (fields) |field| { 1464 inline for (fields) |field| {
1497 const param = @field(opt.params, field.name); 1465 const param = @field(params, field.name);
1498 switch (param.kind) { 1466 switch (param.kind) {
1499 .command, .positional, .positionals => {}, 1467 .command, .positional, .positionals => {},
1500 .flag => try printParam(writer, padding, .{ 1468 .flag => try printParam(writer, padding, .{
@@ -1575,18 +1543,5 @@ fn printParam(writer: anytype, padding: usize, param: struct {
1575 try writer.writeByte('\n'); 1543 try writer.writeByte('\n');
1576} 1544}
1577 1545
1578fn testHelp(comptime T: type, opt: struct {
1579 params: Params(T) = .{},
1580 expected: []const u8,
1581}) !void {
1582 var buf: [std.mem.page_size]u8 = undefined;
1583 var fbs = std.io.fixedBufferStream(&buf);
1584
1585 try help(fbs.writer(), T, .{
1586 .params = opt.params,
1587 });
1588 try std.testing.expectEqualStrings(opt.expected, fbs.getWritten());
1589}
1590
1591const types = @import("types.zig"); 1546const types = @import("types.zig");
1592const std = @import("std"); 1547const std = @import("std");