summaryrefslogtreecommitdiff
path: root/src/streaming.zig
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 /src/streaming.zig
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 'src/streaming.zig')
-rw-r--r--src/streaming.zig338
1 files changed, 338 insertions, 0 deletions
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}