| ... | ... | @@ -293,7 +293,7 @@ fn innerParse(comptime Args: type, allocator: Allocator, iter: anytype, prog: [] |
| 293 | 293 | file_writer.interface.flush() catch {}; |
| 294 | 294 | } |
| 295 | 295 | } else { |
| 296 | | printGeneratedHelp(writer, prog, named_fields); |
| 296 | printGeneratedHelp(named_fields, positional_fields, writer, prog); |
| 297 | 297 | } |
| 298 | 298 | if (exit_on_error) { |
| 299 | 299 | std.process.exit(0); |
| ... | ... | @@ -304,7 +304,7 @@ fn innerParse(comptime Args: type, allocator: Allocator, iter: anytype, prog: [] |
| 304 | 304 | if (!the_rest_is_positional and arg.len >= 2 and arg[0] == '-' and isAlphabetic(arg[1])) { |
| 305 | 305 | // Always invalid. |
| 306 | 306 | // Examples: -h, -flag, -I/path |
| 307 | | return usageError(writer, "unrecognized argument: {s}", .{arg}, exit_on_error); |
| 307 | return usageError(named_fields, positional_fields, writer, "unrecognized argument: {s}", .{arg}, prog, exit_on_error); |
| 308 | 308 | } |
| 309 | 309 | if (!the_rest_is_positional and mem.eql(u8, arg, "--")) { |
| 310 | 310 | // Stop recognizing named arguments. Everything else is positional. |
| ... | ... | @@ -314,14 +314,14 @@ fn innerParse(comptime Args: type, allocator: Allocator, iter: anytype, prog: [] |
| 314 | 314 | if (the_rest_is_positional or !(arg.len >= 3 and arg[0] == '-' and arg[1] == '-')) { |
| 315 | 315 | // Positional. |
| 316 | 316 | // Examples: "", "a", "-", "-1", "other" |
| 317 | | if (positional_field_index >= positional_fields.len) return usageError(writer, "unexpected positional argument: {s}", .{arg}, exit_on_error); |
| 317 | if (positional_field_index >= positional_fields.len) return usageError(named_fields, positional_fields, writer, "unexpected positional argument: {s}", .{arg}, prog, exit_on_error); |
| 318 | 318 | inline for (positional_fields, 0..) |field, i| { |
| 319 | 319 | if (positional_field_index == i) { |
| 320 | 320 | if (getArrayChild(field.type)) |C| { |
| 321 | | try @field(positional_array_lists, field.name).append(allocator, try parseValue(C, arg, field.name, writer, exit_on_error)); |
| 321 | try @field(positional_array_lists, field.name).append(allocator, try parseValue(named_fields, positional_fields, C, arg, field.name, writer, prog, exit_on_error)); |
| 322 | 322 | // Don't increment positional_field_index. |
| 323 | 323 | } else { |
| 324 | | @field(result.positional, field.name) = try parseValue(field.type, arg, field.name, writer, exit_on_error); |
| 324 | @field(result.positional, field.name) = try parseValue(named_fields, positional_fields, field.type, arg, field.name, writer, prog, exit_on_error); |
| 325 | 325 | positional_field_index += 1; |
| 326 | 326 | } |
| 327 | 327 | break; |
| ... | ... | @@ -349,25 +349,25 @@ fn innerParse(comptime Args: type, allocator: Allocator, iter: anytype, prog: [] |
| 349 | 349 | if (mem.eql(u8, field.name, arg_name)) { |
| 350 | 350 | named_fields_seen[i] = true; |
| 351 | 351 | if (field.type == bool) { |
| 352 | | if (immediate_value != null) return usageError(writer, "cannot specify value for bool argument: {s}", .{arg}, exit_on_error); |
| 352 | if (immediate_value != null) return usageError(named_fields, positional_fields, writer, "cannot specify value for bool argument: {s}", .{arg}, prog, exit_on_error); |
| 353 | 353 | @field(result.named, field.name) = !no_prefixed; |
| 354 | 354 | break; |
| 355 | 355 | } |
| 356 | | if (no_prefixed) return usageError(writer, "unrecognized argument: {s}", .{arg}, exit_on_error); |
| 356 | if (no_prefixed) return usageError(named_fields, positional_fields, writer, "unrecognized argument: {s}", .{arg}, prog, exit_on_error); |
| 357 | 357 | |
| 358 | 358 | // All other argument types require a value. |
| 359 | | const arg_value = immediate_value orelse iter.next() orelse return usageError(writer, "expected argument after --{s}", .{field.name}, exit_on_error); |
| 359 | const arg_value = immediate_value orelse iter.next() orelse return usageError(named_fields, positional_fields, writer, "expected argument after --{s}", .{field.name}, prog, exit_on_error); |
| 360 | 360 | |
| 361 | 361 | if (getArrayChild(field.type)) |C| { |
| 362 | | try @field(named_array_lists, field.name).append(allocator, try parseValue(C, arg_value, field.name, writer, exit_on_error)); |
| 362 | try @field(named_array_lists, field.name).append(allocator, try parseValue(named_fields, positional_fields, C, arg_value, field.name, writer, prog, exit_on_error)); |
| 363 | 363 | } else { |
| 364 | | @field(result.named, field.name) = try parseValue(field.type, arg_value, field.name, writer, exit_on_error); |
| 364 | @field(result.named, field.name) = try parseValue(named_fields, positional_fields, field.type, arg_value, field.name, writer, prog, exit_on_error); |
| 365 | 365 | } |
| 366 | 366 | break; |
| 367 | 367 | } |
| 368 | 368 | } else { |
| 369 | 369 | // Didn't match anything. |
| 370 | | return usageError(writer, "unrecognized argument: {s}", .{arg}, exit_on_error); |
| 370 | return usageError(named_fields, positional_fields, writer, "unrecognized argument: {s}", .{arg}, prog, exit_on_error); |
| 371 | 371 | } |
| 372 | 372 | } |
| 373 | 373 | |
| ... | ... | @@ -384,9 +384,9 @@ fn innerParse(comptime Args: type, allocator: Allocator, iter: anytype, prog: [] |
| 384 | 384 | @field(result.named, field.name) = default; |
| 385 | 385 | } else { |
| 386 | 386 | if (field.type == bool) { |
| 387 | | return usageError(writer, "missing required argument: --" ++ field.name ++ " or --no-" ++ field.name, .{}, exit_on_error); |
| 387 | return usageError(named_fields, positional_fields, writer, "missing required argument: --" ++ field.name ++ " or --no-" ++ field.name, .{}, prog, exit_on_error); |
| 388 | 388 | } else { |
| 389 | | return usageError(writer, "missing required argument: --" ++ field.name, .{}, exit_on_error); |
| 389 | return usageError(named_fields, positional_fields, writer, "missing required argument: --" ++ field.name, .{}, prog, exit_on_error); |
| 390 | 390 | } |
| 391 | 391 | } |
| 392 | 392 | } |
| ... | ... | @@ -403,7 +403,7 @@ fn innerParse(comptime Args: type, allocator: Allocator, iter: anytype, prog: [] |
| 403 | 403 | if (field.defaultValue()) |default| { |
| 404 | 404 | @field(result.positional, field.name) = default; |
| 405 | 405 | } else { |
| 406 | | return usageError(writer, "missing required argument: " ++ field.name, .{}, exit_on_error); |
| 406 | return usageError(named_fields, positional_fields, writer, "missing required argument: " ++ field.name, .{}, prog, exit_on_error); |
| 407 | 407 | } |
| 408 | 408 | } |
| 409 | 409 | } |
| ... | ... | @@ -413,22 +413,22 @@ fn innerParse(comptime Args: type, allocator: Allocator, iter: anytype, prog: [] |
| 413 | 413 | } |
| 414 | 414 | |
| 415 | 415 | /// arg_value is []const u8 or [:0]const u8. |
| 416 | | fn parseValue(comptime T: type, arg_value: anytype, comptime field_name: []const u8, writer: ?*Writer, exit_on_error: bool) !T { |
| 416 | fn parseValue(comptime named_fields: []const StructField, comptime positional_fields: []const StructField, comptime T: type, arg_value: anytype, comptime field_name: []const u8, writer: ?*Writer, prog: []const u8, exit_on_error: bool) !T { |
| 417 | 417 | switch (@typeInfo(T)) { |
| 418 | 418 | .bool => comptime unreachable, // Handled elsewhere. |
| 419 | 419 | .float => { |
| 420 | 420 | return std.fmt.parseFloat(T, arg_value) catch |err| { |
| 421 | | return usageError(writer, "unable to parse --{s}={s}: {s}", .{ field_name, arg_value, @errorName(err) }, exit_on_error); |
| 421 | return usageError(named_fields, positional_fields, writer, "unable to parse --{s}={s}: {s}", .{ field_name, arg_value, @errorName(err) }, prog, exit_on_error); |
| 422 | 422 | }; |
| 423 | 423 | }, |
| 424 | 424 | .int => { |
| 425 | 425 | return std.fmt.parseInt(T, arg_value, 0) catch |err| { |
| 426 | | return usageError(writer, "unable to parse --{s}={s}: {s}", .{ field_name, arg_value, @errorName(err) }, exit_on_error); |
| 426 | return usageError(named_fields, positional_fields, writer, "unable to parse --{s}={s}: {s}", .{ field_name, arg_value, @errorName(err) }, prog, exit_on_error); |
| 427 | 427 | }; |
| 428 | 428 | }, |
| 429 | 429 | .@"enum" => { |
| 430 | 430 | return std.meta.stringToEnum(T, arg_value) orelse { |
| 431 | | return usageError(writer, "unrecognized value: --{s}={s}, expected one of: {s}", .{ field_name, arg_value, enumValuesExpr(T) }, exit_on_error); |
| 431 | return usageError(named_fields, positional_fields, writer, "unrecognized value: --{s}={s}, expected one of: {s}", .{ field_name, arg_value, enumValuesExpr(T) }, prog, exit_on_error); |
| 432 | 432 | }; |
| 433 | 433 | }, |
| 434 | 434 | .pointer => |ptrInfo| { |
| ... | ... | @@ -574,12 +574,20 @@ fn ArrayListsForFields(comptime fields: []const StructField) type { |
| 574 | 574 | /// call this function to produce the same error behavior as if this API's validation failed. |
| 575 | 575 | /// An error message will be written to `options.writer` or stderr by default, and `error.Usage` is returned. |
| 576 | 576 | /// The given `msg` template is prefixed by `"error: "` and suffixed by a newline and a prompt to try passing in `--help`. |
| 577 | | /// `options.prog` is not used by this function, but could be in the future. |
| 577 | /// `options.prog` is not used by this function, but could be in the future. TODO: yes it is. |
| 578 | 578 | /// |
| 579 | 579 | /// This function calls `std.process.exit` with an error status unless `options.exit` is set to `false`, in which case it returns `error.Usage`. |
| 580 | 580 | /// This matches the default behavior of `parse`, not `parseIter` or `parseSlice`. |
| 581 | | pub fn @"error"(comptime msg: []const u8, args: anytype, options: Options) error{Usage} { |
| 582 | | return usageError(options.writer, msg, args, options.exit orelse true); |
| 581 | pub fn @"error"(comptime Args: type, comptime msg: []const u8, msg_args: anytype, options: Options) error{Usage} { |
| 582 | const named_fields, const positional_fields = comptime checkArgsType(Args); |
| 583 | var buf: [0x1000]u8 = undefined; |
| 584 | const prog: ?[]const u8 = options.prog orelse blk: { |
| 585 | var fba: std.heap.FixedBufferAllocator = .init(&buf); |
| 586 | var iter = ArgIterator.initWithAllocator(fba.allocator()) catch break :blk null; |
| 587 | const argv0 = iter.next(); |
| 588 | break :blk if (argv0) |arg| std.fs.path.basename(arg) else null; |
| 589 | }; |
| 590 | return usageError(named_fields, positional_fields, options.writer, msg, msg_args, prog orelse "<prog>", options.exit orelse true); |
| 583 | 591 | } |
| 584 | 592 | |
| 585 | 593 | test @"error" { |
| ... | ... | @@ -597,25 +605,8 @@ test @"error" { |
| 597 | 605 | const args = try parseSlice(Args, arena.allocator(), &[_][]const u8{ "--output=o.txt", "i.txt" }, .{}); |
| 598 | 606 | |
| 599 | 607 | if (std.fs.path.isAbsolute(args.named.output)) { |
| 600 | | return std.cli.@"error"("--output must not be absolute: {s}", .{args.named.output}, .{ .exit = false }); |
| 601 | | } |
| 602 | | } |
| 603 | | |
| 604 | | fn usageError(writer: ?*Writer, comptime msg: []const u8, args: anytype, exit_on_error: bool) error{Usage} { |
| 605 | | const whole_msg = |
| 606 | | "error: " ++ msg ++ "\n" ++ |
| 607 | | \\try --help for full help info |
| 608 | | \\ |
| 609 | | ; |
| 610 | | if (writer) |w| { |
| 611 | | w.print(whole_msg, args) catch {}; |
| 612 | | } else { |
| 613 | | std.debug.print(whole_msg, args); |
| 608 | return std.cli.@"error"(Args, "--output must not be absolute: {s}", .{args.named.output}, .{ .exit = false }); |
| 614 | 609 | } |
| 615 | | if (exit_on_error) { |
| 616 | | std.process.exit(1); |
| 617 | | } |
| 618 | | return error.Usage; |
| 619 | 610 | } |
| 620 | 611 | |
| 621 | 612 | fn ArgIteratorSlice(comptime String: type) type { |
| ... | ... | @@ -644,53 +635,125 @@ fn enumValuesExpr(comptime Enum: type) []const u8 { |
| 644 | 635 | return values_str; |
| 645 | 636 | } |
| 646 | 637 | |
| 647 | | fn printGeneratedHelp(writer: ?*Writer, prog: []const u8, comptime named_fields: []const StructField) void { |
| 648 | | const msg = // |
| 649 | | \\usage: {s} [options] [arg...] |
| 650 | | \\ |
| 651 | | \\arguments:{s} |
| 652 | | \\ --help |
| 653 | | \\ |
| 654 | | ; |
| 655 | | comptime var arguments_str: []const u8 = ""; |
| 638 | fn usageError(comptime named_fields: []const StructField, comptime positional_fields: []const StructField, writer: ?*Writer, comptime msg: []const u8, args: anytype, prog: []const u8, exit_on_error: bool) error{Usage} { |
| 639 | const whole_msg = |
| 640 | "error: " ++ msg ++ "\n" ++ // |
| 641 | "usage: {s} " ++ comptime usageLineFmt(named_fields, positional_fields) ++ "\n" ++ |
| 642 | \\try --help for full help info |
| 643 | \\ |
| 644 | ; |
| 645 | if (writer) |w| { |
| 646 | w.print(whole_msg, args ++ .{prog}) catch {}; |
| 647 | } else { |
| 648 | std.debug.print(whole_msg, args ++ .{prog}); |
| 649 | } |
| 650 | if (exit_on_error) { |
| 651 | std.process.exit(1); |
| 652 | } |
| 653 | return error.Usage; |
| 654 | } |
| 655 | |
| 656 | /// returns a string with all "{" escaped for passing into std.fmt. |
| 657 | fn usageLineFmt(comptime named_fields: []const StructField, comptime positional_fields: []const StructField) []const u8 { |
| 658 | comptime var usage_parts: []const []const u8 = &.{}; |
| 659 | var at_least_one_optional_named_argument = false; |
| 660 | inline for (named_fields) |field| { |
| 661 | if (field.default_value_ptr != null) { |
| 662 | // Don't mention optional named arguments. |
| 663 | at_least_one_optional_named_argument = true; |
| 664 | continue; |
| 665 | } |
| 666 | usage_parts = usage_parts ++ .{switch (@typeInfo(field.type)) { |
| 667 | .bool => "--[no-]" ++ field.name, |
| 668 | .int, .float => "--" ++ field.name ++ "=" ++ @typeName(field.type), |
| 669 | .@"enum" => "--" ++ field.name ++ "=" ++ enumValuesExpr(field.type), |
| 670 | else => blk: { |
| 671 | comptime assert(@typeInfo(field.type).pointer.size == .slice and @typeInfo(field.type).pointer.child == u8); |
| 672 | break :blk "--" ++ field.name ++ "=string"; |
| 673 | }, |
| 674 | }}; |
| 675 | } |
| 676 | |
| 677 | if (at_least_one_optional_named_argument) { |
| 678 | // Prepend with an [options] placeholder. |
| 679 | usage_parts = [_][]const u8{"[options]"} ++ usage_parts; |
| 680 | } |
| 681 | |
| 682 | inline for (positional_fields) |field| { |
| 683 | if (field.default_value_ptr != null) { |
| 684 | if (getArrayChild(field.type) != null) { |
| 685 | // Array |
| 686 | usage_parts = usage_parts ++ .{"[" ++ field.name ++ "...]"}; |
| 687 | } else { |
| 688 | // Scalar |
| 689 | usage_parts = usage_parts ++ .{"[" ++ field.name ++ "]"}; |
| 690 | } |
| 691 | } else { |
| 692 | usage_parts = usage_parts ++ .{field.name}; |
| 693 | } |
| 694 | } |
| 695 | |
| 696 | comptime var usage_str: []const u8 = ""; |
| 697 | inline for (usage_parts) |part| { |
| 698 | if (usage_str.len > 0) { |
| 699 | usage_str = usage_str ++ " "; |
| 700 | } |
| 701 | usage_str = usage_str ++ part; |
| 702 | } |
| 703 | return escapeFmt(usage_str); |
| 704 | } |
| 705 | fn printGeneratedHelp(comptime named_fields: []const StructField, comptime positional_fields: []const StructField, writer: ?*Writer, prog: []const u8) void { |
| 706 | comptime var arguments_table: []const []const []const u8 = &.{}; |
| 707 | |
| 708 | comptime var arguments_str: []const u8 = ""; // TODO: delete |
| 709 | |
| 710 | if (positional_fields.len > 0) { |
| 711 | arguments_table = arguments_table ++ .{&[_][]const u8{"positional arguments:"}}; |
| 712 | } |
| 713 | //inline for (positional_fields) |field| {} |
| 714 | |
| 715 | arguments_table = arguments_table ++ .{&[_][]const u8{"named arguments:"}}; // The --help option is always there. |
| 656 | 716 | inline for (named_fields) |field| { |
| 657 | 717 | switch (@typeInfo(field.type)) { |
| 658 | 718 | .bool => { |
| 659 | 719 | if (field.defaultValue()) |default| { |
| 660 | 720 | if (default) { |
| 661 | | arguments_str = arguments_str ++ "\n --no-" ++ field.name ++ " default: --" ++ field.name; |
| 721 | arguments_table = arguments_table ++ .{&[_][]const u8{ " --no-" ++ field.name, "default: --" ++ field.name }}; |
| 662 | 722 | } else { |
| 663 | | arguments_str = arguments_str ++ "\n --" ++ field.name ++ " default: --no-" ++ field.name; |
| 723 | arguments_table = arguments_table ++ .{&[_][]const u8{ " --" ++ field.name, "default: --no-" ++ field.name }}; |
| 664 | 724 | } |
| 665 | 725 | } else { |
| 666 | | arguments_str = arguments_str ++ "\n --" ++ field.name ++ " or --no-" ++ field.name ++ " required"; |
| 726 | arguments_table = arguments_table ++ .{&[_][]const u8{ " --[no-]" ++ field.name, "required" }}; |
| 667 | 727 | } |
| 668 | 728 | }, |
| 669 | 729 | .int, .float => { |
| 670 | | arguments_str = arguments_str ++ "\n --" ++ field.name ++ " " ++ @typeName(field.type); |
| 671 | | if (field.defaultValue()) |default| { |
| 672 | | arguments_str = arguments_str ++ " default: " ++ std.fmt.comptimePrint("{}", .{default}); |
| 673 | | } else { |
| 674 | | arguments_str = arguments_str ++ " required"; |
| 675 | | } |
| 730 | arguments_table = arguments_table ++ .{&[_][]const u8{ |
| 731 | " --" ++ field.name ++ "=" ++ @typeName(field.type), |
| 732 | if (field.defaultValue()) |default| |
| 733 | "default: " ++ std.fmt.comptimePrint("{}", .{default}) |
| 734 | else |
| 735 | "required", |
| 736 | }}; |
| 676 | 737 | }, |
| 677 | 738 | .@"enum" => { |
| 678 | | arguments_str = arguments_str ++ "\n --" ++ field.name ++ " " ++ comptime enumValuesExpr(field.type); |
| 679 | | if (field.defaultValue()) |default| { |
| 680 | | arguments_str = arguments_str ++ " default: " ++ quoteIfEmpty(@tagName(default)); |
| 681 | | } else { |
| 682 | | arguments_str = arguments_str ++ " required"; |
| 683 | | } |
| 739 | arguments_table = arguments_table ++ .{&[_][]const u8{ |
| 740 | " --" ++ field.name ++ "=" ++ comptime enumValuesExpr(field.type), |
| 741 | if (field.defaultValue()) |default| |
| 742 | "default: " ++ @tagName(default) |
| 743 | else |
| 744 | "required", |
| 745 | }}; |
| 684 | 746 | }, |
| 685 | 747 | .pointer => |ptrInfo| { |
| 686 | 748 | if (ptrInfo.size == .slice and ptrInfo.child == u8) { |
| 687 | | // String. |
| 688 | | arguments_str = arguments_str ++ "\n --" ++ field.name ++ " string"; |
| 689 | | if (field.defaultValue()) |default| { |
| 690 | | arguments_str = arguments_str ++ " default: " ++ quoteIfEmpty(default); |
| 691 | | } else { |
| 692 | | arguments_str = arguments_str ++ " required"; |
| 693 | | } |
| 749 | // String |
| 750 | arguments_table = arguments_table ++ .{&[_][]const u8{ |
| 751 | " --" ++ field.name ++ "=string", |
| 752 | if (field.defaultValue()) |default| |
| 753 | "default: " ++ quoteIfEmpty(default) |
| 754 | else |
| 755 | "required", |
| 756 | }}; |
| 694 | 757 | } else { |
| 695 | 758 | // Array |
| 696 | 759 | const type_name = switch (@typeInfo(ptrInfo.child)) { |
| ... | ... | @@ -703,18 +766,43 @@ fn printGeneratedHelp(writer: ?*Writer, prog: []const u8, comptime named_fields: |
| 703 | 766 | arguments_str = arguments_str ++ "\n " ++ // |
| 704 | 767 | "--" ++ field.name ++ " " ++ type_name ++ " " ++ // |
| 705 | 768 | "[--" ++ field.name ++ " " ++ type_name ++ " ...]"; |
| 769 | arguments_table = arguments_table ++ .{&[_][]const u8{ |
| 770 | " --" ++ field.name ++ "=" ++ type_name ++ " " ++ // |
| 771 | "[--" ++ field.name ++ "=" ++ type_name ++ " ...]", |
| 772 | }}; |
| 706 | 773 | } |
| 707 | 774 | }, |
| 708 | | else => @compileError("Unsupported field type: " ++ @typeName(field.type)), |
| 775 | else => comptime unreachable, |
| 776 | } |
| 777 | } |
| 778 | |
| 779 | arguments_table = arguments_table ++ .{&[_][]const u8{ " --help", "print this help and exit" }}; |
| 780 | |
| 781 | comptime var width = 0; |
| 782 | inline for (arguments_table) |row| { |
| 783 | width = @max(width, row[0].len); |
| 784 | } |
| 785 | |
| 786 | comptime var help_str: []const u8 = ""; |
| 787 | inline for (arguments_table) |row| { |
| 788 | help_str = help_str ++ "\n"; |
| 789 | inline for (row, 0..) |cell, c| { |
| 790 | help_str = help_str ++ cell; |
| 791 | if (c == 0 and row.len > 1) { |
| 792 | help_str = help_str ++ " " ** (width + 2 - cell.len); |
| 793 | } |
| 709 | 794 | } |
| 710 | 795 | } |
| 796 | |
| 797 | const msg = "usage: {s} " ++ comptime usageLineFmt(named_fields, positional_fields) ++ // |
| 798 | escapeFmt(help_str) ++ "\n"; |
| 711 | 799 | if (writer) |w| { |
| 712 | | w.print(msg, .{ prog, arguments_str }) catch {}; |
| 800 | w.print(msg, .{prog}) catch {}; |
| 713 | 801 | w.flush() catch {}; |
| 714 | 802 | } else { |
| 715 | 803 | var buffer: [0x100]u8 = undefined; |
| 716 | 804 | var file_writer = std.fs.File.stdout().writer(&buffer); |
| 717 | | file_writer.interface.print(msg, .{ prog, arguments_str }) catch {}; |
| 805 | file_writer.interface.print(msg, .{prog}) catch {}; |
| 718 | 806 | file_writer.interface.flush() catch {}; |
| 719 | 807 | } |
| 720 | 808 | } |
| ... | ... | @@ -724,6 +812,26 @@ inline fn quoteIfEmpty(comptime s: []const u8) []const u8 { |
| 724 | 812 | return s; |
| 725 | 813 | } |
| 726 | 814 | |
| 815 | inline fn escapeFmt(comptime s: []const u8) []const u8 { |
| 816 | var result: []const u8 = ""; |
| 817 | comptime var cursor = 0; |
| 818 | for (s, 0..) |c, i| { |
| 819 | switch (c) { |
| 820 | '{' => { |
| 821 | result = result ++ s[cursor..i] ++ "{{"; |
| 822 | cursor = i + 1; |
| 823 | }, |
| 824 | '}' => { |
| 825 | result = result ++ s[cursor..i] ++ "}}"; |
| 826 | cursor = i + 1; |
| 827 | }, |
| 828 | else => {}, |
| 829 | } |
| 830 | } |
| 831 | result = result ++ s[cursor..]; |
| 832 | return result; |
| 833 | } |
| 834 | |
| 727 | 835 | var failing_writer: Writer = .failing; |
| 728 | 836 | const silent_options = Options{ .writer = &failing_writer, .exit = false }; |
| 729 | 837 | |
| ... | ... | @@ -1310,8 +1418,8 @@ test "actually calling error" { |
| 1310 | 1418 | "--output=/absolute/path", "too", "many", "other", "args", |
| 1311 | 1419 | }, .{}); |
| 1312 | 1420 | |
| 1313 | | try testing.expectEqual(error.Usage, std.cli.@"error"("--output must not be absolute: {s}", .{args.named.output}, silent_options)); |
| 1314 | | try testing.expectEqual(error.Usage, std.cli.@"error"("expected exactly 1 positional arg", .{}, silent_options)); |
| 1421 | try testing.expectEqual(error.Usage, @"error"(Args, "--output must not be absolute: {s}", .{args.named.output}, silent_options)); |
| 1422 | try testing.expectEqual(error.Usage, @"error"(Args, "expected exactly 1 positional arg", .{}, silent_options)); |
| 1315 | 1423 | } |
| 1316 | 1424 | |
| 1317 | 1425 | test "custom help" { |