summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Jimmi Holst Christensen2018-11-14 14:06:20 +0100
committerGravatar Jimmi Holst Christensen2018-11-14 14:06:20 +0100
commitc564b168785e740c37f47da4942825b25cb8b4ec (patch)
tree880252c7a63bdc91f23ba5e13b593d4ca9ad8277
parentZig fmt (diff)
downloadzig-clap-c564b168785e740c37f47da4942825b25cb8b4ec.tar.gz
zig-clap-c564b168785e740c37f47da4942825b25cb8b4ec.tar.xz
zig-clap-c564b168785e740c37f47da4942825b25cb8b4ec.zip
Restructured and make StreamingClap simpler
* Also added a ComptimeClap
Diffstat (limited to '')
-rw-r--r--.travis.yml5
-rw-r--r--README.md100
-rw-r--r--build.zig48
-rw-r--r--clap.zig376
-rw-r--r--example/comptime-clap.zig47
-rw-r--r--example/streaming-clap.zig54
-rw-r--r--src/args.zig90
-rw-r--r--src/comptime.zig142
-rw-r--r--src/index.zig105
-rw-r--r--src/streaming.zig338
-rw-r--r--test.zig245
11 files changed, 921 insertions, 629 deletions
diff --git a/.travis.yml b/.travis.yml
index 48e7273..b4adebe 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -8,7 +8,4 @@ git:
8 depth: false 8 depth: false
9script: 9script:
10 - zig-linux-*/zig version 10 - zig-linux-*/zig version
11 - zig-linux-*/zig test test.zig 11 - zig-linux-*/zig build
12 - zig-linux-*/zig test test.zig --release-fast
13 - zig-linux-*/zig test test.zig --release-safe
14 - zig-linux-*/zig test test.zig --release-small
diff --git a/README.md b/README.md
index f4c3766..92f53c2 100644
--- a/README.md
+++ b/README.md
@@ -1,17 +1,109 @@
1# zig-clap 1# zig-clap
2 2
3A simple and easy to use command line argument parser library for Zig. 3A simple and easy to use command line argument parser library for Zig.
4It's ment as a thin layer of abstraction over parsing arguments which allows
5for further abstraction on top, such as filling in a `HashMap`.
6 4
7## Features 5## Features
8 6
9See [example](https://github.com/Hejsil/zig-clap/blob/38a51948069f405864ab327826b5975a6d0c93a8/test.zig#L200-L247).
10* Short arguments `-a` 7* Short arguments `-a`
11 * Chaining `-abc` where `a` and `b` does not take values. 8 * Chaining `-abc` where `a` and `b` does not take values.
12* Long arguments `--long` 9* Long arguments `--long`
13* Bare arguments `bare`
14* Supports both passing values using spacing and `=` (`-a 100`, `-a=100`) 10* Supports both passing values using spacing and `=` (`-a 100`, `-a=100`)
15 * Short args also support passing values with no spacing or `=` (`-a100`) 11 * Short args also support passing values with no spacing or `=` (`-a100`)
16 * This all works with chaining (`-ba 100`, `-ba=100`, `-ba100`) 12 * This all works with chaining (`-ba 100`, `-ba=100`, `-ba100`)
17 13
14## Examples
15
16### `StreamingClap`
17
18The `StreamingClap` is base of all the other parsers. It's a streaming parser that uses an
19`args.Iterator` to provide it with arguments lazily.
20
21```rust
22const params = []clap.Param(u8){
23 clap.Param(u8).init('h', false, clap.Names.prefix("help")),
24 clap.Param(u8).init('n', true, clap.Names.prefix("number")),
25 clap.Param(u8).init('f', true, clap.Names.positional()),
26};
27
28var os_iter = clap.args.OsIterator.init(allocator);
29const iter = &os_iter.iter;
30defer os_iter.deinit();
31
32const exe = try iter.next();
33
34var parser = clap.StreamingClap(u8, clap.args.OsIterator.Error).init(params, iter);
35
36while (try parser.next()) |arg| {
37 switch (arg.param.id) {
38 'h' => debug.warn("Help!\n"),
39 'n' => debug.warn("--number = {}\n", arg.value.?),
40 'f' => debug.warn("{}\n", arg.value.?),
41 else => unreachable,
42 }
43 }
44```
45
46### `ComptimeClap`
47
48The `ComptimeClap` is a wrapper for `StreamingClap`, which parses all the arguments and makes
49them available through three functions (`flag`, `option`, `positionals`).
50
51```rust
52const params = comptime []clap.Param(void){
53 clap.Param(void).init({}, false, clap.Names.prefix("help")),
54 clap.Param(void).init({}, true, clap.Names.prefix("number")),
55 clap.Param(void).init({}, true, clap.Names.positional()),
56};
57
58var os_iter = clap.args.OsIterator.init(allocator);
59const iter = &os_iter.iter;
60defer os_iter.deinit();
61
62const exe = try iter.next();
63
64var args = try clap.ComptimeClap(void, params).parse(allocator, clap.args.OsIterator.Error, iter);
65defer args.deinit();
66
67if (args.flag("--help"))
68 debug.warn("Help!\n");
69if (args.option("--number")) |n|
70 debug.warn("--number = {}\n", n);
71for (args.positionals()) |pos|
72 debug.warn("{}\n", pos);
73```
74
75The data structure returned from this parser has lookup speed on par with array access (`arr[i]`)
76and validates that the strings you pass to `option` and `flag` are actually parameters that the
77program can take:
78
79```rust
80const params = comptime []clap.Param(void){
81 clap.Param(void).init({}, false, clap.Names.prefix("help")),
82};
83
84var os_iter = clap.args.OsIterator.init(allocator);
85const iter = &os_iter.iter;
86defer os_iter.deinit();
87
88const exe = try iter.next();
89
90var args = try clap.ComptimeClap(params).parse(allocator, clap.args.OsIterator.Error, iter);
91defer args.deinit();
92
93if (args.flag("--helps"))
94 debug.warn("Help!\n");
95```
96
97```
98zig-clap/src/comptime.zig:103:17: error: --helps is not a parameter.
99 @compileError(name ++ " is not a parameter.");
100 ^
101zig-clap/src/comptime.zig:71:45: note: called from here
102 const param = comptime findParam(name);
103 ^
104zig-clap/example/comptime-clap.zig:41:18: note: called from here
105 if (args.flag("--helps"))
106 ^
107```
108
109Ofc, this limits you to use only parameters that are comptime known.
diff --git a/build.zig b/build.zig
new file mode 100644
index 0000000..06c012b
--- /dev/null
+++ b/build.zig
@@ -0,0 +1,48 @@
1const builtin = @import("builtin");
2const std = @import("std");
3
4const Mode = builtin.Mode;
5const Builder = std.build.Builder;
6
7pub fn build(b: *Builder) void {
8 const mode = b.standardReleaseOptions();
9
10 const example_step = b.step("examples", "Build examples");
11 inline for ([][]const u8{
12 "comptime-clap",
13 "streaming-clap",
14 }) |example_name| {
15 const example = b.addExecutable(example_name, "example/" ++ example_name ++ ".zig");
16 example.addPackagePath("clap", "src/index.zig");
17 example.setBuildMode(mode);
18 example_step.dependOn(&example.step);
19 }
20
21 const test_all_step = b.step("test", "Run all tests in all modes.");
22 inline for ([]Mode{ Mode.Debug, Mode.ReleaseFast, Mode.ReleaseSafe, Mode.ReleaseSmall }) |test_mode| {
23 const mode_str = comptime modeToString(test_mode);
24
25 const tests = b.addTest("src/index.zig");
26 tests.setBuildMode(test_mode);
27 tests.setNamePrefix(mode_str ++ " ");
28
29 const test_step = b.step("test-" ++ mode_str, "Run all tests in " ++ mode_str ++ ".");
30 test_step.dependOn(&tests.step);
31 test_all_step.dependOn(test_step);
32 }
33
34 const all_step = b.step("all", "Build everything and runs all tests");
35 all_step.dependOn(test_all_step);
36 all_step.dependOn(example_step);
37
38 b.default_step.dependOn(all_step);
39}
40
41fn modeToString(mode: Mode) []const u8 {
42 return switch (mode) {
43 Mode.Debug => "debug",
44 Mode.ReleaseFast => "release-fast",
45 Mode.ReleaseSafe => "release-safe",
46 Mode.ReleaseSmall => "release-small",
47 };
48}
diff --git a/clap.zig b/clap.zig
deleted file mode 100644
index a2d30a5..0000000
--- a/clap.zig
+++ /dev/null
@@ -1,376 +0,0 @@
1const std = @import("std");
2const builtin = @import("builtin");
3
4const os = std.os;
5const heap = std.heap;
6const mem = std.mem;
7const debug = std.debug;
8
9/// The names a ::Param can have.
10pub const Names = struct {
11 /// No prefix
12 bare: ?[]const u8,
13
14 /// '-' prefix
15 short: ?u8,
16
17 /// '--' prefix
18 long: ?[]const u8,
19
20 /// Initializes no names
21 pub fn none() Names {
22 return Names{
23 .bare = null,
24 .short = null,
25 .long = null,
26 };
27 }
28
29 /// Initializes a bare name
30 pub fn bare(b: []const u8) Names {
31 return Names{
32 .bare = b,
33 .short = null,
34 .long = null,
35 };
36 }
37
38 /// Initializes a short name
39 pub fn short(s: u8) Names {
40 return Names{
41 .bare = null,
42 .short = s,
43 .long = null,
44 };
45 }
46
47 /// Initializes a long name
48 pub fn long(l: []const u8) Names {
49 return Names{
50 .bare = null,
51 .short = null,
52 .long = l,
53 };
54 }
55
56 /// Initializes a name with a prefix.
57 /// ::short is set to ::name[0], and ::long is set to ::name.
58 /// This function asserts that ::name.len != 0
59 pub fn prefix(name: []const u8) Names {
60 debug.assert(name.len != 0);
61
62 return Names{
63 .bare = null,
64 .short = name[0],
65 .long = name,
66 };
67 }
68};
69
70/// Represents a parameter for the command line.
71/// Parameters come in three kinds:
72/// * Short ("-a"): Should be used for the most commonly used parameters in your program.
73/// * They can take a value three different ways.
74/// * "-a value"
75/// * "-a=value"
76/// * "-avalue"
77/// * They chain if they don't take values: "-abc".
78/// * The last given parameter can take a value in the same way that a single parameter can:
79/// * "-abc value"
80/// * "-abc=value"
81/// * "-abcvalue"
82/// * Long ("--long-param"): Should be used for less common parameters, or when no single character
83/// can describe the paramter.
84/// * They can take a value two different ways.
85/// * "--long-param value"
86/// * "--long-param=value"
87/// * Bare ("bare"): Should be used as for sub-commands and other keywords.
88/// * They can take a value two different ways.
89/// * "command value"
90/// * "command=value"
91/// * Value ("value"): Should be used as the primary parameter of the program, like a filename or
92/// an expression to parse.
93/// * Value parameters must take a value.
94pub fn Param(comptime Id: type) type {
95 return struct {
96 const Self = @This();
97
98 id: Id,
99 takes_value: bool,
100 names: Names,
101
102 pub fn init(id: Id, takes_value: bool, names: Names) Self {
103 // Assert, that if the param have no name, then it has to take
104 // a value.
105 debug.assert(names.bare != null or
106 names.long != null or
107 names.short != null or
108 takes_value);
109
110 return Self{
111 .id = id,
112 .takes_value = takes_value,
113 .names = names,
114 };
115 }
116 };
117}
118
119/// The result returned from ::StreamingClap.next
120pub fn Arg(comptime Id: type) type {
121 return struct {
122 const Self = @This();
123
124 param: *const Param(Id),
125 value: ?[]const u8,
126
127 pub fn init(param: *const Param(Id), value: ?[]const u8) Self {
128 return Self{
129 .param = param,
130 .value = value,
131 };
132 }
133 };
134}
135
136/// A interface for iterating over command line arguments
137pub fn ArgIterator(comptime E: type) type {
138 return struct {
139 const Self = @This();
140 const Error = E;
141
142 nextFn: fn (iter: *Self) Error!?[]const u8,
143
144 pub fn next(iter: *Self) Error!?[]const u8 {
145 return iter.nextFn(iter);
146 }
147 };
148}
149
150/// An ::ArgIterator, which iterates over a slice of arguments.
151/// This implementation does not allocate.
152pub const ArgSliceIterator = struct {
153 const Error = error{};
154
155 args: []const []const u8,
156 index: usize,
157 iter: ArgIterator(Error),
158
159 pub fn init(args: []const []const u8) ArgSliceIterator {
160 return ArgSliceIterator{
161 .args = args,
162 .index = 0,
163 .iter = ArgIterator(Error){ .nextFn = nextFn },
164 };
165 }
166
167 fn nextFn(iter: *ArgIterator(Error)) Error!?[]const u8 {
168 const self = @fieldParentPtr(ArgSliceIterator, "iter", iter);
169 if (self.args.len <= self.index)
170 return null;
171
172 defer self.index += 1;
173 return self.args[self.index];
174 }
175};
176
177/// An ::ArgIterator, which wraps the ArgIterator in ::std.
178/// On windows, this iterator allocates.
179pub const OsArgIterator = struct {
180 const Error = os.ArgIterator.NextError;
181
182 arena: heap.ArenaAllocator,
183 args: os.ArgIterator,
184 iter: ArgIterator(Error),
185
186 pub fn init(allocator: *mem.Allocator) OsArgIterator {
187 return OsArgIterator{
188 .arena = heap.ArenaAllocator.init(allocator),
189 .args = os.args(),
190 .iter = ArgIterator(Error){ .nextFn = nextFn },
191 };
192 }
193
194 pub fn deinit(iter: *OsArgIterator) void {
195 iter.arena.deinit();
196 }
197
198 fn nextFn(iter: *ArgIterator(Error)) Error!?[]const u8 {
199 const self = @fieldParentPtr(OsArgIterator, "iter", iter);
200 if (builtin.os == builtin.Os.windows) {
201 return try self.args.next(&self.arena.allocator) orelse return null;
202 } else {
203 return self.args.nextPosix();
204 }
205 }
206};
207
208/// A command line argument parser which, given an ::ArgIterator, will parse arguments according
209/// to the ::params. ::StreamingClap parses in an iterating manner, so you have to use a loop together with
210/// ::StreamingClap.next to parse all the arguments of your program.
211pub fn StreamingClap(comptime Id: type, comptime ArgError: type) type {
212 return struct {
213 const Self = @This();
214
215 const State = union(enum) {
216 Normal,
217 Chaining: Chaining,
218
219 const Chaining = struct {
220 arg: []const u8,
221 index: usize,
222 };
223 };
224
225 params: []const Param(Id),
226 iter: *ArgIterator(ArgError),
227 state: State,
228
229 pub fn init(params: []const Param(Id), iter: *ArgIterator(ArgError)) Self {
230 var res = Self{
231 .params = params,
232 .iter = iter,
233 .state = State.Normal,
234 };
235
236 return res;
237 }
238
239 /// Get the next ::Arg that matches a ::Param.
240 pub fn next(clap: *Self) !?Arg(Id) {
241 const ArgInfo = struct {
242 const Kind = enum {
243 Long,
244 Short,
245 Bare,
246 };
247
248 arg: []const u8,
249 kind: Kind,
250 };
251
252 switch (clap.state) {
253 State.Normal => {
254 const full_arg = (try clap.iter.next()) orelse return null;
255 const arg_info = blk: {
256 var arg = full_arg;
257 var kind = ArgInfo.Kind.Bare;
258
259 if (mem.startsWith(u8, arg, "--")) {
260 arg = arg[2..];
261 kind = ArgInfo.Kind.Long;
262 } else if (mem.startsWith(u8, arg, "-")) {
263 arg = arg[1..];
264 kind = ArgInfo.Kind.Short;
265 }
266
267 // We allow long arguments to go without a name.
268 // This allows the user to use "--" for something important
269 if (kind != ArgInfo.Kind.Long and arg.len == 0)
270 return error.InvalidArgument;
271
272 break :blk ArgInfo{ .arg = arg, .kind = kind };
273 };
274
275 const arg = arg_info.arg;
276 const kind = arg_info.kind;
277 const eql_index = mem.indexOfScalar(u8, arg, '=');
278
279 switch (kind) {
280 ArgInfo.Kind.Bare, ArgInfo.Kind.Long => {
281 for (clap.params) |*param| {
282 const match = switch (kind) {
283 ArgInfo.Kind.Bare => param.names.bare orelse continue,
284 ArgInfo.Kind.Long => param.names.long orelse continue,
285 else => unreachable,
286 };
287 const name = if (eql_index) |i| arg[0..i] else arg;
288 const maybe_value = if (eql_index) |i| arg[i + 1 ..] else null;
289
290 if (!mem.eql(u8, name, match))
291 continue;
292 if (!param.takes_value) {
293 if (maybe_value != null)
294 return error.DoesntTakeValue;
295
296 return Arg(Id).init(param, null);
297 }
298
299 const value = blk: {
300 if (maybe_value) |v|
301 break :blk v;
302
303 break :blk (try clap.iter.next()) orelse return error.MissingValue;
304 };
305
306 return Arg(Id).init(param, value);
307 }
308 },
309 ArgInfo.Kind.Short => {
310 return try clap.chainging(State.Chaining{
311 .arg = full_arg,
312 .index = (full_arg.len - arg.len),
313 });
314 },
315 }
316
317 // We do a final pass to look for value parameters matches
318 if (kind == ArgInfo.Kind.Bare) {
319 for (clap.params) |*param| {
320 if (param.names.bare) |_| continue;
321 if (param.names.short) |_| continue;
322 if (param.names.long) |_| continue;
323
324 return Arg(Id).init(param, arg);
325 }
326 }
327
328 return error.InvalidArgument;
329 },
330 @TagType(State).Chaining => |state| return try clap.chainging(state),
331 }
332 }
333
334 fn chainging(clap: *Self, state: State.Chaining) !?Arg(Id) {
335 const arg = state.arg;
336 const index = state.index;
337 const next_index = index + 1;
338
339 for (clap.params) |*param| {
340 const short = param.names.short orelse continue;
341 if (short != arg[index])
342 continue;
343
344 // Before we return, we have to set the new state of the clap
345 defer {
346 if (arg.len <= next_index or param.takes_value) {
347 clap.state = State.Normal;
348 } else {
349 clap.state = State{
350 .Chaining = State.Chaining{
351 .arg = arg,
352 .index = next_index,
353 },
354 };
355 }
356 }
357
358 if (!param.takes_value)
359 return Arg(Id).init(param, null);
360
361 if (arg.len <= next_index) {
362 const value = (try clap.iter.next()) orelse return error.MissingValue;
363 return Arg(Id).init(param, value);
364 }
365
366 if (arg[next_index] == '=') {
367 return Arg(Id).init(param, arg[next_index + 1 ..]);
368 }
369
370 return Arg(Id).init(param, arg[next_index..]);
371 }
372
373 return error.InvalidArgument;
374 }
375 };
376}
diff --git a/example/comptime-clap.zig b/example/comptime-clap.zig
new file mode 100644
index 0000000..e44d9b1
--- /dev/null
+++ b/example/comptime-clap.zig
@@ -0,0 +1,47 @@
1const std = @import("std");
2const clap = @import("clap");
3
4const debug = std.debug;
5
6pub fn main() !void {
7 var direct_allocator = std.heap.DirectAllocator.init();
8 const allocator = &direct_allocator.allocator;
9 defer direct_allocator.deinit();
10
11 // First we specify what parameters our program can take.
12 const params = comptime []clap.Param(void){
13 // Param.init takes 3 arguments.
14 // * An "id", which can be any type specified by the argument to Param. The
15 // ComptimeClap expects clap.Param(void) only.
16 // * A bool which determins wether the parameter takes a value.
17 // * A "Names" struct, which determins what names the parameter will have on the
18 // commandline. Names.prefix inits a "Names" struct that has the "short" name
19 // set to the first letter, and the "long" name set to the full name.
20 clap.Param(void).init({}, false, clap.Names.prefix("help")),
21 clap.Param(void).init({}, true, clap.Names.prefix("number")),
22
23 // Names.positional returns a "Names" struct where neither the "short" or "long"
24 // name is set.
25 clap.Param(void).init({}, true, clap.Names.positional()),
26 };
27
28 // We then initialize an argument iterator. We will use the OsIterator as it nicely
29 // wraps iterating over arguments the most efficient way on each os.
30 var os_iter = clap.args.OsIterator.init(allocator);
31 const iter = &os_iter.iter;
32 defer os_iter.deinit();
33
34 // Consume the exe arg.
35 const exe = try iter.next();
36
37 // Finally we can parse the arguments
38 var args = try clap.ComptimeClap(void, params).parse(allocator, clap.args.OsIterator.Error, iter);
39 defer args.deinit();
40
41 if (args.flag("--help"))
42 debug.warn("Help!\n");
43 if (args.option("--number")) |n|
44 debug.warn("--number = {}\n", n);
45 for (args.positionals()) |pos|
46 debug.warn("{}\n", pos);
47}
diff --git a/example/streaming-clap.zig b/example/streaming-clap.zig
new file mode 100644
index 0000000..0dc2bf7
--- /dev/null
+++ b/example/streaming-clap.zig
@@ -0,0 +1,54 @@
1const std = @import("std");
2const clap = @import("clap");
3
4const debug = std.debug;
5
6pub fn main() !void {
7 var direct_allocator = std.heap.DirectAllocator.init();
8 const allocator = &direct_allocator.allocator;
9 defer direct_allocator.deinit();
10
11 // First we specify what parameters our program can take.
12 const params = []clap.Param(u8){
13 // Param.init takes 3 arguments.
14 // * An "id", which can be any type specified by the argument to Param. Here, we
15 // use a "u8" as the "id" type.
16 // * A bool which determins wether the parameter takes a value.
17 // * A "Names" struct, which determins what names the parameter will have on the
18 // commandline. Names.prefix inits a "Names" struct that has the "short" name
19 // set to the first letter, and the "long" name set to the full name.
20 clap.Param(u8).init('h', false, clap.Names.prefix("help")),
21 clap.Param(u8).init('n', true, clap.Names.prefix("number")),
22
23 // Names.positional returns a "Names" struct where neither the "short" or "long"
24 // name is set.
25 clap.Param(u8).init('f', true, clap.Names.positional()),
26 };
27
28 // We then initialize an argument iterator. We will use the OsIterator as it nicely
29 // wraps iterating over arguments the most efficient way on each os.
30 var os_iter = clap.args.OsIterator.init(allocator);
31 const iter = &os_iter.iter;
32 defer os_iter.deinit();
33
34 // Consume the exe arg.
35 const exe = try iter.next();
36
37 // Finally we initialize our streaming parser.
38 var parser = clap.StreamingClap(u8, clap.args.OsIterator.Error).init(params, iter);
39
40 // Because we use a streaming parser, we have to consume each argument parsed individually.
41 while (try parser.next()) |arg| {
42 // arg.param will point to the parameter which matched the argument.
43 switch (arg.param.id) {
44 'h' => debug.warn("Help!\n"),
45 'n' => debug.warn("--number = {}\n", arg.value.?),
46
47 // arg.value == null, if arg.param.takes_value == false.
48 // Otherwise, arg.value is the value passed with the argument, such as "-a=10"
49 // or "-a 10".
50 'f' => debug.warn("{}\n", arg.value.?),
51 else => unreachable,
52 }
53 }
54}
diff --git a/src/args.zig b/src/args.zig
new file mode 100644
index 0000000..304596c
--- /dev/null
+++ b/src/args.zig
@@ -0,0 +1,90 @@
1const builtin = @import("builtin");
2const std = @import("std");
3
4const debug = std.debug;
5const heap = std.heap;
6const mem = std.mem;
7const os = std.os;
8
9/// A interface for iterating over command line arguments
10pub fn Iterator(comptime E: type) type {
11 return struct {
12 const Self = @This();
13 const Error = E;
14
15 nextFn: fn (iter: *Self) Error!?[]const u8,
16
17 pub fn next(iter: *Self) Error!?[]const u8 {
18 return iter.nextFn(iter);
19 }
20 };
21}
22
23/// An ::ArgIterator, which iterates over a slice of arguments.
24/// This implementation does not allocate.
25pub const SliceIterator = struct {
26 const Error = error{};
27
28 args: []const []const u8,
29 index: usize,
30 iter: Iterator(Error),
31
32 pub fn init(args: []const []const u8) SliceIterator {
33 return SliceIterator{
34 .args = args,
35 .index = 0,
36 .iter = Iterator(Error){ .nextFn = nextFn },
37 };
38 }
39
40 fn nextFn(iter: *Iterator(Error)) Error!?[]const u8 {
41 const self = @fieldParentPtr(SliceIterator, "iter", iter);
42 if (self.args.len <= self.index)
43 return null;
44
45 defer self.index += 1;
46 return self.args[self.index];
47 }
48};
49
50test "clap.args.SliceIterator" {
51 const args = [][]const u8{ "A", "BB", "CCC" };
52 var slice_iter = SliceIterator.init(args);
53 const iter = &slice_iter.iter;
54
55 for (args) |a| {
56 const b = try iter.next();
57 debug.assert(mem.eql(u8, a, b.?));
58 }
59}
60
61/// An ::ArgIterator, which wraps the ArgIterator in ::std.
62/// On windows, this iterator allocates.
63pub const OsIterator = struct {
64 const Error = os.ArgIterator.NextError;
65
66 arena: heap.ArenaAllocator,
67 args: os.ArgIterator,
68 iter: Iterator(Error),
69
70 pub fn init(allocator: *mem.Allocator) OsIterator {
71 return OsIterator{
72 .arena = heap.ArenaAllocator.init(allocator),
73 .args = os.args(),
74 .iter = Iterator(Error){ .nextFn = nextFn },
75 };
76 }
77
78 pub fn deinit(iter: *OsIterator) void {
79 iter.arena.deinit();
80 }
81
82 fn nextFn(iter: *Iterator(Error)) Error!?[]const u8 {
83 const self = @fieldParentPtr(OsIterator, "iter", iter);
84 if (builtin.os == builtin.Os.windows) {
85 return try self.args.next(&self.arena.allocator) orelse return null;
86 } else {
87 return self.args.nextPosix();
88 }
89 }
90};
diff --git a/src/comptime.zig b/src/comptime.zig
new file mode 100644
index 0000000..b11dccc
--- /dev/null
+++ b/src/comptime.zig
@@ -0,0 +1,142 @@
1const clap = @import("index.zig");
2const std = @import("std");
3
4const debug = std.debug;
5const heap = std.heap;
6const mem = std.mem;
7
8pub fn ComptimeClap(comptime Id: type, comptime params: []const clap.Param(Id)) type {
9 var flags: usize = 0;
10 var options: usize = 0;
11 var converted_params: []const clap.Param(usize) = []clap.Param(usize){};
12 for (params) |param| {
13 const index = blk: {
14 if (param.names.long == null and param.names.short == null)
15 break :blk 0;
16 if (param.takes_value) {
17 const res = options;
18 options += 1;
19 break :blk res;
20 }
21
22 const res = flags;
23 flags += 1;
24 break :blk res;
25 };
26
27 converted_params = converted_params ++ []clap.Param(usize){
28 clap.Param(usize).init(index, param.takes_value, param.names),
29 };
30 }
31
32 return struct {
33 options: [options]?[]const u8,
34 flags: [flags]bool,
35 pos: []const []const u8,
36 allocator: *mem.Allocator,
37
38 pub fn parse(allocator: *mem.Allocator, comptime ArgError: type, iter: *clap.args.Iterator(ArgError)) !@This() {
39 var pos = std.ArrayList([]const u8).init(allocator);
40 var res = @This(){
41 .options = []?[]const u8{null} ** options,
42 .flags = []bool{false} ** flags,
43 .pos = undefined,
44 .allocator = allocator,
45 };
46
47 var stream = clap.StreamingClap(usize, ArgError).init(converted_params, iter);
48 while (try stream.next()) |arg| {
49 const param = arg.param;
50 if (param.names.long == null and param.names.short == null) {
51 try pos.append(arg.value.?);
52 } else if (param.takes_value) {
53 // We slice before access to avoid false positive access out of bound
54 // compile error.
55 res.options[0..][param.id] = arg.value.?;
56 } else {
57 res.flags[0..][param.id] = true;
58 }
59 }
60
61 res.pos = pos.toOwnedSlice();
62 return res;
63 }
64
65 pub fn deinit(parser: *@This()) void {
66 parser.allocator.free(parser.pos);
67 parser.* = undefined;
68 }
69
70 pub fn flag(parser: @This(), comptime name: []const u8) bool {
71 const param = comptime findParam(name);
72 if (param.takes_value)
73 @compileError(name ++ " is an option and not a flag.");
74
75 return parser.flags[param.id];
76 }
77
78 pub fn option(parser: @This(), comptime name: []const u8) ?[]const u8 {
79 const param = comptime findParam(name);
80 if (!param.takes_value)
81 @compileError(name ++ " is a flag and not an option.");
82
83 return parser.options[param.id];
84 }
85
86 pub fn positionals(parser: @This()) []const []const u8 {
87 return parser.pos;
88 }
89
90 fn findParam(comptime name: []const u8) clap.Param(usize) {
91 comptime {
92 for (converted_params) |param| {
93 if (param.names.short) |s| {
94 if (mem.eql(u8, name, "-" ++ []u8{s}))
95 return param;
96 }
97 if (param.names.long) |l| {
98 if (mem.eql(u8, name, "--" ++ l))
99 return param;
100 }
101 }
102
103 @compileError(name ++ " is not a parameter.");
104 }
105 }
106 };
107}
108
109test "clap.comptime.ComptimeClap" {
110 const Clap = ComptimeClap(void, comptime []clap.Param(void){
111 clap.Param(void).init({}, false, clap.Names{
112 .short = 'a',
113 .long = "aa",
114 }),
115 clap.Param(void).init({}, false, clap.Names{
116 .short = 'b',
117 .long = "bb",
118 }),
119 clap.Param(void).init({}, true, clap.Names{
120 .short = 'c',
121 .long = "cc",
122 }),
123 clap.Param(void).init({}, true, clap.Names.positional()),
124 });
125
126 var buf: [1024]u8 = undefined;
127 var fb_allocator = heap.FixedBufferAllocator.init(buf[0..]);
128 var arg_iter = clap.args.SliceIterator.init([][]const u8{
129 "-a", "-c", "0", "something",
130 });
131 var args = try Clap.parse(&fb_allocator.allocator, clap.args.SliceIterator.Error, &arg_iter.iter);
132 defer args.deinit();
133
134 debug.assert(args.flag("-a"));
135 debug.assert(args.flag("--aa"));
136 debug.assert(!args.flag("-b"));
137 debug.assert(!args.flag("--bb"));
138 debug.assert(mem.eql(u8, args.option("-c").?, "0"));
139 debug.assert(mem.eql(u8, args.option("--cc").?, "0"));
140 debug.assert(args.positionals().len == 1);
141 debug.assert(mem.eql(u8, args.positionals()[0], "something"));
142}
diff --git a/src/index.zig b/src/index.zig
new file mode 100644
index 0000000..dde4748
--- /dev/null
+++ b/src/index.zig
@@ -0,0 +1,105 @@
1const std = @import("std");
2
3const debug = std.debug;
4
5pub const @"comptime" = @import("comptime.zig");
6pub const args = @import("args.zig");
7pub const streaming = @import("streaming.zig");
8
9test "clap" {
10 _ = @"comptime";
11 _ = args;
12 _ = streaming;
13}
14
15pub const ComptimeClap = @"comptime".ComptimeClap;
16pub const StreamingClap = streaming.StreamingClap;
17
18/// The names a ::Param can have.
19pub const Names = struct {
20 /// '-' prefix
21 short: ?u8,
22
23 /// '--' prefix
24 long: ?[]const u8,
25
26 /// Initializes no names
27 pub fn positional() Names {
28 return Names{
29 .short = null,
30 .long = null,
31 };
32 }
33
34 /// Initializes a short name
35 pub fn short(s: u8) Names {
36 return Names{
37 .short = s,
38 .long = null,
39 };
40 }
41
42 /// Initializes a long name
43 pub fn long(l: []const u8) Names {
44 return Names{
45 .short = null,
46 .long = l,
47 };
48 }
49
50 /// Initializes a name with a prefix.
51 /// ::short is set to ::name[0], and ::long is set to ::name.
52 /// This function asserts that ::name.len != 0
53 pub fn prefix(name: []const u8) Names {
54 debug.assert(name.len != 0);
55
56 return Names{
57 .short = name[0],
58 .long = name,
59 };
60 }
61};
62
63/// Represents a parameter for the command line.
64/// Parameters come in three kinds:
65/// * Short ("-a"): Should be used for the most commonly used parameters in your program.
66/// * They can take a value three different ways.
67/// * "-a value"
68/// * "-a=value"
69/// * "-avalue"
70/// * They chain if they don't take values: "-abc".
71/// * The last given parameter can take a value in the same way that a single parameter can:
72/// * "-abc value"
73/// * "-abc=value"
74/// * "-abcvalue"
75/// * Long ("--long-param"): Should be used for less common parameters, or when no single character
76/// can describe the paramter.
77/// * They can take a value two different ways.
78/// * "--long-param value"
79/// * "--long-param=value"
80/// * Positional: Should be used as the primary parameter of the program, like a filename or
81/// an expression to parse.
82/// * Positional parameters have both names.long and names.short == null.
83/// * Positional parameters must take a value.
84pub fn Param(comptime Id: type) type {
85 return struct {
86 id: Id,
87 takes_value: bool,
88 names: Names,
89
90 pub fn init(id: Id, takes_value: bool, names: Names) @This() {
91 // Assert, that if the param have no name, then it has to take
92 // a value.
93 debug.assert(
94 names.long != null or
95 names.short != null or
96 takes_value);
97
98 return @This(){
99 .id = id,
100 .takes_value = takes_value,
101 .names = names,
102 };
103 }
104 };
105}
diff --git a/src/streaming.zig b/src/streaming.zig
new file mode 100644
index 0000000..bfb4045
--- /dev/null
+++ b/src/streaming.zig
@@ -0,0 +1,338 @@
1const builtin = @import("builtin");
2const clap = @import("index.zig");
3const std = @import("std");
4
5const args = clap.args;
6const debug = std.debug;
7const heap = std.heap;
8const mem = std.mem;
9const os = std.os;
10
11/// The result returned from ::StreamingClap.next
12pub fn Arg(comptime Id: type) type {
13 return struct {
14 const Self = @This();
15
16 param: *const clap.Param(Id),
17 value: ?[]const u8,
18
19 pub fn init(param: *const clap.Param(Id), value: ?[]const u8) Self {
20 return Self{
21 .param = param,
22 .value = value,
23 };
24 }
25 };
26}
27
28/// A command line argument parser which, given an ::ArgIterator, will parse arguments according
29/// to the ::params. ::StreamingClap parses in an iterating manner, so you have to use a loop together with
30/// ::StreamingClap.next to parse all the arguments of your program.
31pub fn StreamingClap(comptime Id: type, comptime ArgError: type) type {
32 return struct {
33
34 const State = union(enum) {
35 Normal,
36 Chaining: Chaining,
37
38 const Chaining = struct {
39 arg: []const u8,
40 index: usize,
41 };
42 };
43
44 params: []const clap.Param(Id),
45 iter: *args.Iterator(ArgError),
46 state: State,
47
48 pub fn init(params: []const clap.Param(Id), iter: *args.Iterator(ArgError)) @This() {
49 var res = @This(){
50 .params = params,
51 .iter = iter,
52 .state = State.Normal,
53 };
54
55 return res;
56 }
57
58 /// Get the next ::Arg that matches a ::Param.
59 pub fn next(parser: *@This()) !?Arg(Id) {
60 const ArgInfo = struct {
61 const Kind = enum {
62 Long,
63 Short,
64 Positional,
65 };
66
67 arg: []const u8,
68 kind: Kind,
69 };
70
71 switch (parser.state) {
72 State.Normal => {
73 const full_arg = (try parser.iter.next()) orelse return null;
74 const arg_info = blk: {
75 var arg = full_arg;
76 var kind = ArgInfo.Kind.Positional;
77
78 if (mem.startsWith(u8, arg, "--")) {
79 arg = arg[2..];
80 kind = ArgInfo.Kind.Long;
81 } else if (mem.startsWith(u8, arg, "-")) {
82 arg = arg[1..];
83 kind = ArgInfo.Kind.Short;
84 }
85
86 // We allow long arguments to go without a name.
87 // This allows the user to use "--" for something important
88 if (kind != ArgInfo.Kind.Long and arg.len == 0)
89 return error.InvalidArgument;
90
91 break :blk ArgInfo{ .arg = arg, .kind = kind };
92 };
93
94 const arg = arg_info.arg;
95 const kind = arg_info.kind;
96 const eql_index = mem.indexOfScalar(u8, arg, '=');
97
98 switch (kind) {
99 ArgInfo.Kind.Long => {
100 for (parser.params) |*param| {
101 const match = param.names.long orelse continue;
102 const name = if (eql_index) |i| arg[0..i] else arg;
103 const maybe_value = if (eql_index) |i| arg[i + 1 ..] else null;
104
105 if (!mem.eql(u8, name, match))
106 continue;
107 if (!param.takes_value) {
108 if (maybe_value != null)
109 return error.DoesntTakeValue;
110
111 return Arg(Id).init(param, null);
112 }
113
114 const value = blk: {
115 if (maybe_value) |v|
116 break :blk v;
117
118 break :blk (try parser.iter.next()) orelse return error.MissingValue;
119 };
120
121 return Arg(Id).init(param, value);
122 }
123 },
124 ArgInfo.Kind.Short => {
125 return try parser.chainging(State.Chaining{
126 .arg = full_arg,
127 .index = (full_arg.len - arg.len),
128 });
129 },
130 ArgInfo.Kind.Positional => {
131 for (parser.params) |*param| {
132 if (param.names.long) |_|
133 continue;
134 if (param.names.short) |_|
135 continue;
136
137 return Arg(Id).init(param, arg);
138 }
139 },
140 }
141
142 return error.InvalidArgument;
143 },
144 @TagType(State).Chaining => |state| return try parser.chainging(state),
145 }
146 }
147
148 fn chainging(parser: *@This(), state: State.Chaining) !?Arg(Id) {
149 const arg = state.arg;
150 const index = state.index;
151 const next_index = index + 1;
152
153 for (parser.params) |*param| {
154 const short = param.names.short orelse continue;
155 if (short != arg[index])
156 continue;
157
158 // Before we return, we have to set the new state of the clap
159 defer {
160 if (arg.len <= next_index or param.takes_value) {
161 parser.state = State.Normal;
162 } else {
163 parser.state = State{
164 .Chaining = State.Chaining{
165 .arg = arg,
166 .index = next_index,
167 },
168 };
169 }
170 }
171
172 if (!param.takes_value)
173 return Arg(Id).init(param, null);
174
175 if (arg.len <= next_index) {
176 const value = (try parser.iter.next()) orelse return error.MissingValue;
177 return Arg(Id).init(param, value);
178 }
179
180 if (arg[next_index] == '=') {
181 return Arg(Id).init(param, arg[next_index + 1 ..]);
182 }
183
184 return Arg(Id).init(param, arg[next_index..]);
185 }
186
187 return error.InvalidArgument;
188 }
189 };
190}
191
192
193fn testNoErr(params: []const clap.Param(u8), args_strings: []const []const u8, results: []const Arg(u8)) void {
194 var arg_iter = args.SliceIterator.init(args_strings);
195 var c = StreamingClap(u8, args.SliceIterator.Error).init(params, &arg_iter.iter);
196
197 for (results) |res| {
198 const arg = (c.next() catch unreachable) orelse unreachable;
199 debug.assert(res.param == arg.param);
200 const expected_value = res.value orelse {
201 debug.assert(arg.value == null);
202 continue;
203 };
204 const actual_value = arg.value orelse unreachable;
205 debug.assert(mem.eql(u8, expected_value, actual_value));
206 }
207
208 if (c.next() catch unreachable) |_| {
209 unreachable;
210 }
211}
212
213test "clap.streaming.StreamingClap: short params" {
214 const params = []clap.Param(u8){
215 clap.Param(u8).init(0, false, clap.Names.short('a')),
216 clap.Param(u8).init(1, false, clap.Names.short('b')),
217 clap.Param(u8).init(2, true, clap.Names.short('c')),
218 };
219
220 const a = &params[0];
221 const b = &params[1];
222 const c = &params[2];
223
224 testNoErr(
225 params,
226 [][]const u8{
227 "-a", "-b", "-ab", "-ba",
228 "-c", "0", "-c=0", "-ac",
229 "0", "-ac=0",
230 },
231 []const Arg(u8){
232 Arg(u8).init(a, null),
233 Arg(u8).init(b, null),
234 Arg(u8).init(a, null),
235 Arg(u8).init(b, null),
236 Arg(u8).init(b, null),
237 Arg(u8).init(a, null),
238 Arg(u8).init(c, "0"),
239 Arg(u8).init(c, "0"),
240 Arg(u8).init(a, null),
241 Arg(u8).init(c, "0"),
242 Arg(u8).init(a, null),
243 Arg(u8).init(c, "0"),
244 },
245 );
246}
247
248test "clap.streaming.StreamingClap: long params" {
249 const params = []clap.Param(u8){
250 clap.Param(u8).init(0, false, clap.Names.long("aa")),
251 clap.Param(u8).init(1, false, clap.Names.long("bb")),
252 clap.Param(u8).init(2, true, clap.Names.long("cc")),
253 };
254
255 const aa = &params[0];
256 const bb = &params[1];
257 const cc = &params[2];
258
259 testNoErr(
260 params,
261 [][]const u8{
262 "--aa", "--bb",
263 "--cc", "0",
264 "--cc=0",
265 },
266 []const Arg(u8){
267 Arg(u8).init(aa, null),
268 Arg(u8).init(bb, null),
269 Arg(u8).init(cc, "0"),
270 Arg(u8).init(cc, "0"),
271 },
272 );
273}
274
275test "clap.streaming.StreamingClap: positional params" {
276 const params = []clap.Param(u8){clap.Param(u8).init(0, true, clap.Names.positional())};
277
278 testNoErr(
279 params,
280 [][]const u8{ "aa", "bb" },
281 []const Arg(u8){
282 Arg(u8).init(&params[0], "aa"),
283 Arg(u8).init(&params[0], "bb"),
284 },
285 );
286}
287
288test "clap.streaming.StreamingClap: all params" {
289 const params = []clap.Param(u8){
290 clap.Param(u8).init(0, false, clap.Names{
291 .short = 'a',
292 .long = "aa",
293 }),
294 clap.Param(u8).init(1, false, clap.Names{
295 .short = 'b',
296 .long = "bb",
297 }),
298 clap.Param(u8).init(2, true, clap.Names{
299 .short = 'c',
300 .long = "cc",
301 }),
302 clap.Param(u8).init(3, true, clap.Names.positional()),
303 };
304
305 const aa = &params[0];
306 const bb = &params[1];
307 const cc = &params[2];
308 const positional = &params[3];
309
310 testNoErr(
311 params,
312 [][]const u8{
313 "-a", "-b", "-ab", "-ba",
314 "-c", "0", "-c=0", "-ac",
315 "0", "-ac=0", "--aa", "--bb",
316 "--cc", "0", "--cc=0", "something",
317 },
318 []const Arg(u8){
319 Arg(u8).init(aa, null),
320 Arg(u8).init(bb, null),
321 Arg(u8).init(aa, null),
322 Arg(u8).init(bb, null),
323 Arg(u8).init(bb, null),
324 Arg(u8).init(aa, null),
325 Arg(u8).init(cc, "0"),
326 Arg(u8).init(cc, "0"),
327 Arg(u8).init(aa, null),
328 Arg(u8).init(cc, "0"),
329 Arg(u8).init(aa, null),
330 Arg(u8).init(cc, "0"),
331 Arg(u8).init(aa, null),
332 Arg(u8).init(bb, null),
333 Arg(u8).init(cc, "0"),
334 Arg(u8).init(cc, "0"),
335 Arg(u8).init(positional, "something"),
336 },
337 );
338}
diff --git a/test.zig b/test.zig
deleted file mode 100644
index 27c93c2..0000000
--- a/test.zig
+++ /dev/null
@@ -1,245 +0,0 @@
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 StreamingClap = clap.StreamingClap(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 = StreamingClap.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", "-ac",
51 "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",
86 "--cc=0",
87 },
88 []const Arg{
89 Arg.init(aa, null),
90 Arg.init(bb, null),
91 Arg.init(cc, "0"),
92 Arg.init(cc, "0"),
93 },
94 );
95}
96
97test "clap: bare" {
98 const params = []Param{
99 Param.init(0, false, Names.bare("aa")),
100 Param.init(1, false, Names.bare("bb")),
101 Param.init(2, true, Names.bare("cc")),
102 };
103
104 const aa = &params[0];
105 const bb = &params[1];
106 const cc = &params[2];
107
108 testNoErr(
109 params,
110 [][]const u8{
111 "aa", "bb",
112 "cc", "0",
113 "cc=0",
114 },
115 []const Arg{
116 Arg.init(aa, null),
117 Arg.init(bb, null),
118 Arg.init(cc, "0"),
119 Arg.init(cc, "0"),
120 },
121 );
122}
123
124test "clap: none" {
125 const params = []Param{Param.init(0, true, Names.none())};
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", "-ac",
167 "0", "-ac=0", "--aa", "--bb",
168 "--cc", "0", "--cc=0", "aa",
169 "bb", "cc", "0", "cc=0",
170 "something",
171 },
172 []const Arg{
173 Arg.init(aa, null),
174 Arg.init(bb, null),
175 Arg.init(aa, null),
176 Arg.init(bb, null),
177 Arg.init(bb, null),
178 Arg.init(aa, null),
179 Arg.init(cc, "0"),
180 Arg.init(cc, "0"),
181 Arg.init(aa, null),
182 Arg.init(cc, "0"),
183 Arg.init(aa, null),
184 Arg.init(cc, "0"),
185 Arg.init(aa, null),
186 Arg.init(bb, null),
187 Arg.init(cc, "0"),
188 Arg.init(cc, "0"),
189 Arg.init(aa, null),
190 Arg.init(bb, null),
191 Arg.init(cc, "0"),
192 Arg.init(cc, "0"),
193 Arg.init(bare, "something"),
194 },
195 );
196}
197
198test "clap.Example" {
199 // Fake program arguments. Don't mind them
200 const program_args = [][]const u8{
201 "-h", "--help",
202 "-v", "--version",
203 "file.zig",
204 };
205
206 const warn = @import("std").debug.warn;
207 const c = @import("clap.zig");
208
209 // Initialize the parameters you want your program to take.
210 // `Param` has a type passed in, which will determin the type
211 // of `Param.id`. This field can then be used to identify the
212 // `Param`, or something else entirely.
213 // Example: You could have the `id` be a function, and then
214 // call it in the loop further down.
215 const params = []c.Param(u8){
216 c.Param(u8).init('h', false, c.Names.prefix("help")),
217 c.Param(u8).init('v', false, c.Names.prefix("version")),
218 c.Param(u8).init('f', true, c.Names.none()),
219 };
220
221 // Here, we use an `ArgSliceIterator` which iterates over
222 // a slice of arguments. For real program, you would probably
223 // use `OsArgIterator`.
224 var iter = &c.ArgSliceIterator.init(program_args).iter;
225 var parser = c.StreamingClap(u8, c.ArgSliceIterator.Error).init(params, iter);
226
227 // Iterate over all arguments passed to the program.
228 // In real code, you should probably handle the errors
229 // `parser.next` returns.
230 while (parser.next() catch unreachable) |arg| {
231 // `arg.param` is a pointer to its matching `Param`
232 // from the `params` array.
233 switch (arg.param.id) {
234 'h' => warn("Help!\n"),
235 'v' => warn("1.1.1\n"),
236
237 // `arg.value` is `null`, if `arg.param.takes_value`
238 // is `false`. Otherwise, `arg.value` is the value
239 // passed with the argument, such as `-a=10` or
240 // `-a 10`.
241 'f' => warn("{}\n", arg.value.?),
242 else => unreachable,
243 }
244 }
245}