| ... | ... | @@ -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(named_fields, positional_fields, writer, prog); |
| 296 | printGeneratedHelp(Args, 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(named_fields, positional_fields, writer, "unrecognized argument: {s}", .{arg}, prog, exit_on_error); |
| 307 | return usageError(Args, 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(named_fields, positional_fields, writer, "unexpected positional argument: {s}", .{arg}, prog, exit_on_error); |
| 317 | if (positional_field_index >= positional_fields.len) return usageError(Args, 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(named_fields, positional_fields, C, arg, field.name, writer, prog, exit_on_error)); |
| 321 | try @field(positional_array_lists, field.name).append(allocator, try parseValue(Args, 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(named_fields, positional_fields, field.type, arg, field.name, writer, prog, exit_on_error); |
| 324 | @field(result.positional, field.name) = try parseValue(Args, 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(named_fields, positional_fields, writer, "cannot specify value for bool argument: {s}", .{arg}, prog, exit_on_error); |
| 352 | if (immediate_value != null) return usageError(Args, 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(named_fields, positional_fields, writer, "unrecognized argument: {s}", .{arg}, prog, exit_on_error); |
| 356 | if (no_prefixed) return usageError(Args, 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(named_fields, positional_fields, writer, "expected argument after --{s}", .{field.name}, prog, exit_on_error); |
| 359 | const arg_value = immediate_value orelse iter.next() orelse return usageError(Args, 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(named_fields, positional_fields, C, arg_value, field.name, writer, prog, exit_on_error)); |
| 362 | try @field(named_array_lists, field.name).append(allocator, try parseValue(Args, C, arg_value, field.name, writer, prog, exit_on_error)); |
| 363 | 363 | } else { |
| 364 | | @field(result.named, field.name) = try parseValue(named_fields, positional_fields, field.type, arg_value, field.name, writer, prog, exit_on_error); |
| 364 | @field(result.named, field.name) = try parseValue(Args, 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(named_fields, positional_fields, writer, "unrecognized argument: {s}", .{arg}, prog, exit_on_error); |
| 370 | return usageError(Args, 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(named_fields, positional_fields, writer, "missing required argument: --" ++ field.name ++ " or --no-" ++ field.name, .{}, prog, exit_on_error); |
| 387 | return usageError(Args, writer, "missing required argument: --" ++ field.name ++ " or --no-" ++ field.name, .{}, prog, exit_on_error); |
| 388 | 388 | } else { |
| 389 | | return usageError(named_fields, positional_fields, writer, "missing required argument: --" ++ field.name, .{}, prog, exit_on_error); |
| 389 | return usageError(Args, 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(named_fields, positional_fields, writer, "missing required argument: " ++ field.name, .{}, prog, exit_on_error); |
| 406 | return usageError(Args, 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 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 { |
| 416 | fn parseValue(comptime Args: type, 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(named_fields, positional_fields, writer, "unable to parse --{s}={s}: {s}", .{ field_name, arg_value, @errorName(err) }, prog, exit_on_error); |
| 421 | return usageError(Args, 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(named_fields, positional_fields, writer, "unable to parse --{s}={s}: {s}", .{ field_name, arg_value, @errorName(err) }, prog, exit_on_error); |
| 426 | return usageError(Args, 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(named_fields, positional_fields, writer, "unrecognized value: --{s}={s}, expected one of: {s}", .{ field_name, arg_value, enumValuesExpr(T) }, prog, exit_on_error); |
| 431 | return usageError(Args, 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| { |
| ... | ... | @@ -451,8 +451,8 @@ fn checkArgsType(comptime Args: type) struct { []const StructField, []const Stru |
| 451 | 451 | } else @compileError("unrecognized Args name: " ++ field.name); |
| 452 | 452 | } |
| 453 | 453 | |
| 454 | | const named_fields = if (has_named) @typeInfo(@TypeOf(@as(Args, undefined).named)).@"struct".fields else &.{}; |
| 455 | | const positional_fields = if (has_positional) @typeInfo(@TypeOf(@as(Args, undefined).positional)).@"struct".fields else &.{}; |
| 454 | const named_fields = if (has_named) @typeInfo(@FieldType(Args, "named")).@"struct".fields else &.{}; |
| 455 | const positional_fields = if (has_positional) @typeInfo(@FieldType(Args, "positional")).@"struct".fields else &.{}; |
| 456 | 456 | |
| 457 | 457 | // Named arguments are more lenient. |
| 458 | 458 | inline for (named_fields) |field| { |
| ... | ... | @@ -508,15 +508,16 @@ fn validateField(field: StructField) void { |
| 508 | 508 | // String. |
| 509 | 509 | } else { |
| 510 | 510 | // Array. |
| 511 | | if (field.default_value_ptr == null) @compileError("Array arguments must have a default value: " ++ field.name); |
| 511 | if (field.defaultValue()) |default| { |
| 512 | if (default.len != 0) @compileError("Array argument default value must have 0 len: " ++ field.name); |
| 513 | } else @compileError("Array arguments must have a default value: " ++ field.name); |
| 512 | 514 | switch (@typeInfo(ptrInfo.child)) { |
| 513 | 515 | .bool => @compileError("Unsupported field type: " ++ @typeName(field.type)), |
| 514 | 516 | .float => {}, |
| 515 | 517 | .int => {}, |
| 516 | 518 | .@"enum" => @compileError("Unsupported field type: " ++ @typeName(field.type)), |
| 517 | 519 | .pointer => |ptrInfo2| { |
| 518 | | if (ptrInfo2.size != .slice) @compileError("Unsupported field type: " ++ @typeName(field.type)); |
| 519 | | if (ptrInfo2.child == u8) { |
| 520 | if (ptrInfo2.size == .slice and ptrInfo2.child == u8) { |
| 520 | 521 | // String. |
| 521 | 522 | } else { |
| 522 | 523 | @compileError("Unsupported field type: " ++ @typeName(field.type)); |
| ... | ... | @@ -579,7 +580,6 @@ fn ArrayListsForFields(comptime fields: []const StructField) type { |
| 579 | 580 | /// 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 | 581 | /// This matches the default behavior of `parse`, not `parseIter` or `parseSlice`. |
| 581 | 582 | 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 | 583 | var buf: [0x1000]u8 = undefined; |
| 584 | 584 | const prog: ?[]const u8 = options.prog orelse blk: { |
| 585 | 585 | var fba: std.heap.FixedBufferAllocator = .init(&buf); |
| ... | ... | @@ -587,7 +587,7 @@ pub fn @"error"(comptime Args: type, comptime msg: []const u8, msg_args: anytype |
| 587 | 587 | const argv0 = iter.next(); |
| 588 | 588 | break :blk if (argv0) |arg| std.fs.path.basename(arg) else null; |
| 589 | 589 | }; |
| 590 | | return usageError(named_fields, positional_fields, options.writer, msg, msg_args, prog orelse "<prog>", options.exit orelse true); |
| 590 | return usageError(Args, options.writer, msg, msg_args, prog orelse "<prog>", options.exit orelse true); |
| 591 | 591 | } |
| 592 | 592 | |
| 593 | 593 | test @"error" { |
| ... | ... | @@ -635,7 +635,8 @@ fn enumValuesExpr(comptime Enum: type) []const u8 { |
| 635 | 635 | return values_str; |
| 636 | 636 | } |
| 637 | 637 | |
| 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} { |
| 638 | fn usageError(comptime Args: type, writer: ?*Writer, comptime msg: []const u8, args: anytype, prog: []const u8, exit_on_error: bool) error{Usage} { |
| 639 | const named_fields, const positional_fields = comptime checkArgsType(Args); |
| 639 | 640 | const whole_msg = |
| 640 | 641 | "error: " ++ msg ++ "\n" ++ // |
| 641 | 642 | "usage: {s} " ++ comptime usageLineFmt(named_fields, positional_fields) ++ "\n" ++ |
| ... | ... | @@ -702,17 +703,67 @@ fn usageLineFmt(comptime named_fields: []const StructField, comptime positional_ |
| 702 | 703 | } |
| 703 | 704 | return escapeFmt(usage_str); |
| 704 | 705 | } |
| 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 = &.{}; |
| 706 | fn printGeneratedHelp(comptime Args: type, writer: ?*Writer, prog: []const u8) void { |
| 707 | const named_fields, const positional_fields = comptime checkArgsType(Args); |
| 707 | 708 | |
| 708 | | comptime var arguments_str: []const u8 = ""; // TODO: delete |
| 709 | comptime var arguments_table: []const []const []const u8 = &.{}; |
| 709 | 710 | |
| 710 | 711 | if (positional_fields.len > 0) { |
| 711 | | arguments_table = arguments_table ++ .{&[_][]const u8{"positional arguments:"}}; |
| 712 | arguments_table = arguments_table ++ .{ &[_][]const u8{""}, &[_][]const u8{"positional arguments:"} }; |
| 713 | } |
| 714 | inline for (positional_fields) |field| { |
| 715 | switch (@typeInfo(field.type)) { |
| 716 | .int, .float => { |
| 717 | arguments_table = arguments_table ++ .{&[_][]const u8{ |
| 718 | " " ++ field.name, |
| 719 | @typeName(field.type) ++ " " ++ |
| 720 | if (field.defaultValue()) |default| |
| 721 | "default: " ++ std.fmt.comptimePrint("{}", .{default}) |
| 722 | else |
| 723 | "required", |
| 724 | }}; |
| 725 | }, |
| 726 | .@"enum" => { |
| 727 | arguments_table = arguments_table ++ .{&[_][]const u8{ |
| 728 | " " ++ field.name, |
| 729 | comptime enumValuesExpr(field.type) ++ ". " ++ |
| 730 | if (field.defaultValue()) |default| |
| 731 | "default: " ++ @tagName(default) |
| 732 | else |
| 733 | "required", |
| 734 | }}; |
| 735 | }, |
| 736 | .pointer => |ptrInfo| { |
| 737 | if (ptrInfo.size == .slice and ptrInfo.child == u8) { |
| 738 | // String |
| 739 | arguments_table = arguments_table ++ .{&[_][]const u8{ |
| 740 | " " ++ field.name, |
| 741 | "string. " ++ |
| 742 | if (field.defaultValue()) |default| |
| 743 | "default: " ++ quoteIfEmpty(default) |
| 744 | else |
| 745 | "required", |
| 746 | }}; |
| 747 | } else { |
| 748 | // Array |
| 749 | const type_name = switch (@typeInfo(ptrInfo.child)) { |
| 750 | .bool => comptime unreachable, |
| 751 | .int, .float => @typeName(ptrInfo.child), |
| 752 | .@"enum" => comptime unreachable, |
| 753 | .pointer => "string", // The array-of-pointer that doesn't cause compile errors elsewhere. |
| 754 | else => comptime unreachable, |
| 755 | }; |
| 756 | arguments_table = arguments_table ++ .{&[_][]const u8{ |
| 757 | " " ++ field.name, |
| 758 | type_name ++ ". can be specified multiple times", |
| 759 | }}; |
| 760 | } |
| 761 | }, |
| 762 | else => comptime unreachable, |
| 763 | } |
| 712 | 764 | } |
| 713 | | //inline for (positional_fields) |field| {} |
| 714 | 765 | |
| 715 | | arguments_table = arguments_table ++ .{&[_][]const u8{"named arguments:"}}; // The --help option is always there. |
| 766 | arguments_table = arguments_table ++ .{ &[_][]const u8{""}, &[_][]const u8{"named arguments:"} }; |
| 716 | 767 | inline for (named_fields) |field| { |
| 717 | 768 | switch (@typeInfo(field.type)) { |
| 718 | 769 | .bool => { |
| ... | ... | @@ -737,11 +788,12 @@ fn printGeneratedHelp(comptime named_fields: []const StructField, comptime posit |
| 737 | 788 | }, |
| 738 | 789 | .@"enum" => { |
| 739 | 790 | 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", |
| 791 | " --" ++ field.name ++ "=enum", |
| 792 | comptime enumValuesExpr(field.type) ++ " " ++ |
| 793 | if (field.defaultValue()) |default| |
| 794 | "default: " ++ @tagName(default) |
| 795 | else |
| 796 | "required", |
| 745 | 797 | }}; |
| 746 | 798 | }, |
| 747 | 799 | .pointer => |ptrInfo| { |
| ... | ... | @@ -763,12 +815,9 @@ fn printGeneratedHelp(comptime named_fields: []const StructField, comptime posit |
| 763 | 815 | .pointer => "string", // The array-of-pointer that doesn't cause compile errors elsewhere. |
| 764 | 816 | else => comptime unreachable, |
| 765 | 817 | }; |
| 766 | | arguments_str = arguments_str ++ "\n " ++ // |
| 767 | | "--" ++ field.name ++ " " ++ type_name ++ " " ++ // |
| 768 | | "[--" ++ field.name ++ " " ++ type_name ++ " ...]"; |
| 769 | 818 | arguments_table = arguments_table ++ .{&[_][]const u8{ |
| 770 | | " --" ++ field.name ++ "=" ++ type_name ++ " " ++ // |
| 771 | | "[--" ++ field.name ++ "=" ++ type_name ++ " ...]", |
| 819 | " --" ++ field.name ++ "=" ++ type_name, |
| 820 | "can be specified multiple times", |
| 772 | 821 | }}; |
| 773 | 822 | } |
| 774 | 823 | }, |