summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--build.zig46
-rw-r--r--example.zig68
-rw-r--r--examples/core.zig0
-rw-r--r--examples/extended.zig0
-rw-r--r--index.zig493
-rw-r--r--src/core.zig (renamed from core.zig)121
-rw-r--r--src/extended.zig264
-rw-r--r--tests/core.zig134
-rw-r--r--tests/extended.zig234
9 files changed, 680 insertions, 680 deletions
diff --git a/build.zig b/build.zig
new file mode 100644
index 0000000..847a3a2
--- /dev/null
+++ b/build.zig
@@ -0,0 +1,46 @@
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 "core",
10 "extended",
11 };
12
13 b.default_step.dependOn(example_step);
14 inline for (examples) |example| {
15 comptime const path = "examples/" ++ example ++ ".zig";
16 const exe = b.addExecutable(example, path);
17 exe.setBuildMode(mode);
18 exe.addPackagePath("clap", "index.zig");
19
20 const step = b.step("build-" ++ example, "Build '" ++ path ++ "'");
21 step.dependOn(&exe.step);
22 example_step.dependOn(step);
23 }
24 }
25
26 {
27 const test_step = b.step("tests", "Run all tests");
28 const tests = [][]const u8 {
29 "core",
30 "extended",
31 };
32
33 b.default_step.dependOn(test_step);
34 inline for (tests) |test_name| {
35 comptime const path = "tests/" ++ test_name ++ ".zig";
36 const t = b.addTest(path);
37 t.setBuildMode(mode);
38 //t.addPackagePath("clap", "index.zig");
39
40 const step = b.step("test-" ++ test_name, "Run test '" ++ test_name ++ "'");
41 step.dependOn(&t.step);
42 test_step.dependOn(step);
43 }
44
45 }
46}
diff --git a/example.zig b/example.zig
deleted file mode 100644
index 4b3fa82..0000000
--- a/example.zig
+++ /dev/null
@@ -1,68 +0,0 @@
1const std = @import("std");
2const clap = @import("index.zig");
3
4const debug = std.debug;
5const os = std.os;
6
7const Options = struct {
8 print_values: bool,
9 a: i64,
10 b: u64,
11 c: u8,
12 d: []const u8,
13};
14
15// Output on windows:
16// zig-clap> .\example.exe -a 1
17// zig-clap> .\example.exe -p -a 1
18// a = 1
19// zig-clap> .\example.exe -pa 1
20// a = 1
21// zig-clap> .\example.exe -pd V1
22// d = V1
23// zig-clap> .\example.exe -pd=V2
24// d = V2
25// zig-clap> .\example.exe -p -d=V3
26// d = V3
27// zig-clap> .\example.exe -pdV=4
28// d = V=4
29// zig-clap> .\example.exe -p -dV=5
30// d = V=5
31
32pub fn main() !void {
33 const parser = comptime clap.Clap(Options) {
34 .defaults = Options {
35 .print_values = false,
36 .a = 0,
37 .b = 0,
38 .c = 0,
39 .d = "",
40 },
41 .params = []clap.Param {
42 clap.Param.smart("a")
43 .with("takes_value", clap.Parser.int(i64, 10)),
44 clap.Param.smart("b")
45 .with("takes_value", clap.Parser.int(u64, 10)),
46 clap.Param.smart("c")
47 .with("takes_value", clap.Parser.int(u8, 10)),
48 clap.Param.smart("d")
49 .with("takes_value", clap.Parser.string),
50 clap.Param.smart("print_values")
51 .with("short", 'p')
52 .with("long", "print-values"),
53 }
54 };
55
56 var arg_iter = clap.core.OsArgIterator.init();
57 const iter = &arg_iter.iter;
58 const command = iter.next(debug.global_allocator);
59
60 const options = try parser.parse(debug.global_allocator, iter);
61
62 if (options.print_values) {
63 if (options.a != 0) debug.warn("a = {}\n", options.a);
64 if (options.b != 0) debug.warn("b = {}\n", options.b);
65 if (options.c != 0) debug.warn("c = {}\n", options.c);
66 if (options.d.len != 0) debug.warn("d = {}\n", options.d);
67 }
68}
diff --git a/examples/core.zig b/examples/core.zig
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/examples/core.zig
diff --git a/examples/extended.zig b/examples/extended.zig
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/examples/extended.zig
diff --git a/index.zig b/index.zig
index 8226dfa..805d72f 100644
--- a/index.zig
+++ b/index.zig
@@ -1,491 +1,2 @@
1pub const core = @import("core.zig"); 1pub const core = @import("src/core.zig");
2 2pub const extended = @import("src/extended.zig");
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
13const Opaque = @OpaqueType();
14
15pub const Param = struct {
16 field: []const u8,
17 short: ?u8,
18 long: ?[]const u8,
19 takes_value: ?Parser,
20 required: bool,
21 position: ?usize,
22
23 pub fn short(s: u8) Param {
24 return Param{
25 .field = []u8{s},
26 .short = s,
27 .long = null,
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};
77
78pub const Command = struct {
79 field: []const u8,
80 name: []const u8,
81 params: []const Param,
82 sub_commands: []const Command,
83
84 Result: type,
85 defaults: &const Opaque,
86 parent: ?&const Command,
87
88 pub fn init(name: []const u8, comptime Result: type, defaults: &const Result, params: []const Param, sub_commands: []const Command) Command {
89 return Command{
90 .field = name,
91 .name = name,
92 .params = params,
93 .sub_commands = sub_commands,
94 .Result = Result,
95 .defaults = @ptrCast(&const Opaque, defaults),
96 .parent = null,
97 };
98 }
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: &core.ArgIterator) !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: &core.ArgIterator) !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 iter = core.Clap(usize).init(core_params, arg_iter, allocator);
163 defer iter.deinit();
164
165 arg_loop:
166 while (try iter.next()) |arg| : (pos += 1) {
167 inline for(command.params) |param, i| {
168 comptime const field = "result." ++ param.field;
169
170 if (arg.id == i and (param.position ?? pos) == pos) {
171 if (param.takes_value) |parser| {
172 try parser.parse(getFieldPtr(&result, field), ??arg.value);
173 } else {
174 getFieldPtr(&result, field).* = true;
175 }
176 handled[i] = true;
177 continue :arg_loop;
178 }
179 }
180
181 inline for(command.sub_commands) |c, i| {
182 comptime const field = "result." ++ c.field;
183 comptime var sub_command = c;
184 sub_command.parent = command;
185
186 if (arg.id == i + command.params.len) {
187 getFieldPtr(&result, field).* = try sub_command.parseHelper(&result, allocator, arg_iter);
188 continue :arg_loop;
189 }
190 }
191
192 return error.InvalidArgument;
193 }
194
195 return result.result;
196 }
197
198 fn GetFieldPtrReturn(comptime Struct: type, comptime field: []const u8) type {
199 var inst: Struct = undefined;
200 const dot_index = comptime mem.indexOfScalar(u8, field, '.') ?? {
201 return @typeOf(&@field(inst, field));
202 };
203
204 return GetFieldPtrReturn(@typeOf(@field(inst, field[0..dot_index])), field[dot_index + 1..]);
205 }
206
207 fn getFieldPtr(curr: var, comptime field: []const u8) GetFieldPtrReturn(@typeOf(curr).Child, field) {
208 const dot_index = comptime mem.indexOfScalar(u8, field, '.') ?? {
209 return &@field(curr, field);
210 };
211
212 return getFieldPtr(&@field(curr, field[0..dot_index]), field[dot_index + 1..]);
213 }
214};
215
216pub const Parser = struct {
217 const UnsafeFunction = &const void;
218
219 FieldType: type,
220 Errors: type,
221 func: UnsafeFunction,
222
223 pub fn init(comptime FieldType: type, comptime Errors: type, func: parseFunc(FieldType, Errors)) Parser {
224 return Parser {
225 .FieldType = FieldType,
226 .Errors = Errors,
227 .func = @ptrCast(UnsafeFunction, func),
228 };
229 }
230
231 fn parse(comptime parser: Parser, field_ptr: TakePtr(parser.FieldType), arg: []const u8) parser.Errors!void {
232 return @ptrCast(parseFunc(parser.FieldType, parser.Errors), parser.func)(field_ptr, arg);
233 }
234
235 // TODO: This is a workaround, since we don't have pointer reform yet.
236 fn TakePtr(comptime T: type) type { return &T; }
237
238 fn parseFunc(comptime FieldType: type, comptime Errors: type) type {
239 return fn(&FieldType, []const u8) Errors!void;
240 }
241
242 pub fn int(comptime Int: type, comptime radix: u8) Parser {
243 const func = struct {
244 fn i(field_ptr: &Int, arg: []const u8) !void {
245 field_ptr.* = try fmt.parseInt(Int, arg, radix);
246 }
247 }.i;
248 return Parser.init(
249 Int,
250 @typeOf(func).ReturnType.ErrorSet,
251 func
252 );
253 }
254
255 const string = Parser.init(
256 []const u8,
257 error{},
258 struct {
259 fn s(field_ptr: &[]const u8, arg: []const u8) (error{}!void) {
260 field_ptr.* = arg;
261 }
262 }.s
263 );
264};
265
266
267const Options = struct {
268 str: []const u8,
269 int: i64,
270 uint: u64,
271 a: bool,
272 b: bool,
273 cc: bool,
274 sub: &const SubOptions,
275
276 pub fn with(op: &const Options, comptime field: []const u8, value: var) Options {
277 var res = op.*;
278 @field(res, field) = value;
279 return res;
280 }
281};
282
283const SubOptions = struct {
284 a: bool,
285 b: u64,
286 qq: bool,
287
288 pub fn with(op: &const SubOptions, comptime field: []const u8, value: var) SubOptions {
289 var res = op.*;
290 @field(res, field) = value;
291 return res;
292 }
293};
294
295const default = Options {
296 .str = "",
297 .int = 0,
298 .uint = 0,
299 .a = false,
300 .b = false,
301 .cc = false,
302 .sub = SubOptions{
303 .a = false,
304 .b = 0,
305 .qq = false,
306 },
307};
308
309fn testNoErr(comptime command: &const Command, args: []const []const u8, expected: &const command.Result) void {
310 var arg_iter = core.ArgSliceIterator.init(args);
311 const actual = command.parse(debug.global_allocator, &arg_iter.iter) catch unreachable;
312 assert(mem.eql(u8, expected.str, actual.str));
313 assert(expected.int == actual.int);
314 assert(expected.uint == actual.uint);
315 assert(expected.a == actual.a);
316 assert(expected.b == actual.b);
317 assert(expected.cc == actual.cc);
318 assert(expected.sub.a == actual.sub.a);
319 assert(expected.sub.b == actual.sub.b);
320}
321
322fn testErr(comptime command: &const Command, args: []const []const u8, expected: error) void {
323 var arg_iter = core.ArgSliceIterator.init(args);
324 if (command.parse(debug.global_allocator, &arg_iter.iter)) |actual| {
325 unreachable;
326 } else |err| {
327 assert(err == expected);
328 }
329}
330
331test "command.core" {
332 _ = core;
333}
334
335test "command: short" {
336 const command = comptime Command.init(
337 "",
338 Options,
339 default,
340 []Param {
341 Param.smart("a"),
342 Param.smart("b"),
343 Param.smart("int")
344 .with("short", 'i')
345 .with("takes_value", Parser.int(i64, 10)),
346 },
347 []Command{},
348 );
349
350 testNoErr(command, [][]const u8 { "-a" }, default.with("a", true));
351 testNoErr(command, [][]const u8 { "-a", "-b" }, default.with("a", true).with("b", true));
352 testNoErr(command, [][]const u8 { "-i=100" }, default.with("int", 100));
353 testNoErr(command, [][]const u8 { "-i100" }, default.with("int", 100));
354 testNoErr(command, [][]const u8 { "-i", "100" }, default.with("int", 100));
355 testNoErr(command, [][]const u8 { "-ab" }, default.with("a", true).with("b", true));
356 testNoErr(command, [][]const u8 { "-abi", "100" }, default.with("a", true).with("b", true).with("int", 100));
357 testNoErr(command, [][]const u8 { "-abi=100" }, default.with("a", true).with("b", true).with("int", 100));
358 testNoErr(command, [][]const u8 { "-abi100" }, default.with("a", true).with("b", true).with("int", 100));
359}
360
361test "command: long" {
362 const command = comptime Command.init(
363 "",
364 Options,
365 default,
366 []Param {
367 Param.smart("cc"),
368 Param.smart("int").with("takes_value", Parser.int(i64, 10)),
369 Param.smart("uint").with("takes_value", Parser.int(u64, 10)),
370 Param.smart("str").with("takes_value", Parser.string),
371 },
372 []Command{},
373 );
374
375 testNoErr(command, [][]const u8 { "--cc" }, default.with("cc", true));
376 testNoErr(command, [][]const u8 { "--int", "100" }, default.with("int", 100));
377}
378
379test "command: value bool" {
380 const command = comptime Command.init(
381 "",
382 Options,
383 default,
384 []Param {
385 Param.smart("a"),
386 },
387 []Command{},
388 );
389
390 testNoErr(command, [][]const u8 { "-a" }, default.with("a", true));
391}
392
393test "command: value str" {
394 const command = comptime Command.init(
395 "",
396 Options,
397 default,
398 []Param {
399 Param.smart("str").with("takes_value", Parser.string),
400 },
401 []Command{},
402 );
403
404 testNoErr(command, [][]const u8 { "--str", "Hello World!" }, default.with("str", "Hello World!"));
405}
406
407test "command: value int" {
408 const command = comptime Command.init(
409 "",
410 Options,
411 default,
412 []Param {
413 Param.smart("int").with("takes_value", Parser.int(i64, 10)),
414 },
415 []Command{},
416 );
417
418 testNoErr(command, [][]const u8 { "--int", "100" }, default.with("int", 100));
419}
420
421test "command: position" {
422 const command = comptime Command.init(
423 "",
424 Options,
425 default,
426 []Param {
427 Param.smart("a").with("position", 0),
428 Param.smart("b").with("position", 1),
429 },
430 []Command{},
431 );
432
433 testNoErr(command, [][]const u8 { "-a", "-b" }, default.with("a", true).with("b", true));
434 testErr(command, [][]const u8 { "-b", "-a" }, error.InvalidArgument);
435}
436
437test "command: sub fields" {
438 const B = struct {
439 a: bool,
440 };
441 const A = struct {
442 b: B,
443 };
444
445 const command = comptime Command.init(
446 "",
447 A,
448 A { .b = B { .a = false } },
449 []Param {
450 Param.short('a')
451 .with("field", "b.a"),
452 },
453 []Command{},
454 );
455
456 var arg_iter = core.ArgSliceIterator.init([][]const u8{ "-a" });
457 const res = command.parse(debug.global_allocator, &arg_iter.iter) catch unreachable;
458 debug.assert(res.b.a == true);
459}
460
461test "command: sub commands" {
462 const command = comptime Command.init(
463 "",
464 Options,
465 default,
466 []Param {
467 Param.smart("a"),
468 Param.smart("b"),
469 },
470 []Command{
471 Command.init(
472 "sub",
473 SubOptions,
474 default.sub,
475 []Param {
476 Param.smart("a"),
477 Param.smart("b")
478 .with("takes_value", Parser.int(u64, 10)),
479 },
480 []Command{},
481 ),
482 },
483 );
484
485 debug.warn("{c}", ??command.params[0].short);
486
487 testNoErr(command, [][]const u8 { "sub", "-a" }, default.with("sub", default.sub.with("a", true)));
488 testNoErr(command, [][]const u8 { "sub", "-b", "100" }, default.with("sub", default.sub.with("b", 100)));
489 testNoErr(command, [][]const u8 { "-a", "sub", "-a" }, default.with("a", true).with("sub", default.sub.with("a", true)));
490 testErr(command, [][]const u8 { "-qq", "sub" }, error.InvalidArgument);
491}
diff --git a/core.zig b/src/core.zig
index 306ff63..a3fb44c 100644
--- a/core.zig
+++ b/src/core.zig
@@ -370,124 +370,3 @@ pub fn Clap(comptime Id: type) type {
370 } 370 }
371 }; 371 };
372} 372}
373
374fn testNoErr(params: []const Param(u8), args: []const []const u8, ids: []const u8, values: []const ?[]const u8) void {
375 var arg_iter = ArgSliceIterator.init(args);
376 var iter = Clap(u8).init(params, &arg_iter.iter, debug.global_allocator);
377
378 var i: usize = 0;
379 while (iter.next() catch unreachable) |arg| : (i += 1) {
380 debug.assert(ids[i] == arg.id);
381 const expected_value = values[i] ?? {
382 debug.assert(arg.value == null);
383 continue;
384 };
385 const actual_value = arg.value ?? unreachable;
386
387 debug.assert(mem.eql(u8, expected_value, actual_value));
388 }
389}
390
391test "clap.core: short" {
392 const params = []Param(u8) {
393 Param(u8).init(0, false, Names.short('a')),
394 Param(u8).init(1, false, Names.short('b')),
395 Param(u8).init(2, true, Names.short('c')),
396 };
397
398 testNoErr(params, [][]const u8 { "-a" }, []u8{0}, []?[]const u8{null});
399 testNoErr(params, [][]const u8 { "-a", "-b" }, []u8{0,1}, []?[]const u8{null,null});
400 testNoErr(params, [][]const u8 { "-ab" }, []u8{0,1}, []?[]const u8{null,null});
401 testNoErr(params, [][]const u8 { "-c=100" }, []u8{2}, []?[]const u8{"100"});
402 testNoErr(params, [][]const u8 { "-c100" }, []u8{2}, []?[]const u8{"100"});
403 testNoErr(params, [][]const u8 { "-c", "100" }, []u8{2}, []?[]const u8{"100"});
404 testNoErr(params, [][]const u8 { "-abc", "100" }, []u8{0,1,2}, []?[]const u8{null,null,"100"});
405 testNoErr(params, [][]const u8 { "-abc=100" }, []u8{0,1,2}, []?[]const u8{null,null,"100"});
406 testNoErr(params, [][]const u8 { "-abc100" }, []u8{0,1,2}, []?[]const u8{null,null,"100"});
407}
408
409test "clap.core: long" {
410 const params = []Param(u8) {
411 Param(u8).init(0, false, Names.long("aa")),
412 Param(u8).init(1, false, Names.long("bb")),
413 Param(u8).init(2, true, Names.long("cc")),
414 };
415
416 testNoErr(params, [][]const u8 { "--aa" }, []u8{0}, []?[]const u8{null});
417 testNoErr(params, [][]const u8 { "--aa", "--bb" }, []u8{0,1}, []?[]const u8{null,null});
418 testNoErr(params, [][]const u8 { "--cc=100" }, []u8{2}, []?[]const u8{"100"});
419 testNoErr(params, [][]const u8 { "--cc", "100" }, []u8{2}, []?[]const u8{"100"});
420}
421
422test "clap.core: bare" {
423 const params = []Param(u8) {
424 Param(u8).init(0, false, Names.bare("aa")),
425 Param(u8).init(1, false, Names.bare("bb")),
426 Param(u8).init(2, true, Names.bare("cc")),
427 };
428
429 testNoErr(params, [][]const u8 { "aa" }, []u8{0}, []?[]const u8{null});
430 testNoErr(params, [][]const u8 { "aa", "bb" }, []u8{0,1}, []?[]const u8{null,null});
431 testNoErr(params, [][]const u8 { "cc=100" }, []u8{2}, []?[]const u8{"100"});
432 testNoErr(params, [][]const u8 { "cc", "100" }, []u8{2}, []?[]const u8{"100"});
433}
434
435test "clap.core: none" {
436 const params = []Param(u8) {
437 Param(u8).init(0, true, Names.none()),
438 };
439
440 testNoErr(params, [][]const u8 { "aa" }, []u8{0}, []?[]const u8{"aa"});
441}
442
443test "clap.core: all" {
444 const params = []Param(u8) {
445 Param(u8).init(
446 0,
447 false,
448 Names{
449 .bare = "aa",
450 .short = 'a',
451 .long = "aa",
452 }
453 ),
454 Param(u8).init(
455 1,
456 false,
457 Names{
458 .bare = "bb",
459 .short = 'b',
460 .long = "bb",
461 }
462 ),
463 Param(u8).init(
464 2,
465 true,
466 Names{
467 .bare = "cc",
468 .short = 'c',
469 .long = "cc",
470 }
471 ),
472 Param(u8).init(3, true, Names.none()),
473 };
474
475 testNoErr(params, [][]const u8 { "-a" }, []u8{0}, []?[]const u8{null});
476 testNoErr(params, [][]const u8 { "-a", "-b" }, []u8{0,1}, []?[]const u8{null,null});
477 testNoErr(params, [][]const u8 { "-ab" }, []u8{0,1}, []?[]const u8{null,null});
478 testNoErr(params, [][]const u8 { "-c=100" }, []u8{2}, []?[]const u8{"100"});
479 testNoErr(params, [][]const u8 { "-c100" }, []u8{2}, []?[]const u8{"100"});
480 testNoErr(params, [][]const u8 { "-c", "100" }, []u8{2}, []?[]const u8{"100"});
481 testNoErr(params, [][]const u8 { "-abc", "100" }, []u8{0,1,2}, []?[]const u8{null,null,"100"});
482 testNoErr(params, [][]const u8 { "-abc=100" }, []u8{0,1,2}, []?[]const u8{null,null,"100"});
483 testNoErr(params, [][]const u8 { "-abc100" }, []u8{0,1,2}, []?[]const u8{null,null,"100"});
484 testNoErr(params, [][]const u8 { "--aa" }, []u8{0}, []?[]const u8{null});
485 testNoErr(params, [][]const u8 { "--aa", "--bb" }, []u8{0,1}, []?[]const u8{null,null});
486 testNoErr(params, [][]const u8 { "--cc=100" }, []u8{2}, []?[]const u8{"100"});
487 testNoErr(params, [][]const u8 { "--cc", "100" }, []u8{2}, []?[]const u8{"100"});
488 testNoErr(params, [][]const u8 { "aa" }, []u8{0}, []?[]const u8{null});
489 testNoErr(params, [][]const u8 { "aa", "bb" }, []u8{0,1}, []?[]const u8{null,null});
490 testNoErr(params, [][]const u8 { "cc=100" }, []u8{2}, []?[]const u8{"100"});
491 testNoErr(params, [][]const u8 { "cc", "100" }, []u8{2}, []?[]const u8{"100"});
492 testNoErr(params, [][]const u8 { "dd" }, []u8{3}, []?[]const u8{"dd"});
493}
diff --git a/src/extended.zig b/src/extended.zig
new file mode 100644
index 0000000..9427b83
--- /dev/null
+++ b/src/extended.zig
@@ -0,0 +1,264 @@
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
13const Opaque = @OpaqueType();
14
15pub const Param = struct {
16 field: []const u8,
17 short: ?u8,
18 long: ?[]const u8,
19 takes_value: ?Parser,
20 required: bool,
21 position: ?usize,
22
23 pub fn short(s: u8) Param {
24 return Param{
25 .field = []u8{s},
26 .short = s,
27 .long = null,
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};
77
78pub const Command = struct {
79 field: []const u8,
80 name: []const u8,
81 params: []const Param,
82 sub_commands: []const Command,
83
84 Result: type,
85 defaults: &const Opaque,
86 parent: ?&const Command,
87
88 pub fn init(name: []const u8, comptime Result: type, defaults: &const Result, params: []const Param, sub_commands: []const Command) Command {
89 return Command{
90 .field = name,
91 .name = name,
92 .params = params,
93 .sub_commands = sub_commands,
94 .Result = Result,
95 .defaults = @ptrCast(&const Opaque, defaults),
96 .parent = null,
97 };
98 }
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: &core.ArgIterator) !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: &core.ArgIterator) !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 iter = core.Clap(usize).init(core_params, arg_iter, allocator);
163 defer iter.deinit();
164
165 arg_loop:
166 while (try iter.next()) |arg| : (pos += 1) {
167 inline for(command.params) |param, i| {
168 comptime const field = "result." ++ param.field;
169
170 if (arg.id == i and (param.position ?? pos) == pos) {
171 if (param.takes_value) |parser| {
172 try parser.parse(getFieldPtr(&result, field), ??arg.value);
173 } else {
174 getFieldPtr(&result, field).* = true;
175 }
176 handled[i] = true;
177 continue :arg_loop;
178 }
179 }
180
181 inline for(command.sub_commands) |c, i| {
182 comptime const field = "result." ++ c.field;
183 comptime var sub_command = c;
184 sub_command.parent = command;
185
186 if (arg.id == i + command.params.len) {
187 getFieldPtr(&result, field).* = try sub_command.parseHelper(&result, allocator, arg_iter);
188 continue :arg_loop;
189 }
190 }
191
192 return error.InvalidArgument;
193 }
194
195 return result.result;
196 }
197
198 fn GetFieldPtrReturn(comptime Struct: type, comptime field: []const u8) type {
199 var inst: Struct = undefined;
200 const dot_index = comptime mem.indexOfScalar(u8, field, '.') ?? {
201 return @typeOf(&@field(inst, field));
202 };
203
204 return GetFieldPtrReturn(@typeOf(@field(inst, field[0..dot_index])), field[dot_index + 1..]);
205 }
206
207 fn getFieldPtr(curr: var, comptime field: []const u8) GetFieldPtrReturn(@typeOf(curr).Child, field) {
208 const dot_index = comptime mem.indexOfScalar(u8, field, '.') ?? {
209 return &@field(curr, field);
210 };
211
212 return getFieldPtr(&@field(curr, field[0..dot_index]), field[dot_index + 1..]);
213 }
214};
215
216pub const Parser = struct {
217 const UnsafeFunction = &const void;
218
219 FieldType: type,
220 Errors: type,
221 func: UnsafeFunction,
222
223 pub fn init(comptime FieldType: type, comptime Errors: type, func: parseFunc(FieldType, Errors)) Parser {
224 return Parser {
225 .FieldType = FieldType,
226 .Errors = Errors,
227 .func = @ptrCast(UnsafeFunction, func),
228 };
229 }
230
231 fn parse(comptime parser: Parser, field_ptr: TakePtr(parser.FieldType), arg: []const u8) parser.Errors!void {
232 return @ptrCast(parseFunc(parser.FieldType, parser.Errors), parser.func)(field_ptr, arg);
233 }
234
235 // TODO: This is a workaround, since we don't have pointer reform yet.
236 fn TakePtr(comptime T: type) type { return &T; }
237
238 fn parseFunc(comptime FieldType: type, comptime Errors: type) type {
239 return fn(&FieldType, []const u8) Errors!void;
240 }
241
242 pub fn int(comptime Int: type, comptime radix: u8) Parser {
243 const func = struct {
244 fn i(field_ptr: &Int, arg: []const u8) !void {
245 field_ptr.* = try fmt.parseInt(Int, arg, radix);
246 }
247 }.i;
248 return Parser.init(
249 Int,
250 @typeOf(func).ReturnType.ErrorSet,
251 func
252 );
253 }
254
255 const string = Parser.init(
256 []const u8,
257 error{},
258 struct {
259 fn s(field_ptr: &[]const u8, arg: []const u8) (error{}!void) {
260 field_ptr.* = arg;
261 }
262 }.s
263 );
264};
diff --git a/tests/core.zig b/tests/core.zig
new file mode 100644
index 0000000..a6a705e
--- /dev/null
+++ b/tests/core.zig
@@ -0,0 +1,134 @@
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).init(params, &arg_iter.iter, debug.global_allocator);
18
19 var i: usize = 0;
20 while (iter.next() catch unreachable) |arg| : (i += 1) {
21 debug.assert(ids[i] == arg.id);
22 const expected_value = values[i] ?? {
23 debug.assert(arg.value == null);
24 continue;
25 };
26 const actual_value = arg.value ?? 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) {
78 Param(u8).init(0, true, Names.none()),
79 };
80
81 testNoErr(params, [][]const u8 { "aa" }, []u8{0}, []?[]const u8{"aa"});
82}
83
84test "clap.core: all" {
85 const params = []Param(u8) {
86 Param(u8).init(
87 0,
88 false,
89 Names{
90 .bare = "aa",
91 .short = 'a',
92 .long = "aa",
93 }
94 ),
95 Param(u8).init(
96 1,
97 false,
98 Names{
99 .bare = "bb",
100 .short = 'b',
101 .long = "bb",
102 }
103 ),
104 Param(u8).init(
105 2,
106 true,
107 Names{
108 .bare = "cc",
109 .short = 'c',
110 .long = "cc",
111 }
112 ),
113 Param(u8).init(3, true, Names.none()),
114 };
115
116 testNoErr(params, [][]const u8 { "-a" }, []u8{0}, []?[]const u8{null});
117 testNoErr(params, [][]const u8 { "-a", "-b" }, []u8{0,1}, []?[]const u8{null,null});
118 testNoErr(params, [][]const u8 { "-ab" }, []u8{0,1}, []?[]const u8{null,null});
119 testNoErr(params, [][]const u8 { "-c=100" }, []u8{2}, []?[]const u8{"100"});
120 testNoErr(params, [][]const u8 { "-c100" }, []u8{2}, []?[]const u8{"100"});
121 testNoErr(params, [][]const u8 { "-c", "100" }, []u8{2}, []?[]const u8{"100"});
122 testNoErr(params, [][]const u8 { "-abc", "100" }, []u8{0,1,2}, []?[]const u8{null,null,"100"});
123 testNoErr(params, [][]const u8 { "-abc=100" }, []u8{0,1,2}, []?[]const u8{null,null,"100"});
124 testNoErr(params, [][]const u8 { "-abc100" }, []u8{0,1,2}, []?[]const u8{null,null,"100"});
125 testNoErr(params, [][]const u8 { "--aa" }, []u8{0}, []?[]const u8{null});
126 testNoErr(params, [][]const u8 { "--aa", "--bb" }, []u8{0,1}, []?[]const u8{null,null});
127 testNoErr(params, [][]const u8 { "--cc=100" }, []u8{2}, []?[]const u8{"100"});
128 testNoErr(params, [][]const u8 { "--cc", "100" }, []u8{2}, []?[]const u8{"100"});
129 testNoErr(params, [][]const u8 { "aa" }, []u8{0}, []?[]const u8{null});
130 testNoErr(params, [][]const u8 { "aa", "bb" }, []u8{0,1}, []?[]const u8{null,null});
131 testNoErr(params, [][]const u8 { "cc=100" }, []u8{2}, []?[]const u8{"100"});
132 testNoErr(params, [][]const u8 { "cc", "100" }, []u8{2}, []?[]const u8{"100"});
133 testNoErr(params, [][]const u8 { "dd" }, []u8{3}, []?[]const u8{"dd"});
134}
diff --git a/tests/extended.zig b/tests/extended.zig
new file mode 100644
index 0000000..8f722e4
--- /dev/null
+++ b/tests/extended.zig
@@ -0,0 +1,234 @@
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 Command = extended.Command;
13const Param = extended.Param;
14const Parser = extended.Parser;
15
16const Options = struct {
17 str: []const u8,
18 int: i64,
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
37 pub fn with(op: &const SubOptions, comptime field: []const u8, value: var) SubOptions {
38 var res = op.*;
39 @field(res, field) = value;
40 return res;
41 }
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}
70
71fn testErr(comptime command: &const Command, args: []const []const u8, expected: error) void {
72 var arg_iter = ArgSliceIterator.init(args);
73 if (command.parse(debug.global_allocator, &arg_iter.iter)) |actual| {
74 unreachable;
75 } else |err| {
76 assert(err == expected);
77 }
78}
79
80test "clap.extended: short" {
81 const command = comptime Command.init(
82 "",
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
106test "clap.extended: long" {
107 const command = comptime Command.init(
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
120 testNoErr(command, [][]const u8 { "--cc" }, default.with("cc", true));
121 testNoErr(command, [][]const u8 { "--int", "100" }, default.with("int", 100));
122}
123
124test "clap.extended: value bool" {
125 const command = comptime Command.init(
126 "",
127 Options,
128 default,
129 []Param {
130 Param.smart("a"),
131 },
132 []Command{},
133 );
134
135 testNoErr(command, [][]const u8 { "-a" }, default.with("a", true));
136}
137
138test "clap.extended: value str" {
139 const command = comptime Command.init(
140 "",
141 Options,
142 default,
143 []Param {
144 Param.smart("str").with("takes_value", Parser.string),
145 },
146 []Command{},
147 );
148
149 testNoErr(command, [][]const u8 { "--str", "Hello World!" }, default.with("str", "Hello World!"));
150}
151
152test "clap.extended: value int" {
153 const command = comptime Command.init(
154 "",
155 Options,
156 default,
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
166test "clap.extended: position" {
167 const command = comptime Command.init(
168 "",
169 Options,
170 default,
171 []Param {
172 Param.smart("a").with("position", 0),
173 Param.smart("b").with("position", 1),
174 },
175 []Command{},
176 );
177
178 testNoErr(command, [][]const u8 { "-a", "-b" }, default.with("a", true).with("b", true));
179 testErr(command, [][]const u8 { "-b", "-a" }, error.InvalidArgument);
180}
181
182test "clap.extended: sub fields" {
183 const B = struct {
184 a: bool,
185 };
186 const A = struct {
187 b: B,
188 };
189
190 const command = comptime Command.init(
191 "",
192 A,
193 A { .b = B { .a = false } },
194 []Param {
195 Param.short('a')
196 .with("field", "b.a"),
197 },
198 []Command{},
199 );
200
201 var arg_iter = ArgSliceIterator.init([][]const u8{ "-a" });
202 const res = command.parse(debug.global_allocator, &arg_iter.iter) catch unreachable;
203 debug.assert(res.b.a == true);
204}
205
206test "clap.extended: sub commands" {
207 const command = comptime Command.init(
208 "",
209 Options,
210 default,
211 []Param {
212 Param.smart("a"),
213 Param.smart("b"),
214 },
215 []Command{
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 },
228 );
229
230 testNoErr(command, [][]const u8 { "sub", "-a" }, default.with("sub", default.sub.with("a", true)));
231 testNoErr(command, [][]const u8 { "sub", "-b", "100" }, default.with("sub", default.sub.with("b", 100)));
232 testNoErr(command, [][]const u8 { "-a", "sub", "-a" }, default.with("a", true).with("sub", default.sub.with("a", true)));
233 testErr(command, [][]const u8 { "-qq", "sub" }, error.InvalidArgument);
234}