summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md10
-rw-r--r--clap.zig250
-rw-r--r--example/README.md.template6
-rw-r--r--example/simple-ex.zig2
-rw-r--r--example/simple.zig2
5 files changed, 192 insertions, 78 deletions
diff --git a/README.md b/README.md
index c6e14e9..3281d91 100644
--- a/README.md
+++ b/README.md
@@ -90,7 +90,7 @@ pub fn main() !void {
90 std.debug.print("--number = {}\n", .{n}); 90 std.debug.print("--number = {}\n", .{n});
91 for (res.args.string) |s| 91 for (res.args.string) |s|
92 std.debug.print("--string = {s}\n", .{s}); 92 std.debug.print("--string = {s}\n", .{s});
93 for (res.positionals) |pos| 93 for (res.positionals[0]) |pos|
94 std.debug.print("{s}\n", .{pos}); 94 std.debug.print("{s}\n", .{pos});
95} 95}
96 96
@@ -101,10 +101,10 @@ const std = @import("std");
101 101
102The result will contain an `args` field and a `positionals` field. `args` will have one field 102The result will contain an `args` field and a `positionals` field. `args` will have one field
103for each none positional parameter of your program. The name of the field will be the longest 103for each none positional parameter of your program. The name of the field will be the longest
104name of the parameter. 104name of the parameter. `positionals` be a tuple with one field for each positional parameter.
105 105
106The fields in `args` are typed. The type is based on the name of the value the parameter takes. 106The fields in `args` and `psotionals` are typed. The type is based on the name of the value the
107Since `--number` takes a `usize` the field `res.args.number` has the type `usize`. 107parameter takes. Since `--number` takes a `usize` the field `res.args.number` has the type `usize`.
108 108
109Note that this is only the case because `clap.parsers.default` has a field called `usize` which 109Note that this is only the case because `clap.parsers.default` has a field called `usize` which
110contains a parser that returns `usize`. You can pass in something other than 110contains a parser that returns `usize`. You can pass in something other than
@@ -157,7 +157,7 @@ pub fn main() !void {
157 std.debug.print("--answer = {s}\n", .{@tagName(a)}); 157 std.debug.print("--answer = {s}\n", .{@tagName(a)});
158 for (res.args.string) |s| 158 for (res.args.string) |s|
159 std.debug.print("--string = {s}\n", .{s}); 159 std.debug.print("--string = {s}\n", .{s});
160 for (res.positionals) |pos| 160 for (res.positionals[0]) |pos|
161 std.debug.print("{s}\n", .{pos}); 161 std.debug.print("{s}\n", .{pos});
162} 162}
163 163
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}
diff --git a/example/README.md.template b/example/README.md.template
index dda0cc9..d234dcd 100644
--- a/example/README.md.template
+++ b/example/README.md.template
@@ -61,10 +61,10 @@ The simplest way to use this library is to just call the `clap.parse` function.
61 61
62The result will contain an `args` field and a `positionals` field. `args` will have one field 62The result will contain an `args` field and a `positionals` field. `args` will have one field
63for each none positional parameter of your program. The name of the field will be the longest 63for each none positional parameter of your program. The name of the field will be the longest
64name of the parameter. 64name of the parameter. `positionals` be a tuple with one field for each positional parameter.
65 65
66The fields in `args` are typed. The type is based on the name of the value the parameter takes. 66The fields in `args` and `psotionals` are typed. The type is based on the name of the value the
67Since `--number` takes a `usize` the field `res.args.number` has the type `usize`. 67parameter takes. Since `--number` takes a `usize` the field `res.args.number` has the type `usize`.
68 68
69Note that this is only the case because `clap.parsers.default` has a field called `usize` which 69Note that this is only the case because `clap.parsers.default` has a field called `usize` which
70contains a parser that returns `usize`. You can pass in something other than 70contains a parser that returns `usize`. You can pass in something other than
diff --git a/example/simple-ex.zig b/example/simple-ex.zig
index 4ca9791..154e486 100644
--- a/example/simple-ex.zig
+++ b/example/simple-ex.zig
@@ -44,7 +44,7 @@ pub fn main() !void {
44 std.debug.print("--answer = {s}\n", .{@tagName(a)}); 44 std.debug.print("--answer = {s}\n", .{@tagName(a)});
45 for (res.args.string) |s| 45 for (res.args.string) |s|
46 std.debug.print("--string = {s}\n", .{s}); 46 std.debug.print("--string = {s}\n", .{s});
47 for (res.positionals) |pos| 47 for (res.positionals[0]) |pos|
48 std.debug.print("{s}\n", .{pos}); 48 std.debug.print("{s}\n", .{pos});
49} 49}
50 50
diff --git a/example/simple.zig b/example/simple.zig
index 7f1bfc0..74157a6 100644
--- a/example/simple.zig
+++ b/example/simple.zig
@@ -32,7 +32,7 @@ pub fn main() !void {
32 std.debug.print("--number = {}\n", .{n}); 32 std.debug.print("--number = {}\n", .{n});
33 for (res.args.string) |s| 33 for (res.args.string) |s|
34 std.debug.print("--string = {s}\n", .{s}); 34 std.debug.print("--string = {s}\n", .{s});
35 for (res.positionals) |pos| 35 for (res.positionals[0]) |pos|
36 std.debug.print("{s}\n", .{pos}); 36 std.debug.print("{s}\n", .{pos});
37} 37}
38 38