summaryrefslogtreecommitdiff
path: root/clap.zig
diff options
context:
space:
mode:
authorGravatar Jimmi Holst Christensen2024-10-24 15:36:55 +0200
committerGravatar Komari Spaghetti2024-10-24 16:35:47 +0200
commit4e293092fc56ebf46d9c17e4e203ca54c63a0b09 (patch)
treefc400c62cb7f96f0cc475162c7084dadb1f3b029 /clap.zig
parentrefactor: positonals type now depend on the number of values taken (diff)
downloadzig-clap-4e293092fc56ebf46d9c17e4e203ca54c63a0b09.tar.gz
zig-clap-4e293092fc56ebf46d9c17e4e203ca54c63a0b09.tar.xz
zig-clap-4e293092fc56ebf46d9c17e4e203ca54c63a0b09.zip
feat: Support multiple positionals of different types
Diffstat (limited to '')
-rw-r--r--clap.zig250
1 files changed, 182 insertions, 68 deletions
diff --git a/clap.zig b/clap.zig
index 8d6594c..ed042ee 100644
--- a/clap.zig
+++ b/clap.zig
@@ -709,6 +709,30 @@ pub fn Result(
709/// `T` can be any type and `Error` can be any error. You can pass `clap.parsers.default` if you 709/// `T` can be any type and `Error` can be any error. You can pass `clap.parsers.default` if you
710/// just wonna get something up and running. 710/// just wonna get something up and running.
711/// 711///
712/// The result will also contain a `positionals` field which contains all positional arguments
713/// passed. This field will be a tuple with one field for each positional parameter.
714///
715/// Example:
716/// -h, --help
717/// -s, --str <str>
718/// -i, --int <usize>
719/// -m, --many <usize>...
720/// <u8>
721/// <str>...
722///
723/// struct {
724/// args: struct {
725/// help: u8,
726/// str: ?[]const u8,
727/// int: ?usize,
728/// many: []const usize,
729/// },
730/// positionals: struct {
731/// ?u8,
732/// []const []const u8,
733/// },
734/// }
735///
712/// Caller owns the result and should free it by calling `result.deinit()` 736/// Caller owns the result and should free it by calling `result.deinit()`
713pub fn parseEx( 737pub fn parseEx(
714 comptime Id: type, 738 comptime Id: type,
@@ -720,7 +744,7 @@ pub fn parseEx(
720 const allocator = opt.allocator; 744 const allocator = opt.allocator;
721 745
722 var positional_count: usize = 0; 746 var positional_count: usize = 0;
723 var positionals = initPositionals(Positionals(Id, params, value_parsers, .list)); 747 var positionals = Positionals(Id, params, value_parsers, .list){};
724 errdefer deinitPositionals(&positionals, allocator); 748 errdefer deinitPositionals(&positionals, allocator);
725 749
726 var arguments = Arguments(Id, params, value_parsers, .list){}; 750 var arguments = Arguments(Id, params, value_parsers, .list){};
@@ -733,7 +757,13 @@ pub fn parseEx(
733 .assignment_separators = opt.assignment_separators, 757 .assignment_separators = opt.assignment_separators,
734 }; 758 };
735 arg_loop: while (try stream.next()) |arg| { 759 arg_loop: while (try stream.next()) |arg| {
760 // This loop checks if we got a short or long parameter. If so, the value is parsed and
761 // stored in `arguments`
736 inline for (params) |*param| continue_params_loop: { 762 inline for (params) |*param| continue_params_loop: {
763 const longest = comptime param.names.longest();
764 if (longest.kind == .positional)
765 continue;
766
737 if (param != arg.param) 767 if (param != arg.param)
738 // This is a trick to emulate a runtime `continue` in an `inline for`. 768 // This is a trick to emulate a runtime `continue` in an `inline for`.
739 break :continue_params_loop; 769 break :continue_params_loop;
@@ -743,50 +773,75 @@ pub fn parseEx(
743 .one, .many => @field(value_parsers, param.id.value()), 773 .one, .many => @field(value_parsers, param.id.value()),
744 }; 774 };
745 775
746 const longest = comptime param.names.longest();
747 const name = longest.name[0..longest.name.len].*; 776 const name = longest.name[0..longest.name.len].*;
748 switch (longest.kind) { 777 switch (param.takes_value) {
749 .short, .long => switch (param.takes_value) { 778 .none => @field(arguments, &name) +|= 1,
750 .none => @field(arguments, &name) +|= 1, 779 .one => @field(arguments, &name) = try parser(arg.value.?),
751 .one => @field(arguments, &name) = try parser(arg.value.?), 780 .many => {
752 .many => { 781 const value = try parser(arg.value.?);
753 const value = try parser(arg.value.?); 782 try @field(arguments, &name).append(allocator, value);
754 try @field(arguments, &name).append(allocator, value);
755 },
756 },
757 .positional => {
758 switch (@typeInfo(@TypeOf(positionals))) {
759 .optional => positionals = try parser(arg.value.?),
760 else => try positionals.append(allocator, try parser(arg.value.?)),
761 }
762 if (opt.terminating_positional <= positional_count)
763 break :arg_loop;
764
765 positional_count += 1;
766 }, 783 },
767 } 784 }
768 } 785 }
786
787 // This loop checks if we got a positional parameter. If so, the value is parsed and
788 // stored in `positionals`
789 comptime var positionals_index = 0;
790 inline for (params) |*param| continue_params_loop: {
791 const longest = comptime param.names.longest();
792 if (longest.kind != .positional)
793 continue;
794
795 const i = positionals_index;
796 positionals_index += 1;
797
798 if (stream.positional != arg.param)
799 // This is a trick to emulate a runtime `continue` in an `inline for`.
800 break :continue_params_loop;
801
802 const parser = comptime switch (param.takes_value) {
803 .none => null,
804 .one, .many => @field(value_parsers, param.id.value()),
805 };
806
807 // We keep track of how many positionals we have received. This is used to pick which
808 // `positional` field to store to. Once `positional_count` exceeds the number of
809 // positional parameters, the rest are stored in the last `positional` field.
810 const pos = &positionals[i];
811 const last = positionals.len == i + 1;
812 if ((last and positional_count >= i) or positional_count == i)
813 switch (@typeInfo(@TypeOf(pos.*))) {
814 .optional => pos.* = try parser(arg.value.?),
815 else => try pos.append(allocator, try parser(arg.value.?)),
816 };
817
818 if (opt.terminating_positional <= positional_count)
819 break :arg_loop;
820 positional_count += 1;
821 }
769 } 822 }
770 823
771 // We are done parsing, but our arguments are stored in lists, and not slices. Map the list 824 // We are done parsing, but our arguments are stored in lists, and not slices. Map the list
772 // fields to slices and return that. 825 // fields to slices and return that.
773 var result_args = Arguments(Id, params, value_parsers, .slice){}; 826 var result_args = Arguments(Id, params, value_parsers, .slice){};
774 inline for (std.meta.fields(@TypeOf(arguments))) |field| { 827 inline for (std.meta.fields(@TypeOf(arguments))) |field| {
775 if (@typeInfo(field.type) == .@"struct" and 828 switch (@typeInfo(field.type)) {
776 @hasDecl(field.type, "toOwnedSlice")) 829 .@"struct" => {
777 { 830 const slice = try @field(arguments, field.name).toOwnedSlice(allocator);
778 const slice = try @field(arguments, field.name).toOwnedSlice(allocator); 831 @field(result_args, field.name) = slice;
779 @field(result_args, field.name) = slice; 832 },
780 } else { 833 else => @field(result_args, field.name) = @field(arguments, field.name),
781 @field(result_args, field.name) = @field(arguments, field.name);
782 } 834 }
783 } 835 }
784 836
785 // We are done parsing, but our positionals are stored in lists, and not slices. 837 // We are done parsing, but our positionals are stored in lists, and not slices.
786 const result_positionals = switch (@typeInfo(@TypeOf(positionals))) { 838 var result_positionals = Positionals(Id, params, value_parsers, .slice){};
787 .optional => positionals, 839 inline for (&result_positionals, &positionals) |*res_pos, *pos| {
788 else => try positionals.toOwnedSlice(allocator), 840 switch (@typeInfo(@TypeOf(pos.*))) {
789 }; 841 .@"struct" => res_pos.* = try pos.toOwnedSlice(allocator),
842 else => res_pos.* = pos.*,
843 }
844 }
790 845
791 return ResultEx(Id, params, value_parsers){ 846 return ResultEx(Id, params, value_parsers){
792 .args = result_args, 847 .args = result_args,
@@ -813,55 +868,72 @@ pub fn ResultEx(
813 }; 868 };
814} 869}
815 870
871/// Turn a list of parameters into a tuple with one field for each positional parameter.
872/// The type of each parameter field is determined by `ParamType`.
816fn Positionals( 873fn Positionals(
817 comptime Id: type, 874 comptime Id: type,
818 comptime params: []const Param(Id), 875 comptime params: []const Param(Id),
819 comptime value_parsers: anytype, 876 comptime value_parsers: anytype,
820 comptime multi_arg_kind: MultiArgKind, 877 comptime multi_arg_kind: MultiArgKind,
821) type { 878) type {
822 const pos = findPositional(Id, params) orelse return ?void; 879 var fields_len: usize = 0;
823 const T = ParamType(Id, pos, value_parsers); 880 for (params) |param| {
824 if (pos.takes_value == .many) 881 const longest = param.names.longest();
825 return switch (multi_arg_kind) { 882 if (longest.kind != .positional)
826 .slice => []const T, 883 continue;
827 .list => std.ArrayListUnmanaged(T), 884 fields_len += 1;
828 }; 885 }
829 886
830 return ?T; 887 var fields: [fields_len]std.builtin.Type.StructField = undefined;
831} 888 var i: usize = 0;
889 for (params) |param| {
890 const longest = param.names.longest();
891 if (longest.kind != .positional)
892 continue;
832 893
833fn initPositionals(comptime T: type) T { 894 const T = ParamType(Id, param, value_parsers);
834 return switch (@typeInfo(T)) { 895 const default_value = switch (param.takes_value) {
835 .optional => null, 896 .none => continue,
836 else => .{}, 897 .one => @as(?T, null),
837 }; 898 .many => switch (multi_arg_kind) {
838} 899 .slice => @as([]const T, &[_]T{}),
900 .list => std.ArrayListUnmanaged(T){},
901 },
902 };
839 903
840fn deinitPositionals(positionals: anytype, allocator: std.mem.Allocator) void { 904 fields[i] = .{
841 switch (@typeInfo(@TypeOf(positionals.*))) { 905 .name = std.fmt.comptimePrint("{}", .{i}),
842 .optional => {}, 906 .type = @TypeOf(default_value),
843 .@"struct" => positionals.deinit(allocator), 907 .default_value = @ptrCast(&default_value),
844 else => allocator.free(positionals.*), 908 .is_comptime = false,
909 .alignment = @alignOf(@TypeOf(default_value)),
910 };
911 i += 1;
845 } 912 }
913
914 return @Type(.{ .@"struct" = .{
915 .layout = .auto,
916 .fields = &fields,
917 .decls = &.{},
918 .is_tuple = true,
919 } });
846} 920}
847 921
848fn findPositional(comptime Id: type, params: []const Param(Id)) ?Param(Id) { 922/// Deinitializes a tuple of type `Positionals`. Since the `Positionals` type is generated, and we
849 for (params) |param| { 923/// cannot add the deinit declaration to it, we declare it here instead.
850 const longest = param.names.longest(); 924fn deinitPositionals(positionals: anytype, allocator: std.mem.Allocator) void {
851 if (longest.kind == .positional) 925 inline for (positionals) |*pos| {
852 return param; 926 switch (@typeInfo(@TypeOf(pos.*))) {
927 .optional => {},
928 .@"struct" => pos.deinit(allocator),
929 else => allocator.free(pos.*),
930 }
853 } 931 }
854
855 return null;
856} 932}
857 933
858/// Given a parameter figure out which type that parameter is parsed into when using the correct 934/// Given a parameter figure out which type that parameter is parsed into when using the correct
859/// parser from `value_parsers`. 935/// parser from `value_parsers`.
860fn ParamType( 936fn ParamType(comptime Id: type, comptime param: Param(Id), comptime value_parsers: anytype) type {
861 comptime Id: type,
862 comptime param: Param(Id),
863 comptime value_parsers: anytype,
864) type {
865 const parser = switch (param.takes_value) { 937 const parser = switch (param.takes_value) {
866 .none => parsers.string, 938 .none => parsers.string,
867 .one, .many => @field(value_parsers, param.id.value()), 939 .one, .many => @field(value_parsers, param.id.value()),
@@ -983,7 +1055,7 @@ test "single positional" {
983 }); 1055 });
984 defer res.deinit(); 1056 defer res.deinit();
985 1057
986 try std.testing.expect(res.positionals == null); 1058 try std.testing.expect(res.positionals[0] == null);
987 } 1059 }
988 1060
989 { 1061 {
@@ -993,7 +1065,7 @@ test "single positional" {
993 }); 1065 });
994 defer res.deinit(); 1066 defer res.deinit();
995 1067
996 try std.testing.expectEqualStrings("a", res.positionals.?); 1068 try std.testing.expectEqualStrings("a", res.positionals[0].?);
997 } 1069 }
998 1070
999 { 1071 {
@@ -1003,7 +1075,48 @@ test "single positional" {
1003 }); 1075 });
1004 defer res.deinit(); 1076 defer res.deinit();
1005 1077
1006 try std.testing.expectEqualStrings("b", res.positionals.?); 1078 try std.testing.expectEqualStrings("b", res.positionals[0].?);
1079 }
1080}
1081
1082test "multiple positionals" {
1083 const params = comptime parseParamsComptime(
1084 \\<u8>
1085 \\<str>
1086 \\
1087 );
1088
1089 // {
1090 // var iter = args.SliceIterator{ .args = &.{} };
1091 // var res = try parseEx(Help, &params, parsers.default, &iter, .{
1092 // .allocator = std.testing.allocator,
1093 // });
1094 // defer res.deinit();
1095
1096 // try std.testing.expect(res.positionals[0] == null);
1097 // try std.testing.expect(res.positionals[1] == null);
1098 // }
1099
1100 // {
1101 // var iter = args.SliceIterator{ .args = &.{"1"} };
1102 // var res = try parseEx(Help, &params, parsers.default, &iter, .{
1103 // .allocator = std.testing.allocator,
1104 // });
1105 // defer res.deinit();
1106
1107 // try std.testing.expectEqual(@as(u8, 1), res.positionals[0].?);
1108 // try std.testing.expect(res.positionals[1] == null);
1109 // }
1110
1111 {
1112 var iter = args.SliceIterator{ .args = &.{ "1", "b" } };
1113 var res = try parseEx(Help, &params, parsers.default, &iter, .{
1114 .allocator = std.testing.allocator,
1115 });
1116 defer res.deinit();
1117
1118 try std.testing.expectEqual(@as(u8, 1), res.positionals[0].?);
1119 try std.testing.expectEqualStrings("b", res.positionals[1].?);
1007 } 1120 }
1008} 1121}
1009 1122
@@ -1031,7 +1144,7 @@ test "everything" {
1031 try std.testing.expect(res.args.h == 1); 1144 try std.testing.expect(res.args.h == 1);
1032 try std.testing.expectEqualStrings("0", res.args.cc.?); 1145 try std.testing.expectEqualStrings("0", res.args.cc.?);
1033 try std.testing.expectEqual(@as(usize, 1), res.positionals.len); 1146 try std.testing.expectEqual(@as(usize, 1), res.positionals.len);
1034 try std.testing.expectEqualStrings("something", res.positionals[0]); 1147 try std.testing.expectEqualStrings("something", res.positionals[0][0]);
1035 try std.testing.expectEqualSlices(usize, &.{ 1, 2 }, res.args.dd); 1148 try std.testing.expectEqualSlices(usize, &.{ 1, 2 }, res.args.dd);
1036 try std.testing.expectEqual(@as(usize, 10), iter.index); 1149 try std.testing.expectEqual(@as(usize, 10), iter.index);
1037} 1150}
@@ -1061,7 +1174,8 @@ test "terminating positional" {
1061 try std.testing.expect(res.args.h == 0); 1174 try std.testing.expect(res.args.h == 0);
1062 try std.testing.expectEqualStrings("0", res.args.cc.?); 1175 try std.testing.expectEqualStrings("0", res.args.cc.?);
1063 try std.testing.expectEqual(@as(usize, 1), res.positionals.len); 1176 try std.testing.expectEqual(@as(usize, 1), res.positionals.len);
1064 try std.testing.expectEqualStrings("something", res.positionals[0]); 1177 try std.testing.expectEqual(@as(usize, 1), res.positionals[0].len);
1178 try std.testing.expectEqualStrings("something", res.positionals[0][0]);
1065 try std.testing.expectEqualSlices(usize, &.{}, res.args.dd); 1179 try std.testing.expectEqualSlices(usize, &.{}, res.args.dd);
1066 try std.testing.expectEqual(@as(usize, 5), iter.index); 1180 try std.testing.expectEqual(@as(usize, 5), iter.index);
1067} 1181}