summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/core.zig6
-rw-r--r--src/extended.zig303
-rw-r--r--tests/extended.zig494
3 files changed, 429 insertions, 374 deletions
diff --git a/src/core.zig b/src/core.zig
index 5e47714..f2e1fe0 100644
--- a/src/core.zig
+++ b/src/core.zig
@@ -266,8 +266,10 @@ pub fn Clap(comptime Id: type, comptime ArgError: type) type {
266 kind = ArgInfo.Kind.Short; 266 kind = ArgInfo.Kind.Short;
267 } 267 }
268 268
269 if (arg.len == 0) 269 // We allow long arguments to go without a name.
270 return error.ArgWithNoName; 270 // This allows the user to use "--" for something important
271 if (kind != ArgInfo.Kind.Long and arg.len == 0)
272 return error.InvalidArgument;
271 273
272 break :blk ArgInfo { .arg = arg, .kind = kind }; 274 break :blk ArgInfo { .arg = arg, .kind = kind };
273 }; 275 };
diff --git a/src/extended.zig b/src/extended.zig
index 31e6455..ffcce5b 100644
--- a/src/extended.zig
+++ b/src/extended.zig
@@ -10,206 +10,34 @@ const io = std.io;
10 10
11const assert = debug.assert; 11const assert = debug.assert;
12 12
13const Opaque = @OpaqueType();
14
15pub const Param = struct { 13pub const Param = struct {
16 field: []const u8, 14 field: []const u8,
17 short: ?u8, 15 names: core.Names,
18 long: ?[]const u8, 16 kind: Kind,
19 takes_value: ?Parser,
20 required: bool, 17 required: bool,
21 position: ?usize, 18 position: ?usize,
22 19
23 pub fn short(s: u8) Param { 20 pub const Kind = union(enum) {
24 return Param{ 21 Flag,
25 .field = []u8{s}, 22 Option: Parser,
26 .short = s, 23 SubCommand: Command,
27 .long = null, 24 };
28 .takes_value = null,
29 .required = false,
30 .position = null,
31 };
32 }
33
34 pub fn long(l: []const u8) Param {
35 return Param{
36 .field = l,
37 .short = null,
38 .long = l,
39 .takes_value = null,
40 .required = false,
41 .position = null,
42 };
43 }
44
45 pub fn value(f: []const u8) Param {
46 return Param{
47 .field = f,
48 .short = null,
49 .long = null,
50 .takes_value = null,
51 .required = false,
52 .position = null,
53 };
54 }
55
56 /// Initialize a ::Param.
57 /// If ::name.len == 0, then it's a value parameter: "value".
58 /// If ::name.len == 1, then it's a short parameter: "-s".
59 /// If ::name.len > 1, then it's a long parameter: "--long".
60 pub fn smart(name: []const u8) Param {
61 return Param{
62 .field = name,
63 .short = if (name.len == 1) name[0] else null,
64 .long = if (name.len > 1) name else null,
65 .takes_value = null,
66 .required = false,
67 .position = null,
68 };
69 }
70
71 pub fn with(param: &const Param, comptime field_name: []const u8, v: var) Param {
72 var res = param.*;
73 @field(res, field_name) = v;
74 return res;
75 }
76}; 25};
77 26
27const Opaque = @OpaqueType();
78pub const Command = struct { 28pub const Command = struct {
79 field: []const u8,
80 name: []const u8,
81 params: []const Param, 29 params: []const Param,
82 sub_commands: []const Command,
83 30
84 Result: type, 31 Result: type,
85 defaults: &const Opaque, 32 default: &const Opaque,
86 parent: ?&const Command,
87 33
88 pub fn init(name: []const u8, comptime Result: type, defaults: &const Result, params: []const Param, sub_commands: []const Command) Command { 34 pub fn init(comptime Result: type, default: &const Result, params: []const Param) Command {
89 return Command{ 35 return Command{
90 .field = name,
91 .name = name,
92 .params = params, 36 .params = params,
93 .sub_commands = sub_commands,
94 .Result = Result, 37 .Result = Result,
95 .defaults = @ptrCast(&const Opaque, defaults), 38 .default = @ptrCast(&const Opaque, default),
96 .parent = null,
97 }; 39 };
98 } 40 }
99
100 pub fn with(command: &const Command, comptime field_name: []const u8, v: var) Param {
101 var res = command.*;
102 @field(res, field_name) = v;
103 return res;
104 }
105
106 pub fn parse(comptime command: &const Command, allocator: &mem.Allocator, arg_iter: var) !command.Result {
107 const Parent = struct {};
108 var parent = Parent{};
109 return command.parseHelper(&parent, allocator, arg_iter);
110 }
111
112 fn parseHelper(comptime command: &const Command, parent: var, allocator: &mem.Allocator, arg_iter: var) !command.Result {
113 const Result = struct {
114 parent: @typeOf(parent),
115 result: command.Result,
116 };
117
118 var result = Result{
119 .parent = parent,
120 .result = @ptrCast(&const command.Result, command.defaults).*,
121 };
122
123 // In order for us to wrap the core api, we have to translate clap.Param into core.Param.
124 const core_params = comptime blk: {
125 var res: [command.params.len + command.sub_commands.len]core.Param(usize) = undefined;
126
127 for (command.params) |p, i| {
128 const id = i;
129 res[id] = core.Param(usize) {
130 .id = id,
131 .takes_value = p.takes_value != null,
132 .names = core.Names{
133 .bare = null,
134 .short = p.short,
135 .long = p.long,
136 },
137 };
138 }
139
140 for (command.sub_commands) |c, i| {
141 const id = i + command.params.len;
142 res[id] = core.Param(usize) {
143 .id = id,
144 .takes_value = false,
145 .names = core.Names.bare(c.name),
146 };
147 }
148
149 break :blk res;
150 };
151
152 var handled = comptime blk: {
153 var res: [command.params.len]bool = undefined;
154 for (command.params) |p, i| {
155 res[i] = !p.required;
156 }
157
158 break :blk res;
159 };
160
161 var pos: usize = 0;
162 var clap = core.Clap(usize, @typeOf(arg_iter.*).Error).init(core_params, arg_iter);
163
164 arg_loop:
165 while (try clap.next()) |arg| : (pos += 1) {
166 inline for(command.params) |param, i| {
167 comptime const field = "result." ++ param.field;
168
169 if (arg.param.id == i and (param.position ?? pos) == pos) {
170 if (param.takes_value) |parser| {
171 try parser.parse(getFieldPtr(&result, field), ??arg.value);
172 } else {
173 getFieldPtr(&result, field).* = true;
174 }
175 handled[i] = true;
176 continue :arg_loop;
177 }
178 }
179
180 inline for(command.sub_commands) |c, i| {
181 comptime const field = "result." ++ c.field;
182 comptime var sub_command = c;
183 sub_command.parent = command;
184
185 if (arg.param.id == i + command.params.len) {
186 getFieldPtr(&result, field).* = try sub_command.parseHelper(&result, allocator, arg_iter);
187 continue :arg_loop;
188 }
189 }
190
191 return error.InvalidArgument;
192 }
193
194 return result.result;
195 }
196
197 fn GetFieldPtrReturn(comptime Struct: type, comptime field: []const u8) type {
198 var inst: Struct = undefined;
199 const dot_index = comptime mem.indexOfScalar(u8, field, '.') ?? {
200 return @typeOf(&@field(inst, field));
201 };
202
203 return GetFieldPtrReturn(@typeOf(@field(inst, field[0..dot_index])), field[dot_index + 1..]);
204 }
205
206 fn getFieldPtr(curr: var, comptime field: []const u8) GetFieldPtrReturn(@typeOf(curr).Child, field) {
207 const dot_index = comptime mem.indexOfScalar(u8, field, '.') ?? {
208 return &@field(curr, field);
209 };
210
211 return getFieldPtr(&@field(curr, field[0..dot_index]), field[dot_index + 1..]);
212 }
213}; 41};
214 42
215pub const Parser = struct { 43pub const Parser = struct {
@@ -261,3 +89,112 @@ pub const Parser = struct {
261 }.s 89 }.s
262 ); 90 );
263}; 91};
92
93pub fn Clap(comptime Result: type) type {
94 return struct {
95 const Self = this;
96
97 default: Result,
98 params: []const Param,
99
100 pub fn parse(
101 comptime clap: &const Self,
102 comptime Error: type,
103 iter: &core.ArgIterator(Error),
104 ) !Result {
105 // We initialize the core.Clap without any params, and fill them out in parseHelper.
106 var c = core.Clap(usize, Error).init([]core.Param(usize){}, iter);
107
108 const top_level_command = comptime Command.init(Result, &clap.default, clap.params);
109 return try parseHelper(top_level_command, Error, &c);
110 }
111
112 fn parseHelper(
113 comptime command: &const Command,
114 comptime Error: type,
115 clap: &core.Clap(usize, Error),
116 ) !command.Result {
117 var result = @ptrCast(&const command.Result, command.default).*;
118
119 var handled = comptime blk: {
120 var res: [command.params.len]bool = undefined;
121 for (command.params) |p, i| {
122 res[i] = !p.required;
123 }
124
125 break :blk res;
126 };
127
128 // We replace the current clap with the commands parameters, so that we preserve the that
129 // claps state. This is important, as core.Clap could be in a Chaining state, and
130 // constructing a new core.Clap would skip the last chaining arguments.
131 clap.params = comptime blk: {
132 var res: [command.params.len]core.Param(usize) = undefined;
133
134 for (command.params) |p, i| {
135 const id = i;
136 res[id] = core.Param(usize) {
137 .id = id,
138 .takes_value = p.kind == Param.Kind.Option,
139 .names = p.names,
140 };
141 }
142
143 break :blk res;
144 };
145
146 var pos: usize = 0;
147
148 arg_loop:
149 while (try clap.next()) |arg| : (pos += 1) {
150 inline for(command.params) |param, i| {
151 if (arg.param.id == i and (param.position ?? pos) == pos) {
152 handled[i] = true;
153
154 switch (param.kind) {
155 Param.Kind.Flag => {
156 getFieldPtr(&result, param.field).* = true;
157 },
158 Param.Kind.Option => |parser| {
159 try parser.parse(getFieldPtr(&result, param.field), ??arg.value);
160 },
161 Param.Kind.SubCommand => |sub_command| {
162 getFieldPtr(&result, param.field).* = try sub_command.parseHelper(Error, clap);
163
164 // After parsing a subcommand, there should be no arguments left.
165 break :arg_loop;
166 },
167 }
168 continue :arg_loop;
169 }
170 }
171
172 return error.InvalidArgument;
173 }
174
175 for (handled) |h| {
176 if (!h)
177 return error.ParamNotHandled;
178 }
179
180 return result;
181 }
182
183 fn GetFieldPtrReturn(comptime Struct: type, comptime field: []const u8) type {
184 var inst: Struct = undefined;
185 const dot_index = comptime mem.indexOfScalar(u8, field, '.') ?? {
186 return @typeOf(&@field(inst, field));
187 };
188
189 return GetFieldPtrReturn(@typeOf(@field(inst, field[0..dot_index])), field[dot_index + 1..]);
190 }
191
192 fn getFieldPtr(curr: var, comptime field: []const u8) GetFieldPtrReturn(@typeOf(curr).Child, field) {
193 const dot_index = comptime mem.indexOfScalar(u8, field, '.') ?? {
194 return &@field(curr, field);
195 };
196
197 return getFieldPtr(&@field(curr, field[0..dot_index]), field[dot_index + 1..]);
198 }
199 };
200}
diff --git a/tests/extended.zig b/tests/extended.zig
index 8f722e4..140c822 100644
--- a/tests/extended.zig
+++ b/tests/extended.zig
@@ -9,226 +9,342 @@ const extended = clap.extended;
9const assert = debug.assert; 9const assert = debug.assert;
10 10
11const ArgSliceIterator = core.ArgSliceIterator; 11const ArgSliceIterator = core.ArgSliceIterator;
12const Command = extended.Command; 12const Names = core.Names;
13const Clap = extended.Clap;
13const Param = extended.Param; 14const Param = extended.Param;
14const Parser = extended.Parser; 15const Parser = extended.Parser;
15 16
16const Options = struct { 17fn success(comptime parser: var, expect: var, args: []const []const u8) void {
17 str: []const u8, 18 var iter = ArgSliceIterator.init(args);
18 int: i64, 19 const actual = parser.parse(ArgSliceIterator.Error, &iter.iter) catch unreachable;
19 uint: u64,
20 a: bool,
21 b: bool,
22 cc: bool,
23 sub: &const SubOptions,
24
25 pub fn with(op: &const Options, comptime field: []const u8, value: var) Options {
26 var res = op.*;
27 @field(res, field) = value;
28 return res;
29 }
30};
31
32const SubOptions = struct {
33 a: bool,
34 b: u64,
35 qq: bool,
36 20
37 pub fn with(op: &const SubOptions, comptime field: []const u8, value: var) SubOptions { 21 const T = @typeOf(expect).Child;
38 var res = op.*; 22 inline for (@typeInfo(T).Struct.fields) |field| {
39 @field(res, field) = value; 23 assert(@field(expect, field.name) == @field(actual, field.name));
40 return res;
41 } 24 }
42};
43
44const default = Options {
45 .str = "",
46 .int = 0,
47 .uint = 0,
48 .a = false,
49 .b = false,
50 .cc = false,
51 .sub = SubOptions{
52 .a = false,
53 .b = 0,
54 .qq = false,
55 },
56};
57
58fn testNoErr(comptime command: &const Command, args: []const []const u8, expected: &const command.Result) void {
59 var arg_iter = ArgSliceIterator.init(args);
60 const actual = command.parse(debug.global_allocator, &arg_iter.iter) catch unreachable;
61 assert(mem.eql(u8, expected.str, actual.str));
62 assert(expected.int == actual.int);
63 assert(expected.uint == actual.uint);
64 assert(expected.a == actual.a);
65 assert(expected.b == actual.b);
66 assert(expected.cc == actual.cc);
67 assert(expected.sub.a == actual.sub.a);
68 assert(expected.sub.b == actual.sub.b);
69} 25}
70 26
71fn testErr(comptime command: &const Command, args: []const []const u8, expected: error) void { 27fn fail(comptime parser: var, expect: error, args: []const []const u8) void {
72 var arg_iter = ArgSliceIterator.init(args); 28 var iter = ArgSliceIterator.init(args);
73 if (command.parse(debug.global_allocator, &arg_iter.iter)) |actual| { 29 if (parser.parse(ArgSliceIterator.Error, &iter.iter)) |_| {
74 unreachable; 30 unreachable;
75 } else |err| { 31 } else |actual| {
76 assert(err == expected); 32 assert(expect == actual);
77 } 33 }
78} 34}
79 35
80test "clap.extended: short" { 36pub fn Test(comptime Expect: type) type {
81 const command = comptime Command.init( 37 return struct {
82 "", 38 const Self = this;
83 Options,
84 default,
85 []Param {
86 Param.smart("a"),
87 Param.smart("b"),
88 Param.smart("int")
89 .with("short", 'i')
90 .with("takes_value", Parser.int(i64, 10)),
91 },
92 []Command{},
93 );
94
95 testNoErr(command, [][]const u8 { "-a" }, default.with("a", true));
96 testNoErr(command, [][]const u8 { "-a", "-b" }, default.with("a", true).with("b", true));
97 testNoErr(command, [][]const u8 { "-i=100" }, default.with("int", 100));
98 testNoErr(command, [][]const u8 { "-i100" }, default.with("int", 100));
99 testNoErr(command, [][]const u8 { "-i", "100" }, default.with("int", 100));
100 testNoErr(command, [][]const u8 { "-ab" }, default.with("a", true).with("b", true));
101 testNoErr(command, [][]const u8 { "-abi", "100" }, default.with("a", true).with("b", true).with("int", 100));
102 testNoErr(command, [][]const u8 { "-abi=100" }, default.with("a", true).with("b", true).with("int", 100));
103 testNoErr(command, [][]const u8 { "-abi100" }, default.with("a", true).with("b", true).with("int", 100));
104}
105 39
106test "clap.extended: long" { 40 args: []const []const u8,
107 const command = comptime Command.init( 41 kind: Kind,
108 "",
109 Options,
110 default,
111 []Param {
112 Param.smart("cc"),
113 Param.smart("int").with("takes_value", Parser.int(i64, 10)),
114 Param.smart("uint").with("takes_value", Parser.int(u64, 10)),
115 Param.smart("str").with("takes_value", Parser.string),
116 },
117 []Command{},
118 );
119 42
120 testNoErr(command, [][]const u8 { "--cc" }, default.with("cc", true)); 43 const Kind = union(enum) {
121 testNoErr(command, [][]const u8 { "--int", "100" }, default.with("int", 100)); 44 Success: Expect,
122} 45 Fail: error,
46 };
123 47
124test "clap.extended: value bool" { 48 pub fn success(args: []const []const u8, expected: &const Expect) Self {
125 const command = comptime Command.init( 49 return Self{
126 "", 50 .args = args,
127 Options, 51 .kind = Kind{
128 default, 52 .Success = expected.*,
129 []Param { 53 },
130 Param.smart("a"), 54 };
131 }, 55 }
132 []Command{},
133 );
134 56
135 testNoErr(command, [][]const u8 { "-a" }, default.with("a", true)); 57 pub fn fail(args: []const []const u8, err: error) Self {
136} 58 return Self{
59 .args = args,
60 .kind = Kind{
61 .Fail = err,
62 },
63 };
64 }
137 65
138test "clap.extended: value str" { 66 pub fn run(t: &const Self, comptime parser: var) void {
139 const command = comptime Command.init( 67 var iter = ArgSliceIterator.init(t.args);
140 "", 68 const actual = parser.parse(ArgSliceIterator.Error, &iter.iter);
141 Options,
142 default,
143 []Param {
144 Param.smart("str").with("takes_value", Parser.string),
145 },
146 []Command{},
147 );
148 69
149 testNoErr(command, [][]const u8 { "--str", "Hello World!" }, default.with("str", "Hello World!")); 70 switch (t.kind) {
71 Kind.Success => |expected| {
72 const actual_value = actual catch unreachable;
73 inline for (@typeInfo(Expect).Struct.fields) |field| {
74 assert(@field(expected, field.name) == @field(actual_value, field.name));
75 }
76 },
77 Kind.Fail => |expected| {
78 if (actual) |_| {
79 unreachable;
80 } else |actual_err| {
81 assert(actual_err == expected);
82 }
83 },
84 }
85 }
86 };
150} 87}
151 88
152test "clap.extended: value int" { 89test "clap.extended: short" {
153 const command = comptime Command.init( 90 const S = struct {
154 "", 91 a: bool,
155 Options, 92 b: u8,
156 default, 93 };
157 []Param {
158 Param.smart("int").with("takes_value", Parser.int(i64, 10)),
159 },
160 []Command{},
161 );
162
163 testNoErr(command, [][]const u8 { "--int", "100" }, default.with("int", 100));
164}
165 94
166test "clap.extended: position" { 95 const parser = comptime Clap(S){
167 const command = comptime Command.init( 96 .default = S{
168 "", 97 .a = false,
169 Options, 98 .b = 0,
170 default,
171 []Param {
172 Param.smart("a").with("position", 0),
173 Param.smart("b").with("position", 1),
174 }, 99 },
175 []Command{}, 100 .params = []Param{
176 ); 101 Param{
102 .field = "a",
103 .names = Names.short('a'),
104 .kind = Param.Kind.Flag,
105 .required = true,
106 .position = 0,
107 },
108 Param{
109 .field = "b",
110 .names = Names.short('b'),
111 .kind = Param.Kind{ .Option = Parser.int(u8, 10) },
112 .required = false,
113 .position = null,
114 },
115 }
116 };
117
118 const T = Test(S);
119 const tests = []T{
120 T.success(
121 [][]const u8 { "-a" },
122 S{
123 .a = true,
124 .b = 0,
125 },
126 ),
127 T.success(
128 [][]const u8 { "-a", "-b", "100" },
129 S{
130 .a = true,
131 .b = 100,
132 },
133 ),
134 T.success(
135 [][]const u8 { "-a", "-b=100" },
136 S{
137 .a = true,
138 .b = 100,
139 },
140 ),
141 T.success(
142 [][]const u8 { "-a", "-b100" },
143 S{
144 .a = true,
145 .b = 100,
146 },
147 ),
148 T.success(
149 [][]const u8 { "-ab", "100" },
150 S{
151 .a = true,
152 .b = 100,
153 },
154 ),
155 T.success(
156 [][]const u8 { "-ab=100" },
157 S{
158 .a = true,
159 .b = 100,
160 },
161 ),
162 T.success(
163 [][]const u8 { "-ab100" },
164 S{
165 .a = true,
166 .b = 100,
167 },
168 ),
169 T.fail(
170 [][]const u8 { "-q" },
171 error.InvalidArgument,
172 ),
173 T.fail(
174 [][]const u8 { "--a" },
175 error.InvalidArgument,
176 ),
177 T.fail(
178 [][]const u8 { "-b=100" },
179 error.ParamNotHandled,
180 ),
181 T.fail(
182 [][]const u8 { "-b=100", "-a" },
183 error.InvalidArgument,
184 ),
185 };
177 186
178 testNoErr(command, [][]const u8 { "-a", "-b" }, default.with("a", true).with("b", true)); 187 for (tests) |t| {
179 testErr(command, [][]const u8 { "-b", "-a" }, error.InvalidArgument); 188 t.run(parser);
189 }
180} 190}
181 191
182test "clap.extended: sub fields" { 192test "clap.extended: long" {
183 const B = struct { 193 const S = struct {
184 a: bool, 194 a: bool,
185 }; 195 b: u8,
186 const A = struct {
187 b: B,
188 }; 196 };
189 197
190 const command = comptime Command.init( 198 const parser = comptime Clap(S){
191 "", 199 .default = S{
192 A, 200 .a = false,
193 A { .b = B { .a = false } }, 201 .b = 0,
194 []Param {
195 Param.short('a')
196 .with("field", "b.a"),
197 }, 202 },
198 []Command{}, 203 .params = []Param{
199 ); 204 Param{
205 .field = "a",
206 .names = Names.long("a"),
207 .kind = Param.Kind.Flag,
208 .required = true,
209 .position = 0,
210 },
211 Param{
212 .field = "b",
213 .names = Names.long("b"),
214 .kind = Param.Kind{ .Option = Parser.int(u8, 10) },
215 .required = false,
216 .position = null,
217 },
218 }
219 };
200 220
201 var arg_iter = ArgSliceIterator.init([][]const u8{ "-a" }); 221 const T = Test(S);
202 const res = command.parse(debug.global_allocator, &arg_iter.iter) catch unreachable; 222 const tests = []T{
203 debug.assert(res.b.a == true); 223 T.success(
224 [][]const u8 { "--a" },
225 S{
226 .a = true,
227 .b = 0,
228 },
229 ),
230 T.success(
231 [][]const u8 { "--a", "--b", "100" },
232 S{
233 .a = true,
234 .b = 100,
235 },
236 ),
237 T.success(
238 [][]const u8 { "--a", "--b=100" },
239 S{
240 .a = true,
241 .b = 100,
242 },
243 ),
244 T.fail(
245 [][]const u8 { "--a=100" },
246 error.DoesntTakeValue,
247 ),
248 T.fail(
249 [][]const u8 { "--q" },
250 error.InvalidArgument,
251 ),
252 T.fail(
253 [][]const u8 { "-a" },
254 error.InvalidArgument,
255 ),
256 T.fail(
257 [][]const u8 { "--b=100" },
258 error.ParamNotHandled,
259 ),
260 T.fail(
261 [][]const u8 { "--b=100", "--a" },
262 error.InvalidArgument,
263 ),
264 };
265
266 for (tests) |t| {
267 t.run(parser);
268 }
204} 269}
205 270
206test "clap.extended: sub commands" { 271test "clap.extended: bare" {
207 const command = comptime Command.init( 272 const S = struct {
208 "", 273 a: bool,
209 Options, 274 b: u8,
210 default, 275 };
211 []Param { 276
212 Param.smart("a"), 277 const parser = comptime Clap(S){
213 Param.smart("b"), 278 .default = S{
214 }, 279 .a = false,
215 []Command{ 280 .b = 0,
216 Command.init(
217 "sub",
218 SubOptions,
219 default.sub,
220 []Param {
221 Param.smart("a"),
222 Param.smart("b")
223 .with("takes_value", Parser.int(u64, 10)),
224 },
225 []Command{},
226 ),
227 }, 281 },
228 ); 282 .params = []Param{
283 Param{
284 .field = "a",
285 .names = Names.bare("a"),
286 .kind = Param.Kind.Flag,
287 .required = true,
288 .position = 0,
289 },
290 Param{
291 .field = "b",
292 .names = Names.bare("b"),
293 .kind = Param.Kind{ .Option = Parser.int(u8, 10) },
294 .required = false,
295 .position = null,
296 },
297 }
298 };
299
300 const T = Test(S);
301 const tests = []T{
302 T.success(
303 [][]const u8 { "a" },
304 S{
305 .a = true,
306 .b = 0,
307 },
308 ),
309 T.success(
310 [][]const u8 { "a", "b", "100" },
311 S{
312 .a = true,
313 .b = 100,
314 },
315 ),
316 T.success(
317 [][]const u8 { "a", "b=100" },
318 S{
319 .a = true,
320 .b = 100,
321 },
322 ),
323 T.fail(
324 [][]const u8 { "a=100" },
325 error.DoesntTakeValue,
326 ),
327 T.fail(
328 [][]const u8 { "--a" },
329 error.InvalidArgument,
330 ),
331 T.fail(
332 [][]const u8 { "-a" },
333 error.InvalidArgument,
334 ),
335 T.fail(
336 [][]const u8 { "b=100" },
337 error.ParamNotHandled,
338 ),
339 T.fail(
340 [][]const u8 { "b=100", "--a" },
341 error.InvalidArgument,
342 ),
343 };
229 344
230 testNoErr(command, [][]const u8 { "sub", "-a" }, default.with("sub", default.sub.with("a", true))); 345 for (tests) |t| {
231 testNoErr(command, [][]const u8 { "sub", "-b", "100" }, default.with("sub", default.sub.with("b", 100))); 346 t.run(parser);
232 testNoErr(command, [][]const u8 { "-a", "sub", "-a" }, default.with("a", true).with("sub", default.sub.with("a", true))); 347 }
233 testErr(command, [][]const u8 { "-qq", "sub" }, error.InvalidArgument);
234} 348}
349
350// TODO: Test sub commands and sub field access