summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Jimmi Holst Christensen2018-09-06 17:11:58 +0200
committerGravatar Jimmi Holst Christensen2018-09-06 17:11:58 +0200
commit5e1480a7a7537451f7196498ac2988bda8273a9b (patch)
tree2c85b66feeeeec1114d9dbe2608ef64f3ef5ee76
parentUpdated to use pass-by-value where possible (diff)
downloadzig-clap-5e1480a7a7537451f7196498ac2988bda8273a9b.tar.gz
zig-clap-5e1480a7a7537451f7196498ac2988bda8273a9b.tar.xz
zig-clap-5e1480a7a7537451f7196498ac2988bda8273a9b.zip
Removed the extended api. Refactored tests
-rw-r--r--build.zig42
-rw-r--r--clap.zig (renamed from src/core.zig)0
-rw-r--r--index.zig2
-rw-r--r--src/extended.zig233
-rw-r--r--test.zig198
-rw-r--r--tests/core.zig120
-rw-r--r--tests/extended.zig306
7 files changed, 198 insertions, 703 deletions
diff --git a/build.zig b/build.zig
deleted file mode 100644
index 6ec8837..0000000
--- a/build.zig
+++ /dev/null
@@ -1,42 +0,0 @@
1const Builder = @import("std").build.Builder;
2
3pub fn build(b: *Builder) void {
4 const mode = b.standardReleaseOptions();
5
6 {
7 const example_step = b.step("examples", "Build all examples");
8 const examples = [][]const u8{};
9
10 b.default_step.dependOn(example_step);
11 inline for (examples) |example| {
12 comptime const path = "examples/" ++ example ++ ".zig";
13 const exe = b.addExecutable(example, path);
14 exe.setBuildMode(mode);
15 exe.addPackagePath("clap", "index.zig");
16
17 const step = b.step("build-" ++ example, "Build '" ++ path ++ "'");
18 step.dependOn(&exe.step);
19 example_step.dependOn(step);
20 }
21 }
22
23 {
24 const test_step = b.step("tests", "Run all tests");
25 const tests = [][]const u8{
26 "core",
27 "extended",
28 };
29
30 b.default_step.dependOn(test_step);
31 inline for (tests) |test_name| {
32 comptime const path = "tests/" ++ test_name ++ ".zig";
33 const t = b.addTest(path);
34 t.setBuildMode(mode);
35 //t.addPackagePath("clap", "index.zig");
36
37 const step = b.step("test-" ++ test_name, "Run test '" ++ test_name ++ "'");
38 step.dependOn(&t.step);
39 test_step.dependOn(step);
40 }
41 }
42}
diff --git a/src/core.zig b/clap.zig
index bdd1bf4..bdd1bf4 100644
--- a/src/core.zig
+++ b/clap.zig
diff --git a/index.zig b/index.zig
deleted file mode 100644
index 805d72f..0000000
--- a/index.zig
+++ /dev/null
@@ -1,2 +0,0 @@
1pub const core = @import("src/core.zig");
2pub const extended = @import("src/extended.zig");
diff --git a/src/extended.zig b/src/extended.zig
deleted file mode 100644
index f7fc87d..0000000
--- a/src/extended.zig
+++ /dev/null
@@ -1,233 +0,0 @@
1pub const core = @import("core.zig");
2
3const builtin = @import("builtin");
4const std = @import("std");
5
6const mem = std.mem;
7const fmt = std.fmt;
8const debug = std.debug;
9const io = std.io;
10
11const assert = debug.assert;
12
13pub const Param = struct {
14 field: []const u8,
15 names: core.Names,
16 kind: Kind,
17
18 required: bool,
19 position: ?usize,
20
21 pub fn flag(field: []const u8, names: core.Names) Param {
22 return init(
23 field,
24 names,
25 Kind.Flag,
26 );
27 }
28
29 pub fn option(
30 field: []const u8,
31 names: core.Names,
32 comptime parser: Parser,
33 ) Param {
34 return init(
35 field,
36 names,
37 Kind{ .Option = parser },
38 );
39 }
40
41 pub fn subcommand(
42 field: []const u8,
43 names: core.Names,
44 comptime command: Command,
45 ) Param {
46 return init(
47 field,
48 names,
49 Kind{ .Subcommand = command },
50 );
51 }
52
53 pub fn init(field: []const u8, names: core.Names, kind: Kind) Param {
54 return Param{
55 .field = field,
56 .names = names,
57 .kind = kind,
58 .required = false,
59 .position = null,
60 };
61 }
62
63 pub const Kind = union(enum) {
64 Flag,
65 Option: Parser,
66 Subcommand: Command,
67 };
68};
69
70const Opaque = @OpaqueType();
71pub const Command = struct {
72 params: []const Param,
73
74 Result: type,
75 default: *const Opaque,
76
77 pub fn init(comptime Result: type, default: *const Result, params: []const Param) Command {
78 return Command{
79 .params = params,
80 .Result = Result,
81 .default = @ptrCast(*const Opaque, default),
82 };
83 }
84};
85
86pub const Parser = struct {
87 const UnsafeFunction = *const void;
88
89 FieldType: type,
90 Errors: type,
91 func: UnsafeFunction,
92
93 pub fn init(comptime FieldType: type, comptime Errors: type, func: ParseFunc(FieldType, Errors)) Parser {
94 return Parser{
95 .FieldType = FieldType,
96 .Errors = Errors,
97 .func = @ptrCast(UnsafeFunction, func),
98 };
99 }
100
101 fn parse(comptime parser: Parser, field_ptr: *parser.FieldType, arg: []const u8) parser.Errors!void {
102 return @ptrCast(ParseFunc(parser.FieldType, parser.Errors), parser.func)(field_ptr, arg);
103 }
104
105 fn ParseFunc(comptime FieldType: type, comptime Errors: type) type {
106 return fn (*FieldType, []const u8) Errors!void;
107 }
108
109 pub fn int(comptime Int: type, comptime radix: u8) Parser {
110 const func = struct {
111 fn i(field_ptr: *Int, arg: []const u8) !void {
112 field_ptr.* = try fmt.parseInt(Int, arg, radix);
113 }
114 }.i;
115 return Parser.init(Int, @typeOf(func).ReturnType.ErrorSet, func);
116 }
117
118 const string = Parser.init([]const u8, error{}, struct {
119 fn s(field_ptr: *[]const u8, arg: []const u8) (error{}!void) {
120 field_ptr.* = arg;
121 }
122 }.s);
123};
124
125pub fn Clap(comptime Result: type) type {
126 return struct {
127 const Self = this;
128
129 default: Result,
130 params: []const Param,
131
132 // TODO: pass-by-value
133 pub fn parse(
134 comptime clap: *const Self,
135 comptime Error: type,
136 iter: *core.ArgIterator(Error),
137 ) !Result {
138 // We initialize the core.Clap without any params, and fill them out in parseHelper.
139 var c = core.Clap(usize, Error).init([]core.Param(usize){}, iter);
140
141 const top_level_command = comptime Command.init(Result, &clap.default, clap.params);
142 return try parseHelper(&top_level_command, Error, &c);
143 }
144
145 // TODO: pass-by-value
146 fn parseHelper(
147 comptime command: *const Command,
148 comptime Error: type,
149 clap: *core.Clap(usize, Error),
150 ) !command.Result {
151 var result = @ptrCast(*const command.Result, command.default).*;
152
153 var handled = comptime blk: {
154 var res: [command.params.len]bool = undefined;
155 for (command.params) |p, i| {
156 res[i] = !p.required;
157 }
158
159 break :blk res;
160 };
161
162 // We replace the current clap with the commands parameters, so that we preserve the that
163 // claps state. This is important, as core.Clap could be in a Chaining state, and
164 // constructing a new core.Clap would skip the last chaining arguments.
165 clap.params = comptime blk: {
166 var res: [command.params.len]core.Param(usize) = undefined;
167
168 for (command.params) |p, i| {
169 const id = i;
170 res[id] = core.Param(usize){
171 .id = id,
172 .takes_value = p.kind == Param.Kind.Option,
173 .names = p.names,
174 };
175 }
176
177 break :blk res;
178 };
179
180 var pos: usize = 0;
181
182 arg_loop: while (try clap.next()) |arg| : (pos += 1) {
183 inline for (command.params) |param, i| {
184 if (arg.param.id == i and (param.position orelse pos) == pos) {
185 handled[i] = true;
186
187 switch (param.kind) {
188 Param.Kind.Flag => {
189 getFieldPtr(&result, param.field).* = true;
190 },
191 Param.Kind.Option => |parser| {
192 try parser.parse(getFieldPtr(&result, param.field), arg.value.?);
193 },
194 Param.Kind.Subcommand => |sub_command| {
195 getFieldPtr(&result, param.field).* = try sub_command.parseHelper(Error, clap);
196
197 // After parsing a subcommand, there should be no arguments left.
198 break :arg_loop;
199 },
200 }
201 continue :arg_loop;
202 }
203 }
204
205 return error.InvalidArgument;
206 }
207
208 for (handled) |h| {
209 if (!h)
210 return error.ParamNotHandled;
211 }
212
213 return result;
214 }
215
216 fn GetFieldPtrReturn(comptime Struct: type, comptime field: []const u8) type {
217 var inst: Struct = undefined;
218 const dot_index = comptime mem.indexOfScalar(u8, field, '.') orelse {
219 return @typeOf(&@field(inst, field));
220 };
221
222 return GetFieldPtrReturn(@typeOf(@field(inst, field[0..dot_index])), field[dot_index + 1 ..]);
223 }
224
225 fn getFieldPtr(curr: var, comptime field: []const u8) GetFieldPtrReturn(@typeOf(curr).Child, field) {
226 const dot_index = comptime mem.indexOfScalar(u8, field, '.') orelse {
227 return &@field(curr, field);
228 };
229
230 return getFieldPtr(&@field(curr, field[0..dot_index]), field[dot_index + 1 ..]);
231 }
232 };
233}
diff --git a/test.zig b/test.zig
new file mode 100644
index 0000000..1708353
--- /dev/null
+++ b/test.zig
@@ -0,0 +1,198 @@
1const std = @import("std");
2const clap = @import("clap.zig");
3
4const debug = std.debug;
5const mem = std.mem;
6
7const assert = debug.assert;
8
9const ArgSliceIterator = clap.ArgSliceIterator;
10const Names = clap.Names;
11const Param = clap.Param(u8);
12const Clap = clap.Clap(u8, ArgSliceIterator.Error);
13const Arg = clap.Arg(u8);
14
15fn testNoErr(params: []const Param, args: []const []const u8, results: []const Arg) void {
16 var arg_iter = ArgSliceIterator.init(args);
17 var c = Clap.init(params, &arg_iter.iter);
18
19 for (results) |res| {
20 const arg = (c.next() catch unreachable) orelse unreachable;
21 debug.assert(res.param == arg.param);
22 const expected_value = res.value orelse {
23 debug.assert(arg.value == null);
24 continue;
25 };
26 const actual_value = arg.value orelse unreachable;
27 debug.assert(mem.eql(u8, expected_value, actual_value));
28 }
29
30 if (c.next() catch unreachable) |_| {
31 unreachable;
32 }
33}
34
35test "clap: short" {
36 const params = []Param{
37 Param.init(0, false, Names.short('a')),
38 Param.init(1, false, Names.short('b')),
39 Param.init(2, true, Names.short('c')),
40 };
41
42 const a = &params[0];
43 const b = &params[1];
44 const c = &params[2];
45
46 testNoErr(
47 params,
48 [][]const u8{
49 "-a", "-b", "-ab", "-ba",
50 "-c", "0", "-c=0",
51 "-ac", "0", "-ac=0",
52 },
53 []const Arg{
54 Arg.init(a, null),
55 Arg.init(b, null),
56 Arg.init(a, null),
57 Arg.init(b, null),
58 Arg.init(b, null),
59 Arg.init(a, null),
60 Arg.init(c, "0"),
61 Arg.init(c, "0"),
62 Arg.init(a, null),
63 Arg.init(c, "0"),
64 Arg.init(a, null),
65 Arg.init(c, "0"),
66 },
67 );
68}
69
70test "clap: long" {
71 const params = []Param{
72 Param.init(0, false, Names.long("aa")),
73 Param.init(1, false, Names.long("bb")),
74 Param.init(2, true, Names.long("cc")),
75 };
76
77 const aa = &params[0];
78 const bb = &params[1];
79 const cc = &params[2];
80
81 testNoErr(
82 params,
83 [][]const u8{
84 "--aa", "--bb",
85 "--cc", "0", "--cc=0",
86 },
87 []const Arg{
88 Arg.init(aa, null),
89 Arg.init(bb, null),
90 Arg.init(cc, "0"),
91 Arg.init(cc, "0"),
92 },
93 );
94}
95
96test "clap: bare" {
97 const params = []Param{
98 Param.init(0, false, Names.bare("aa")),
99 Param.init(1, false, Names.bare("bb")),
100 Param.init(2, true, Names.bare("cc")),
101 };
102
103 const aa = &params[0];
104 const bb = &params[1];
105 const cc = &params[2];
106
107 testNoErr(
108 params,
109 [][]const u8{
110 "aa", "bb",
111 "cc", "0", "cc=0",
112 },
113 []const Arg{
114 Arg.init(aa, null),
115 Arg.init(bb, null),
116 Arg.init(cc, "0"),
117 Arg.init(cc, "0"),
118 },
119 );
120}
121
122test "clap: none" {
123 const params = []Param{
124 Param.init(0, true, Names.none()),
125 };
126
127 testNoErr(
128 params,
129 [][]const u8{"aa", "bb"},
130 []const Arg{
131 Arg.init(&params[0], "aa"),
132 Arg.init(&params[0], "bb"),
133 },
134 );
135}
136
137test "clap: all" {
138 const params = []Param{
139 Param.init(0, false, Names{
140 .bare = "aa",
141 .short = 'a',
142 .long = "aa",
143 }),
144 Param.init(1, false, Names{
145 .bare = "bb",
146 .short = 'b',
147 .long = "bb",
148 }),
149 Param.init(2, true, Names{
150 .bare = "cc",
151 .short = 'c',
152 .long = "cc",
153 }),
154 Param.init(3, true, Names.none()),
155 };
156
157 const aa = &params[0];
158 const bb = &params[1];
159 const cc = &params[2];
160 const bare = &params[3];
161
162 testNoErr(
163 params,
164 [][]const u8{
165 "-a", "-b", "-ab", "-ba",
166 "-c", "0", "-c=0",
167 "-ac", "0", "-ac=0",
168 "--aa", "--bb",
169 "--cc", "0", "--cc=0",
170 "aa", "bb",
171 "cc", "0", "cc=0",
172 "something",
173 },
174 []const Arg{
175 Arg.init(aa, null),
176 Arg.init(bb, null),
177 Arg.init(aa, null),
178 Arg.init(bb, null),
179 Arg.init(bb, null),
180 Arg.init(aa, null),
181 Arg.init(cc, "0"),
182 Arg.init(cc, "0"),
183 Arg.init(aa, null),
184 Arg.init(cc, "0"),
185 Arg.init(aa, null),
186 Arg.init(cc, "0"),
187 Arg.init(aa, null),
188 Arg.init(bb, null),
189 Arg.init(cc, "0"),
190 Arg.init(cc, "0"),
191 Arg.init(aa, null),
192 Arg.init(bb, null),
193 Arg.init(cc, "0"),
194 Arg.init(cc, "0"),
195 Arg.init(bare, "something"),
196 },
197 );
198}
diff --git a/tests/core.zig b/tests/core.zig
deleted file mode 100644
index 765b161..0000000
--- a/tests/core.zig
+++ /dev/null
@@ -1,120 +0,0 @@
1const std = @import("std");
2const clap = @import("../index.zig");
3
4const debug = std.debug;
5const mem = std.mem;
6const core = clap.core;
7
8const assert = debug.assert;
9
10const ArgSliceIterator = core.ArgSliceIterator;
11const Names = core.Names;
12const Param = core.Param;
13const Clap = core.Clap;
14
15fn testNoErr(params: []const Param(u8), args: []const []const u8, ids: []const u8, values: []const ?[]const u8) void {
16 var arg_iter = ArgSliceIterator.init(args);
17 var iter = Clap(u8, ArgSliceIterator.Error).init(params, &arg_iter.iter);
18
19 var i: usize = 0;
20 while (iter.next() catch unreachable) |arg| : (i += 1) {
21 debug.assert(ids[i] == arg.param.id);
22 const expected_value = values[i] orelse {
23 debug.assert(arg.value == null);
24 continue;
25 };
26 const actual_value = arg.value orelse unreachable;
27
28 debug.assert(mem.eql(u8, expected_value, actual_value));
29 }
30}
31
32test "clap.core: short" {
33 const params = []Param(u8){
34 Param(u8).init(0, false, Names.short('a')),
35 Param(u8).init(1, false, Names.short('b')),
36 Param(u8).init(2, true, Names.short('c')),
37 };
38
39 testNoErr(params, [][]const u8{"-a"}, []u8{0}, []?[]const u8{null});
40 testNoErr(params, [][]const u8{ "-a", "-b" }, []u8{ 0, 1 }, []?[]const u8{ null, null });
41 testNoErr(params, [][]const u8{"-ab"}, []u8{ 0, 1 }, []?[]const u8{ null, null });
42 testNoErr(params, [][]const u8{"-c=100"}, []u8{2}, []?[]const u8{"100"});
43 testNoErr(params, [][]const u8{"-c100"}, []u8{2}, []?[]const u8{"100"});
44 testNoErr(params, [][]const u8{ "-c", "100" }, []u8{2}, []?[]const u8{"100"});
45 testNoErr(params, [][]const u8{ "-abc", "100" }, []u8{ 0, 1, 2 }, []?[]const u8{ null, null, "100" });
46 testNoErr(params, [][]const u8{"-abc=100"}, []u8{ 0, 1, 2 }, []?[]const u8{ null, null, "100" });
47 testNoErr(params, [][]const u8{"-abc100"}, []u8{ 0, 1, 2 }, []?[]const u8{ null, null, "100" });
48}
49
50test "clap.core: long" {
51 const params = []Param(u8){
52 Param(u8).init(0, false, Names.long("aa")),
53 Param(u8).init(1, false, Names.long("bb")),
54 Param(u8).init(2, true, Names.long("cc")),
55 };
56
57 testNoErr(params, [][]const u8{"--aa"}, []u8{0}, []?[]const u8{null});
58 testNoErr(params, [][]const u8{ "--aa", "--bb" }, []u8{ 0, 1 }, []?[]const u8{ null, null });
59 testNoErr(params, [][]const u8{"--cc=100"}, []u8{2}, []?[]const u8{"100"});
60 testNoErr(params, [][]const u8{ "--cc", "100" }, []u8{2}, []?[]const u8{"100"});
61}
62
63test "clap.core: bare" {
64 const params = []Param(u8){
65 Param(u8).init(0, false, Names.bare("aa")),
66 Param(u8).init(1, false, Names.bare("bb")),
67 Param(u8).init(2, true, Names.bare("cc")),
68 };
69
70 testNoErr(params, [][]const u8{"aa"}, []u8{0}, []?[]const u8{null});
71 testNoErr(params, [][]const u8{ "aa", "bb" }, []u8{ 0, 1 }, []?[]const u8{ null, null });
72 testNoErr(params, [][]const u8{"cc=100"}, []u8{2}, []?[]const u8{"100"});
73 testNoErr(params, [][]const u8{ "cc", "100" }, []u8{2}, []?[]const u8{"100"});
74}
75
76test "clap.core: none" {
77 const params = []Param(u8){Param(u8).init(0, true, Names.none())};
78
79 testNoErr(params, [][]const u8{"aa"}, []u8{0}, []?[]const u8{"aa"});
80}
81
82test "clap.core: all" {
83 const params = []Param(u8){
84 Param(u8).init(0, false, Names{
85 .bare = "aa",
86 .short = 'a',
87 .long = "aa",
88 }),
89 Param(u8).init(1, false, Names{
90 .bare = "bb",
91 .short = 'b',
92 .long = "bb",
93 }),
94 Param(u8).init(2, true, Names{
95 .bare = "cc",
96 .short = 'c',
97 .long = "cc",
98 }),
99 Param(u8).init(3, true, Names.none()),
100 };
101
102 testNoErr(params, [][]const u8{"-a"}, []u8{0}, []?[]const u8{null});
103 testNoErr(params, [][]const u8{ "-a", "-b" }, []u8{ 0, 1 }, []?[]const u8{ null, null });
104 testNoErr(params, [][]const u8{"-ab"}, []u8{ 0, 1 }, []?[]const u8{ null, null });
105 testNoErr(params, [][]const u8{"-c=100"}, []u8{2}, []?[]const u8{"100"});
106 testNoErr(params, [][]const u8{"-c100"}, []u8{2}, []?[]const u8{"100"});
107 testNoErr(params, [][]const u8{ "-c", "100" }, []u8{2}, []?[]const u8{"100"});
108 testNoErr(params, [][]const u8{ "-abc", "100" }, []u8{ 0, 1, 2 }, []?[]const u8{ null, null, "100" });
109 testNoErr(params, [][]const u8{"-abc=100"}, []u8{ 0, 1, 2 }, []?[]const u8{ null, null, "100" });
110 testNoErr(params, [][]const u8{"-abc100"}, []u8{ 0, 1, 2 }, []?[]const u8{ null, null, "100" });
111 testNoErr(params, [][]const u8{"--aa"}, []u8{0}, []?[]const u8{null});
112 testNoErr(params, [][]const u8{ "--aa", "--bb" }, []u8{ 0, 1 }, []?[]const u8{ null, null });
113 testNoErr(params, [][]const u8{"--cc=100"}, []u8{2}, []?[]const u8{"100"});
114 testNoErr(params, [][]const u8{ "--cc", "100" }, []u8{2}, []?[]const u8{"100"});
115 testNoErr(params, [][]const u8{"aa"}, []u8{0}, []?[]const u8{null});
116 testNoErr(params, [][]const u8{ "aa", "bb" }, []u8{ 0, 1 }, []?[]const u8{ null, null });
117 testNoErr(params, [][]const u8{"cc=100"}, []u8{2}, []?[]const u8{"100"});
118 testNoErr(params, [][]const u8{ "cc", "100" }, []u8{2}, []?[]const u8{"100"});
119 testNoErr(params, [][]const u8{"dd"}, []u8{3}, []?[]const u8{"dd"});
120}
diff --git a/tests/extended.zig b/tests/extended.zig
deleted file mode 100644
index 9670814..0000000
--- a/tests/extended.zig
+++ /dev/null
@@ -1,306 +0,0 @@
1const std = @import("std");
2const clap = @import("../index.zig");
3
4const debug = std.debug;
5const mem = std.mem;
6const core = clap.core;
7const extended = clap.extended;
8
9const assert = debug.assert;
10
11const ArgSliceIterator = core.ArgSliceIterator;
12const Names = core.Names;
13const Clap = extended.Clap;
14const Param = extended.Param;
15const Parser = extended.Parser;
16
17pub fn Test(comptime Expect: type) type {
18 return struct {
19 const Self = this;
20
21 args: []const []const u8,
22 kind: Kind,
23
24 const Kind = union(enum) {
25 Success: Expect,
26 Fail: error,
27 };
28
29 pub fn success(args: []const []const u8, expected: Expect) Self {
30 return Self{
31 .args = args,
32 .kind = Kind{ .Success = expected },
33 };
34 }
35
36 pub fn fail(args: []const []const u8, err: error) Self {
37 return Self{
38 .args = args,
39 .kind = Kind{ .Fail = err },
40 };
41 }
42
43 pub fn run(t: Self, comptime parser: var) void {
44 var iter = ArgSliceIterator.init(t.args);
45 const actual = parser.parse(ArgSliceIterator.Error, &iter.iter);
46
47 switch (t.kind) {
48 Kind.Success => |expected| {
49 const actual_value = actual catch unreachable;
50 inline for (@typeInfo(Expect).Struct.fields) |field| {
51 assert(@field(expected, field.name) == @field(actual_value, field.name));
52 }
53 },
54 Kind.Fail => |expected| {
55 if (actual) |_| {
56 unreachable;
57 } else |actual_err| {
58 assert(actual_err == expected);
59 }
60 },
61 }
62 }
63 };
64}
65
66test "clap.extended: short" {
67 const S = struct {
68 a: bool,
69 b: u8,
70 };
71
72 const parser = comptime Clap(S){
73 .default = S{
74 .a = false,
75 .b = 0,
76 },
77 .params = []Param{
78 p: {
79 var res = Param.flag("a", Names.short('a'));
80 res.required = true;
81 res.position = 0;
82 break :p res;
83 },
84 Param.option("b", Names.short('b'), Parser.int(u8, 10)),
85 },
86 };
87
88 const T = Test(S);
89 const tests = []T{
90 T.success(
91 [][]const u8{"-a"},
92 S{
93 .a = true,
94 .b = 0,
95 },
96 ),
97 T.success(
98 [][]const u8{ "-a", "-b", "100" },
99 S{
100 .a = true,
101 .b = 100,
102 },
103 ),
104 T.success(
105 [][]const u8{ "-a", "-b=100" },
106 S{
107 .a = true,
108 .b = 100,
109 },
110 ),
111 T.success(
112 [][]const u8{ "-a", "-b100" },
113 S{
114 .a = true,
115 .b = 100,
116 },
117 ),
118 T.success(
119 [][]const u8{ "-ab", "100" },
120 S{
121 .a = true,
122 .b = 100,
123 },
124 ),
125 T.success(
126 [][]const u8{"-ab=100"},
127 S{
128 .a = true,
129 .b = 100,
130 },
131 ),
132 T.success(
133 [][]const u8{"-ab100"},
134 S{
135 .a = true,
136 .b = 100,
137 },
138 ),
139 T.fail(
140 [][]const u8{"-q"},
141 error.InvalidArgument,
142 ),
143 T.fail(
144 [][]const u8{"--a"},
145 error.InvalidArgument,
146 ),
147 T.fail(
148 [][]const u8{"-b=100"},
149 error.ParamNotHandled,
150 ),
151 T.fail(
152 [][]const u8{ "-b=100", "-a" },
153 error.InvalidArgument,
154 ),
155 };
156
157 for (tests) |t| {
158 t.run(parser);
159 }
160}
161
162test "clap.extended: long" {
163 const S = struct {
164 a: bool,
165 b: u8,
166 };
167
168 const parser = comptime Clap(S){
169 .default = S{
170 .a = false,
171 .b = 0,
172 },
173 .params = []Param{
174 p: {
175 var res = Param.flag("a", Names.long("a"));
176 res.required = true;
177 res.position = 0;
178 break :p res;
179 },
180 Param.option("b", Names.long("b"), Parser.int(u8, 10)),
181 },
182 };
183
184 const T = Test(S);
185 const tests = []T{
186 T.success(
187 [][]const u8{"--a"},
188 S{
189 .a = true,
190 .b = 0,
191 },
192 ),
193 T.success(
194 [][]const u8{ "--a", "--b", "100" },
195 S{
196 .a = true,
197 .b = 100,
198 },
199 ),
200 T.success(
201 [][]const u8{ "--a", "--b=100" },
202 S{
203 .a = true,
204 .b = 100,
205 },
206 ),
207 T.fail(
208 [][]const u8{"--a=100"},
209 error.DoesntTakeValue,
210 ),
211 T.fail(
212 [][]const u8{"--q"},
213 error.InvalidArgument,
214 ),
215 T.fail(
216 [][]const u8{"-a"},
217 error.InvalidArgument,
218 ),
219 T.fail(
220 [][]const u8{"--b=100"},
221 error.ParamNotHandled,
222 ),
223 T.fail(
224 [][]const u8{ "--b=100", "--a" },
225 error.InvalidArgument,
226 ),
227 };
228
229 for (tests) |t| {
230 t.run(parser);
231 }
232}
233
234test "clap.extended: bare" {
235 const S = struct {
236 a: bool,
237 b: u8,
238 };
239
240 const parser = comptime Clap(S){
241 .default = S{
242 .a = false,
243 .b = 0,
244 },
245 .params = []Param{
246 p: {
247 var res = Param.flag("a", Names.bare("a"));
248 res.required = true;
249 res.position = 0;
250 break :p res;
251 },
252 Param.option("b", Names.bare("b"), Parser.int(u8, 10)),
253 },
254 };
255
256 const T = Test(S);
257 const tests = []T{
258 T.success(
259 [][]const u8{"a"},
260 S{
261 .a = true,
262 .b = 0,
263 },
264 ),
265 T.success(
266 [][]const u8{ "a", "b", "100" },
267 S{
268 .a = true,
269 .b = 100,
270 },
271 ),
272 T.success(
273 [][]const u8{ "a", "b=100" },
274 S{
275 .a = true,
276 .b = 100,
277 },
278 ),
279 T.fail(
280 [][]const u8{"a=100"},
281 error.DoesntTakeValue,
282 ),
283 T.fail(
284 [][]const u8{"--a"},
285 error.InvalidArgument,
286 ),
287 T.fail(
288 [][]const u8{"-a"},
289 error.InvalidArgument,
290 ),
291 T.fail(
292 [][]const u8{"b=100"},
293 error.ParamNotHandled,
294 ),
295 T.fail(
296 [][]const u8{ "b=100", "--a" },
297 error.InvalidArgument,
298 ),
299 };
300
301 for (tests) |t| {
302 t.run(parser);
303 }
304}
305
306// TODO: Test sub commands and sub field access