summaryrefslogtreecommitdiff
path: root/index.zig
diff options
context:
space:
mode:
authorGravatar Jimmi HC2018-05-31 16:00:44 +0200
committerGravatar Jimmi HC2018-05-31 16:00:44 +0200
commitf4e53cd6149ed4dcbfd8b81a6427b1b652d0a472 (patch)
tree7ca591977efac33ebc9aa51aab7356a4faa97d46 /index.zig
parentReworked the core. (diff)
downloadzig-clap-f4e53cd6149ed4dcbfd8b81a6427b1b652d0a472.tar.gz
zig-clap-f4e53cd6149ed4dcbfd8b81a6427b1b652d0a472.tar.xz
zig-clap-f4e53cd6149ed4dcbfd8b81a6427b1b652d0a472.zip
Started work on the proper structure for the lib
Diffstat (limited to 'index.zig')
-rw-r--r--index.zig493
1 files changed, 2 insertions, 491 deletions
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}