| ... | @@ -40,15 +40,37 @@ pub const Error = error{ | ... | @@ -40,15 +40,37 @@ pub const Error = error{ |
| 40 | /// `Args` is a struct that you define looking like this: | 40 | /// `Args` is a struct that you define looking like this: |
| 41 | /// ``` | 41 | /// ``` |
| 42 | /// const Args = struct { | 42 | /// const Args = struct { |
| | 43 | /// pub const description = "this program does a thing"; |
| 43 | /// named: struct { | 44 | /// named: struct { |
| 44 | /// // ... | 45 | /// verbose: bool = false, |
| | 46 | /// output: [:0]const u8, |
| | 47 | /// pub const output_help = "path to output file"; |
| 45 | /// }, | 48 | /// }, |
| 46 | /// positional: struct { | 49 | /// positional: struct { |
| 47 | /// // ... | 50 | /// input: []const u8, |
| | 51 | /// args: []const []const u8 = &.{}, |
| 48 | /// }, | 52 | /// }, |
| 49 | /// }; | 53 | /// }; |
| 50 | /// ``` | 54 | /// ``` |
| 51 | /// Either or both of `named` and `positional` may be omitted, which is effectively equivalent to them having no fields. | 55 | /// Which results in this generated `--help` output: |
| | 56 | /// ``` |
| | 57 | /// usage: <prog> [options] --output=string input [args...] |
| | 58 | /// |
| | 59 | /// this program does a thing |
| | 60 | /// |
| | 61 | /// positional arguments: |
| | 62 | /// input string. required |
| | 63 | /// args string. can be specified multiple times |
| | 64 | /// |
| | 65 | /// named arguments: |
| | 66 | /// --verbose default: --no-verbose |
| | 67 | /// --output=string required. path to output file |
| | 68 | /// --help print this help and exit |
| | 69 | /// ``` |
| | 70 | /// Either or both of `named` and `positional` may be omitted, which is effectively equivalent to declaring them as `struct {}`. |
| | 71 | /// If `description` is declared, it is concatenated into the help output. |
| | 72 | /// If any `pub const <name>_help` accompanies a field `<name>` in either `named` or `positional`, |
| | 73 | /// it is included in that argument's help text. |
| 52 | /// | 74 | /// |
| 53 | /// The sequence of arg strings from the `ArgIterator` is parsed to determine named and positional arguments. | 75 | /// The sequence of arg strings from the `ArgIterator` is parsed to determine named and positional arguments. |
| 54 | /// | 76 | /// |
| ... | @@ -160,7 +182,7 @@ test parse { | ... | @@ -160,7 +182,7 @@ test parse { |
| 160 | /// First positional (non-named) argument: | 182 | /// First positional (non-named) argument: |
| 161 | input: [:0]const u8 = "", | 183 | input: [:0]const u8 = "", |
| 162 | /// Second positional argument is declared as optional: | 184 | /// Second positional argument is declared as optional: |
| 163 | repititions: u32 = 1, | 185 | repetitions: u32 = 1, |
| 164 | /// Receives the rest of the positional arguments. | 186 | /// Receives the rest of the positional arguments. |
| 165 | @"the-rest": []const [:0]const u8 = &.{}, | 187 | @"the-rest": []const [:0]const u8 = &.{}, |
| 166 | }, | 188 | }, |
| ... | @@ -717,20 +739,20 @@ fn printGeneratedHelp(comptime Args: type, writer: ?*Writer, prog: []const u8) v | ... | @@ -717,20 +739,20 @@ fn printGeneratedHelp(comptime Args: type, writer: ?*Writer, prog: []const u8) v |
| 717 | arguments_table = arguments_table ++ .{&[_][]const u8{ | 739 | arguments_table = arguments_table ++ .{&[_][]const u8{ |
| 718 | " " ++ field.name, | 740 | " " ++ field.name, |
| 719 | @typeName(field.type) ++ " " ++ | 741 | @typeName(field.type) ++ " " ++ |
| 720 | if (field.defaultValue()) |default| | 742 | (if (field.defaultValue()) |default| |
| 721 | "default: " ++ std.fmt.comptimePrint("{}", .{default}) | 743 | "default: " ++ std.fmt.comptimePrint("{}", .{default}) |
| 722 | else | 744 | else |
| 723 | "required", | 745 | "required") ++ argHelp(Args, "positional", field.name), |
| 724 | }}; | 746 | }}; |
| 725 | }, | 747 | }, |
| 726 | .@"enum" => { | 748 | .@"enum" => { |
| 727 | arguments_table = arguments_table ++ .{&[_][]const u8{ | 749 | arguments_table = arguments_table ++ .{&[_][]const u8{ |
| 728 | " " ++ field.name, | 750 | " " ++ field.name, |
| 729 | comptime enumValuesExpr(field.type) ++ ". " ++ | 751 | comptime enumValuesExpr(field.type) ++ ". " ++ |
| 730 | if (field.defaultValue()) |default| | 752 | (if (field.defaultValue()) |default| |
| 731 | "default: " ++ @tagName(default) | 753 | "default: " ++ @tagName(default) |
| 732 | else | 754 | else |
| 733 | "required", | 755 | "required") ++ argHelp(Args, "positional", field.name), |
| 734 | }}; | 756 | }}; |
| 735 | }, | 757 | }, |
| 736 | .pointer => |ptrInfo| { | 758 | .pointer => |ptrInfo| { |
| ... | @@ -739,10 +761,10 @@ fn printGeneratedHelp(comptime Args: type, writer: ?*Writer, prog: []const u8) v | ... | @@ -739,10 +761,10 @@ fn printGeneratedHelp(comptime Args: type, writer: ?*Writer, prog: []const u8) v |
| 739 | arguments_table = arguments_table ++ .{&[_][]const u8{ | 761 | arguments_table = arguments_table ++ .{&[_][]const u8{ |
| 740 | " " ++ field.name, | 762 | " " ++ field.name, |
| 741 | "string. " ++ | 763 | "string. " ++ |
| 742 | if (field.defaultValue()) |default| | 764 | (if (field.defaultValue()) |default| |
| 743 | "default: " ++ quoteIfEmpty(default) | 765 | "default: " ++ quoteIfEmpty(default) |
| 744 | else | 766 | else |
| 745 | "required", | 767 | "required") ++ argHelp(Args, "positional", field.name), |
| 746 | }}; | 768 | }}; |
| 747 | } else { | 769 | } else { |
| 748 | // Array | 770 | // Array |
| ... | @@ -755,7 +777,7 @@ fn printGeneratedHelp(comptime Args: type, writer: ?*Writer, prog: []const u8) v | ... | @@ -755,7 +777,7 @@ fn printGeneratedHelp(comptime Args: type, writer: ?*Writer, prog: []const u8) v |
| 755 | }; | 777 | }; |
| 756 | arguments_table = arguments_table ++ .{&[_][]const u8{ | 778 | arguments_table = arguments_table ++ .{&[_][]const u8{ |
| 757 | " " ++ field.name, | 779 | " " ++ field.name, |
| 758 | type_name ++ ". can be specified multiple times", | 780 | type_name ++ ". can be specified multiple times" ++ argHelp(Args, "positional", field.name), |
| 759 | }}; | 781 | }}; |
| 760 | } | 782 | } |
| 761 | }, | 783 | }, |
| ... | @@ -769,31 +791,31 @@ fn printGeneratedHelp(comptime Args: type, writer: ?*Writer, prog: []const u8) v | ... | @@ -769,31 +791,31 @@ fn printGeneratedHelp(comptime Args: type, writer: ?*Writer, prog: []const u8) v |
| 769 | .bool => { | 791 | .bool => { |
| 770 | if (field.defaultValue()) |default| { | 792 | if (field.defaultValue()) |default| { |
| 771 | if (default) { | 793 | if (default) { |
| 772 | arguments_table = arguments_table ++ .{&[_][]const u8{ " --no-" ++ field.name, "default: --" ++ field.name }}; | 794 | arguments_table = arguments_table ++ .{&[_][]const u8{ " --no-" ++ field.name, "default: --" ++ field.name ++ argHelp(Args, "named", field.name) }}; |
| 773 | } else { | 795 | } else { |
| 774 | arguments_table = arguments_table ++ .{&[_][]const u8{ " --" ++ field.name, "default: --no-" ++ field.name }}; | 796 | arguments_table = arguments_table ++ .{&[_][]const u8{ " --" ++ field.name, "default: --no-" ++ field.name ++ argHelp(Args, "named", field.name) }}; |
| 775 | } | 797 | } |
| 776 | } else { | 798 | } else { |
| 777 | arguments_table = arguments_table ++ .{&[_][]const u8{ " --[no-]" ++ field.name, "required" }}; | 799 | arguments_table = arguments_table ++ .{&[_][]const u8{ " --[no-]" ++ field.name, "required" ++ argHelp(Args, "named", field.name) }}; |
| 778 | } | 800 | } |
| 779 | }, | 801 | }, |
| 780 | .int, .float => { | 802 | .int, .float => { |
| 781 | arguments_table = arguments_table ++ .{&[_][]const u8{ | 803 | arguments_table = arguments_table ++ .{&[_][]const u8{ |
| 782 | " --" ++ field.name ++ "=" ++ @typeName(field.type), | 804 | " --" ++ field.name ++ "=" ++ @typeName(field.type), |
| 783 | if (field.defaultValue()) |default| | 805 | (if (field.defaultValue()) |default| |
| 784 | "default: " ++ std.fmt.comptimePrint("{}", .{default}) | 806 | "default: " ++ std.fmt.comptimePrint("{}", .{default}) |
| 785 | else | 807 | else |
| 786 | "required", | 808 | "required") ++ argHelp(Args, "named", field.name), |
| 787 | }}; | 809 | }}; |
| 788 | }, | 810 | }, |
| 789 | .@"enum" => { | 811 | .@"enum" => { |
| 790 | arguments_table = arguments_table ++ .{&[_][]const u8{ | 812 | arguments_table = arguments_table ++ .{&[_][]const u8{ |
| 791 | " --" ++ field.name ++ "=enum", | 813 | " --" ++ field.name ++ "=enum", |
| 792 | comptime enumValuesExpr(field.type) ++ " " ++ | 814 | comptime enumValuesExpr(field.type) ++ ". " ++ |
| 793 | if (field.defaultValue()) |default| | 815 | (if (field.defaultValue()) |default| |
| 794 | "default: " ++ @tagName(default) | 816 | "default: " ++ @tagName(default) |
| 795 | else | 817 | else |
| 796 | "required", | 818 | "required") ++ argHelp(Args, "named", field.name), |
| 797 | }}; | 819 | }}; |
| 798 | }, | 820 | }, |
| 799 | .pointer => |ptrInfo| { | 821 | .pointer => |ptrInfo| { |
| ... | @@ -801,10 +823,10 @@ fn printGeneratedHelp(comptime Args: type, writer: ?*Writer, prog: []const u8) v | ... | @@ -801,10 +823,10 @@ fn printGeneratedHelp(comptime Args: type, writer: ?*Writer, prog: []const u8) v |
| 801 | // String | 823 | // String |
| 802 | arguments_table = arguments_table ++ .{&[_][]const u8{ | 824 | arguments_table = arguments_table ++ .{&[_][]const u8{ |
| 803 | " --" ++ field.name ++ "=string", | 825 | " --" ++ field.name ++ "=string", |
| 804 | if (field.defaultValue()) |default| | 826 | (if (field.defaultValue()) |default| |
| 805 | "default: " ++ quoteIfEmpty(default) | 827 | "default: " ++ quoteIfEmpty(default) |
| 806 | else | 828 | else |
| 807 | "required", | 829 | "required") ++ argHelp(Args, "named", field.name), |
| 808 | }}; | 830 | }}; |
| 809 | } else { | 831 | } else { |
| 810 | // Array | 832 | // Array |
| ... | @@ -817,7 +839,7 @@ fn printGeneratedHelp(comptime Args: type, writer: ?*Writer, prog: []const u8) v | ... | @@ -817,7 +839,7 @@ fn printGeneratedHelp(comptime Args: type, writer: ?*Writer, prog: []const u8) v |
| 817 | }; | 839 | }; |
| 818 | arguments_table = arguments_table ++ .{&[_][]const u8{ | 840 | arguments_table = arguments_table ++ .{&[_][]const u8{ |
| 819 | " --" ++ field.name ++ "=" ++ type_name, | 841 | " --" ++ field.name ++ "=" ++ type_name, |
| 820 | "can be specified multiple times", | 842 | "can be specified multiple times" ++ argHelp(Args, "named", field.name), |
| 821 | }}; | 843 | }}; |
| 822 | } | 844 | } |
| 823 | }, | 845 | }, |
| ... | @@ -833,6 +855,9 @@ fn printGeneratedHelp(comptime Args: type, writer: ?*Writer, prog: []const u8) v | ... | @@ -833,6 +855,9 @@ fn printGeneratedHelp(comptime Args: type, writer: ?*Writer, prog: []const u8) v |
| 833 | } | 855 | } |
| 834 | | 856 | |
| 835 | comptime var help_str: []const u8 = ""; | 857 | comptime var help_str: []const u8 = ""; |
| | 858 | if (@hasDecl(Args, "description")) { |
| | 859 | help_str = "\n\n" ++ Args.description; |
| | 860 | } |
| 836 | inline for (arguments_table) |row| { | 861 | inline for (arguments_table) |row| { |
| 837 | help_str = help_str ++ "\n"; | 862 | help_str = help_str ++ "\n"; |
| 838 | inline for (row, 0..) |cell, c| { | 863 | inline for (row, 0..) |cell, c| { |
| ... | @@ -856,6 +881,13 @@ fn printGeneratedHelp(comptime Args: type, writer: ?*Writer, prog: []const u8) v | ... | @@ -856,6 +881,13 @@ fn printGeneratedHelp(comptime Args: type, writer: ?*Writer, prog: []const u8) v |
| 856 | } | 881 | } |
| 857 | } | 882 | } |
| 858 | | 883 | |
| | 884 | inline fn argHelp(comptime Args: type, comptime named_or_positional: []const u8, comptime field_name: []const u8) []const u8 { |
| | 885 | const N = @FieldType(Args, named_or_positional); |
| | 886 | comptime assert(@hasField(N, field_name)); |
| | 887 | if (!@hasDecl(N, field_name ++ "_help")) return ""; |
| | 888 | return ". " ++ @field(N, field_name ++ "_help"); |
| | 889 | } |
| | 890 | |
| 859 | inline fn quoteIfEmpty(comptime s: []const u8) []const u8 { | 891 | inline fn quoteIfEmpty(comptime s: []const u8) []const u8 { |
| 860 | if (s.len == 0) return "''"; | 892 | if (s.len == 0) return "''"; |
| 861 | return s; | 893 | return s; |