summaryrefslogtreecommitdiff
path: root/clap/streaming.zig
diff options
context:
space:
mode:
Diffstat (limited to 'clap/streaming.zig')
-rw-r--r--clap/streaming.zig348
1 files changed, 348 insertions, 0 deletions
diff --git a/clap/streaming.zig b/clap/streaming.zig
new file mode 100644
index 0000000..fa7ce80
--- /dev/null
+++ b/clap/streaming.zig
@@ -0,0 +1,348 @@
1const builtin = @import("builtin");
2const clap = @import("../clap.zig");
3const std = @import("std");
4
5const args = clap.args;
6const testing = std.testing;
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 = null,
18 };
19}
20
21/// A command line argument parser which, given an ::ArgIterator, will parse arguments according
22/// to the ::params. ::StreamingClap parses in an iterating manner, so you have to use a loop together with
23/// ::StreamingClap.next to parse all the arguments of your program.
24pub fn StreamingClap(comptime Id: type, comptime ArgIterator: type) type {
25 return struct {
26 const State = union(enum) {
27 Normal,
28 Chaining: Chaining,
29
30 const Chaining = struct {
31 arg: []const u8,
32 index: usize,
33 };
34 };
35
36 params: []const clap.Param(Id),
37 iter: *ArgIterator,
38 state: State = State.Normal,
39
40 /// Get the next ::Arg that matches a ::Param.
41 pub fn next(parser: *@This()) !?Arg(Id) {
42 const ArgInfo = struct {
43 const Kind = enum {
44 Long,
45 Short,
46 Positional,
47 };
48
49 arg: []const u8,
50 kind: Kind,
51 };
52
53 switch (parser.state) {
54 .Normal => {
55 const full_arg = (try parser.iter.next()) orelse return null;
56 const arg_info = if (mem.eql(u8, full_arg, "--") or mem.eql(u8, full_arg, "-"))
57 ArgInfo{ .arg = full_arg, .kind = .Positional }
58 else if (mem.startsWith(u8, full_arg, "--"))
59 ArgInfo{ .arg = full_arg[2..], .kind = .Long }
60 else if (mem.startsWith(u8, full_arg, "-"))
61 ArgInfo{ .arg = full_arg[1..], .kind = .Short }
62 else
63 ArgInfo{ .arg = full_arg, .kind = .Positional };
64
65 const arg = arg_info.arg;
66 const kind = arg_info.kind;
67 const eql_index = mem.indexOfScalar(u8, arg, '=');
68
69 switch (kind) {
70 ArgInfo.Kind.Long => {
71 for (parser.params) |*param| {
72 const match = param.names.long orelse continue;
73 const name = if (eql_index) |i| arg[0..i] else arg;
74 const maybe_value = if (eql_index) |i| arg[i + 1 ..] else null;
75
76 if (!mem.eql(u8, name, match))
77 continue;
78 if (!param.takes_value) {
79 if (maybe_value != null)
80 return error.DoesntTakeValue;
81
82 return Arg(Id){ .param = param };
83 }
84
85 const value = blk: {
86 if (maybe_value) |v|
87 break :blk v;
88
89 break :blk (try parser.iter.next()) orelse return error.MissingValue;
90 };
91
92 return Arg(Id){ .param = param, .value = value };
93 }
94 },
95 ArgInfo.Kind.Short => {
96 return try parser.chainging(State.Chaining{
97 .arg = full_arg,
98 .index = (full_arg.len - arg.len),
99 });
100 },
101 ArgInfo.Kind.Positional => {
102 for (parser.params) |*param| {
103 if (param.names.long) |_|
104 continue;
105 if (param.names.short) |_|
106 continue;
107
108 return Arg(Id){ .param = param, .value = arg };
109 }
110 },
111 }
112
113 return error.InvalidArgument;
114 },
115 .Chaining => |state| return try parser.chainging(state),
116 }
117 }
118
119 fn chainging(parser: *@This(), state: State.Chaining) !?Arg(Id) {
120 const arg = state.arg;
121 const index = state.index;
122 const next_index = index + 1;
123
124 for (parser.params) |*param| {
125 const short = param.names.short orelse continue;
126 if (short != arg[index])
127 continue;
128
129 // Before we return, we have to set the new state of the clap
130 defer {
131 if (arg.len <= next_index or param.takes_value) {
132 parser.state = State.Normal;
133 } else {
134 parser.state = State{
135 .Chaining = State.Chaining{
136 .arg = arg,
137 .index = next_index,
138 },
139 };
140 }
141 }
142
143 if (!param.takes_value)
144 return Arg(Id){ .param = param };
145
146 if (arg.len <= next_index) {
147 const value = (try parser.iter.next()) orelse return error.MissingValue;
148 return Arg(Id){ .param = param, .value = value };
149 }
150
151 if (arg[next_index] == '=')
152 return Arg(Id){ .param = param, .value = arg[next_index + 1 ..] };
153
154 return Arg(Id){ .param = param, .value = arg[next_index..] };
155 }
156
157 return error.InvalidArgument;
158 }
159 };
160}
161
162fn testNoErr(params: []const clap.Param(u8), args_strings: []const []const u8, results: []const Arg(u8)) void {
163 var iter = args.SliceIterator{ .args = args_strings };
164 var c = StreamingClap(u8, args.SliceIterator){
165 .params = params,
166 .iter = &iter,
167 };
168
169 for (results) |res| {
170 const arg = (c.next() catch unreachable) orelse unreachable;
171 testing.expectEqual(res.param, arg.param);
172 const expected_value = res.value orelse {
173 testing.expectEqual(@typeOf(arg.value)(null), arg.value);
174 continue;
175 };
176 const actual_value = arg.value orelse unreachable;
177 testing.expectEqualSlices(u8, expected_value, actual_value);
178 }
179
180 if (c.next() catch unreachable) |_|
181 unreachable;
182}
183
184test "clap.streaming.StreamingClap: short params" {
185 const params = [_]clap.Param(u8){
186 clap.Param(u8){
187 .id = 0,
188 .names = clap.Names{ .short = 'a' },
189 },
190 clap.Param(u8){
191 .id = 1,
192 .names = clap.Names{ .short = 'b' },
193 },
194 clap.Param(u8){
195 .id = 2,
196 .names = clap.Names{ .short = 'c' },
197 .takes_value = true,
198 },
199 };
200
201 const a = &params[0];
202 const b = &params[1];
203 const c = &params[2];
204
205 testNoErr(
206 params,
207 [_][]const u8{
208 "-a", "-b", "-ab", "-ba",
209 "-c", "0", "-c=0", "-ac",
210 "0", "-ac=0",
211 },
212 [_]Arg(u8){
213 Arg(u8){ .param = a },
214 Arg(u8){ .param = b },
215 Arg(u8){ .param = a },
216 Arg(u8){ .param = b },
217 Arg(u8){ .param = b },
218 Arg(u8){ .param = a },
219 Arg(u8){ .param = c, .value = "0" },
220 Arg(u8){ .param = c, .value = "0" },
221 Arg(u8){ .param = a },
222 Arg(u8){ .param = c, .value = "0" },
223 Arg(u8){ .param = a },
224 Arg(u8){ .param = c, .value = "0" },
225 },
226 );
227}
228
229test "clap.streaming.StreamingClap: long params" {
230 const params = [_]clap.Param(u8){
231 clap.Param(u8){
232 .id = 0,
233 .names = clap.Names{ .long = "aa" },
234 },
235 clap.Param(u8){
236 .id = 1,
237 .names = clap.Names{ .long = "bb" },
238 },
239 clap.Param(u8){
240 .id = 2,
241 .names = clap.Names{ .long = "cc" },
242 .takes_value = true,
243 },
244 };
245
246 const aa = &params[0];
247 const bb = &params[1];
248 const cc = &params[2];
249
250 testNoErr(
251 params,
252 [_][]const u8{
253 "--aa", "--bb",
254 "--cc", "0",
255 "--cc=0",
256 },
257 [_]Arg(u8){
258 Arg(u8){ .param = aa },
259 Arg(u8){ .param = bb },
260 Arg(u8){ .param = cc, .value = "0" },
261 Arg(u8){ .param = cc, .value = "0" },
262 },
263 );
264}
265
266test "clap.streaming.StreamingClap: positional params" {
267 const params = [_]clap.Param(u8){clap.Param(u8){
268 .id = 0,
269 .takes_value = true,
270 }};
271
272 testNoErr(
273 params,
274 [_][]const u8{ "aa", "bb" },
275 [_]Arg(u8){
276 Arg(u8){ .param = &params[0], .value = "aa" },
277 Arg(u8){ .param = &params[0], .value = "bb" },
278 },
279 );
280}
281
282test "clap.streaming.StreamingClap: all params" {
283 const params = [_]clap.Param(u8){
284 clap.Param(u8){
285 .id = 0,
286 .names = clap.Names{
287 .short = 'a',
288 .long = "aa",
289 },
290 },
291 clap.Param(u8){
292 .id = 1,
293 .names = clap.Names{
294 .short = 'b',
295 .long = "bb",
296 },
297 },
298 clap.Param(u8){
299 .id = 2,
300 .names = clap.Names{
301 .short = 'c',
302 .long = "cc",
303 },
304 .takes_value = true,
305 },
306 clap.Param(u8){
307 .id = 3,
308 .takes_value = true,
309 },
310 };
311
312 const aa = &params[0];
313 const bb = &params[1];
314 const cc = &params[2];
315 const positional = &params[3];
316
317 testNoErr(
318 params,
319 [_][]const u8{
320 "-a", "-b", "-ab", "-ba",
321 "-c", "0", "-c=0", "-ac",
322 "0", "-ac=0", "--aa", "--bb",
323 "--cc", "0", "--cc=0", "something",
324 "--", "-",
325 },
326 [_]Arg(u8){
327 Arg(u8){ .param = aa },
328 Arg(u8){ .param = bb },
329 Arg(u8){ .param = aa },
330 Arg(u8){ .param = bb },
331 Arg(u8){ .param = bb },
332 Arg(u8){ .param = aa },
333 Arg(u8){ .param = cc, .value = "0" },
334 Arg(u8){ .param = cc, .value = "0" },
335 Arg(u8){ .param = aa },
336 Arg(u8){ .param = cc, .value = "0" },
337 Arg(u8){ .param = aa },
338 Arg(u8){ .param = cc, .value = "0" },
339 Arg(u8){ .param = aa },
340 Arg(u8){ .param = bb },
341 Arg(u8){ .param = cc, .value = "0" },
342 Arg(u8){ .param = cc, .value = "0" },
343 Arg(u8){ .param = positional, .value = "something" },
344 Arg(u8){ .param = positional, .value = "--" },
345 Arg(u8){ .param = positional, .value = "-" },
346 },
347 );
348}