diff options
Diffstat (limited to 'clap/v2.zig')
| -rw-r--r-- | clap/v2.zig | 483 |
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 @@ | |||
| 1 | pub fn Params(comptime T: type) type { | 1 | pub 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 | ||
| 278 | fn validateParams(comptime T: type, name: []const u8, opt: ParseOptions(T)) !void { | 278 | fn 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 | ||
| 376 | fn testValidateParams(comptime T: type, opt: struct { | 377 | fn 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 | |||
| 389 | fn 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 | ||
| 397 | test validateParams { | 405 | test 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 | ||
| 625 | pub const ExtraParams = struct { | 633 | pub const ExtraParameters = struct { |
| 626 | help: HelpParam = .{}, | 634 | help: HelpParam = .{}, |
| 627 | version: VersionParam = .{}, | 635 | version: VersionParam = .{}, |
| 628 | }; | 636 | }; |
| 629 | 637 | ||
| 630 | pub fn ParseOptions(comptime T: type) type { | 638 | pub 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 | ||
| 661 | pub const ParseError = error{ | 653 | pub const ParseError = error{ |
| 662 | ParsingInterrupted, | 654 | ParsingInterrupted, |
| 663 | ParsingFailed, | 655 | ParsingFailed, |
| 664 | } || std.mem.Allocator.Error; | 656 | } || std.mem.Allocator.Error; |
| 665 | 657 | ||
| 666 | pub fn parseIter(it: anytype, comptime T: type, opt: ParseOptions(T)) ParseError!T { | 658 | pub 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); | 663 | pub 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 | ||
| 678 | fn Parser(comptime Iter: type, comptime T: type) type { | 674 | fn 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 | ||
| 966 | fn testParseIter(comptime T: type, opt: struct { | 958 | fn 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 | ||
| 1003 | test "parseIterParams" { | 993 | test "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 | ||
| 1407 | pub fn HelpOptions(comptime T: type) type { | 1368 | pub const HelpOptions = struct { |
| 1408 | return struct { | 1369 | extra_params: ExtraParameters = .{}, |
| 1409 | params: Params(T) = .{}, | 1370 | }; |
| 1410 | extra_params: ExtraParams = .{}, | ||
| 1411 | }; | ||
| 1412 | } | ||
| 1413 | 1371 | ||
| 1414 | const help_long_prefix_len = 4; | 1372 | const help_long_prefix_len = 4; |
| 1415 | const help_value_prefix_len = 3; | 1373 | const help_value_prefix_len = 3; |
| 1416 | const help_description_spacing = 2; | 1374 | const help_description_spacing = 2; |
| 1417 | 1375 | ||
| 1418 | pub fn help(writer: anytype, comptime T: type, opt: HelpOptions(T)) !void { | 1376 | pub 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 | |||
| 1381 | pub 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 | ||
| 1578 | fn 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 | |||
| 1591 | const types = @import("types.zig"); | 1546 | const types = @import("types.zig"); |
| 1592 | const std = @import("std"); | 1547 | const std = @import("std"); |