summaryrefslogtreecommitdiff
path: root/clap.zig
diff options
context:
space:
mode:
authorGravatar Jimmi Holst Christensen2018-05-20 01:21:15 +0200
committerGravatar Jimmi Holst Christensen2018-05-20 01:21:15 +0200
commita1f024342d33fc7fe54a657c3b07a05e33f631c3 (patch)
tree44074ff67e1e90459beae9e8b309e79a1921ba67 /clap.zig
parentcore.zig tested and ready for action! (diff)
downloadzig-clap-a1f024342d33fc7fe54a657c3b07a05e33f631c3.tar.gz
zig-clap-a1f024342d33fc7fe54a657c3b07a05e33f631c3.tar.xz
zig-clap-a1f024342d33fc7fe54a657c3b07a05e33f631c3.zip
The old clap now uses core.zig to get the same func as before
Diffstat (limited to 'clap.zig')
-rw-r--r--clap.zig500
1 files changed, 0 insertions, 500 deletions
diff --git a/clap.zig b/clap.zig
deleted file mode 100644
index 269e9f1..0000000
--- a/clap.zig
+++ /dev/null
@@ -1,500 +0,0 @@
1const builtin = @import("builtin");
2const std = @import("std");
3
4const mem = std.mem;
5const fmt = std.fmt;
6const debug = std.debug;
7const io = std.io;
8
9const assert = debug.assert;
10
11pub fn Clap(comptime Result: type) type {
12 return struct {
13 const Self = this;
14
15 program_name: []const u8,
16 author: []const u8,
17 version: []const u8,
18 about: []const u8,
19 command: Command,
20 defaults: Result,
21
22 pub fn init(defaults: &const Result) Self {
23 return Self {
24 .program_name = "",
25 .author = "",
26 .version = "",
27 .about = "",
28 .command = Command.init(""),
29 .defaults = *defaults,
30 };
31 }
32
33 pub fn with(parser: &const Self, comptime field: []const u8, value: var) Self {
34 var res = *parser;
35 @field(res, field) = value;
36 return res;
37 }
38
39 pub fn parse(comptime clap: &const Self, arguments: []const []const u8) !Result {
40 return parseCommand(CommandList { .command = clap.command, .prev = null }, clap.defaults, arguments);
41 }
42
43 const CommandList = struct {
44 command: &const Command,
45 prev: ?&const CommandList,
46 };
47
48 fn parseCommand(comptime list: &const CommandList, defaults: &const Result, arguments: []const []const u8) !Result {
49 const command = list.command;
50
51 const Arg = struct {
52 const Kind = enum { Long, Short, Value };
53
54 arg: []const u8,
55 kind: Kind,
56 };
57
58 const Iterator = struct {
59 index: usize,
60 slice: []const []const u8,
61
62 const Pair = struct {
63 value: []const u8,
64 index: usize,
65 };
66
67 pub fn next(it: &this) ?[]const u8 {
68 const res = it.nextWithIndex() ?? return null;
69 return res.value;
70 }
71
72 pub fn nextWithIndex(it: &this) ?Pair {
73 if (it.index >= it.slice.len)
74 return null;
75
76 defer it.index += 1;
77 return Pair {
78 .value = it.slice[it.index],
79 .index = it.index,
80 };
81 }
82 };
83
84 // NOTE: For now, a bitfield is used to keep track of the required arguments.
85 // This limits the user to 128 required arguments, which should be more
86 // than enough.
87 var required = comptime blk: {
88 var required_index : u128 = 0;
89 var required_res : u128 = 0;
90 for (command.arguments) |option| {
91 if (option.required) {
92 required_res |= 0x1 << required_index;
93 required_index += 1;
94 }
95 }
96
97 break :blk required_res;
98 };
99
100 var result = *defaults;
101
102 var it = Iterator { .index = 0, .slice = arguments };
103 while (it.nextWithIndex()) |item| {
104 const arg_info = blk: {
105 var arg = item.value;
106 var kind = Arg.Kind.Value;
107
108 if (mem.startsWith(u8, arg, "--")) {
109 arg = arg[2..];
110 kind = Arg.Kind.Long;
111 } else if (mem.startsWith(u8, arg, "-")) {
112 arg = arg[1..];
113 kind = Arg.Kind.Short;
114 }
115
116 break :blk Arg { .arg = arg, .kind = kind };
117 };
118 const arg = arg_info.arg;
119 const arg_index = item.index;
120 const kind = arg_info.kind;
121 const eql_index = mem.indexOfScalar(u8, arg, '=');
122
123 success: {
124 // TODO: Revert a lot of if statements when inline loop compiler bugs have been fixed
125 switch (kind) {
126 // TODO: Handle subcommands
127 Arg.Kind.Value => {
128 var required_index = usize(0);
129 inline for (command.arguments) |option| {
130 defer if (option.required) required_index += 1;
131
132 if (option.short != null) continue;
133 if (option.long != null) continue;
134 const has_right_index = if (option.index) |index| index == it.index else true;
135
136 if (has_right_index) {
137 if (option.takes_value) |parser| {
138 try parser.parse(&@field(result, option.field), arg);
139 } else {
140 @field(result, option.field) = true;
141 }
142
143 required = newRequired(option, required, required_index);
144 break :success;
145 }
146 }
147 },
148 Arg.Kind.Short => {
149 if (arg.len == 0) return error.InvalidArg;
150
151 const end = (eql_index ?? arg.len) - 1;
152
153 short_arg_loop:
154 for (arg[0..end]) |short_arg, i| {
155 var required_index = usize(0);
156
157 inline for (command.arguments) |option| {
158 defer if (option.required) required_index += 1;
159
160 const short = option.short ?? continue;
161 const has_right_index = if (option.index) |index| index == arg_index else true;
162
163 if (has_right_index) {
164 if (short_arg == short) {
165 if (option.takes_value) |parser| {
166 const value = arg[i + 1..];
167 try parser.parse(&@field(result, option.field), value);
168 break :success;
169 } else {
170 @field(result, option.field) = true;
171 continue :short_arg_loop;
172 }
173
174 required = newRequired(option, required, required_index);
175 }
176 }
177 }
178 }
179
180 const last_arg = arg[end];
181 var required_index = usize(0);
182 inline for (command.arguments) |option| {
183 defer if (option.required) required_index += 1;
184
185 const short = option.short ?? continue;
186 const has_right_index = if (option.index) |index| index == arg_index else true;
187
188 if (has_right_index and last_arg == short) {
189 if (option.takes_value) |parser| {
190 const value = if (eql_index) |index| arg[index + 1..] else it.next() ?? return error.ArgMissingValue;
191 try parser.parse(&@field(result, option.field), value);
192 } else {
193 if (eql_index) |_| return error.ArgTakesNoValue;
194 @field(result, option.field) = true;
195 }
196
197 required = newRequired(option, required, required_index);
198 break :success;
199 }
200 }
201 },
202 Arg.Kind.Long => {
203 var required_index = usize(0);
204 inline for (command.arguments) |option| {
205 defer if (option.required) required_index += 1;
206
207 const long = option.long ?? continue;
208 const has_right_index = if (option.index) |index| index == arg_index else true;
209
210 if (has_right_index and mem.eql(u8, arg, long)) {
211 if (option.takes_value) |parser| {
212 const value = if (eql_index) |index| arg[index + 1..] else it.next() ?? return error.ArgMissingValue;
213 try parser.parse(&@field(result, option.field), value);
214 } else {
215 @field(result, option.field) = true;
216 }
217
218 required = newRequired(option, required, required_index);
219 break :success;
220 }
221 }
222 }
223 }
224
225 return error.InvalidArg;
226 }
227 }
228
229 if (required != 0) {
230 return error.RequiredArgNotHandled;
231 }
232
233 return result;
234 }
235
236 fn newRequired(argument: &const Argument, old_required: u128, index: usize) u128 {
237 if (argument.required)
238 return old_required & ~(u128(1) << u7(index));
239
240 return old_required;
241 }
242 };
243}
244
245pub const Command = struct {
246 field: ?[]const u8,
247 name: []const u8,
248 arguments: []const Argument,
249 sub_commands: []const Command,
250
251 pub fn init(command_name: []const u8) Command {
252 return Command {
253 .field = null,
254 .name = command_name,
255 .arguments = []Argument{ },
256 .sub_commands = []Command{ },
257 };
258 }
259
260 pub fn with(command: &const Command, comptime field: []const u8, value: var) Command {
261 var res = *command;
262 @field(res, field) = value;
263 return res;
264 }
265};
266
267const Parser = struct {
268 const UnsafeFunction = &const void;
269
270 FieldType: type,
271 Errors: type,
272 func: UnsafeFunction,
273
274 fn init(comptime FieldType: type, comptime Errors: type, func: parseFunc(FieldType, Errors)) Parser {
275 return Parser {
276 .FieldType = FieldType,
277 .Errors = Errors,
278 .func = @ptrCast(UnsafeFunction, func),
279 };
280 }
281
282 fn parse(comptime parser: Parser, field_ptr: takePtr(parser.FieldType), arg: []const u8) parser.Errors!void {
283 return @ptrCast(parseFunc(parser.FieldType, parser.Errors), parser.func)(field_ptr, arg);
284 }
285
286 // TODO: This is a workaround, since we don't have pointer reform yet.
287 fn takePtr(comptime T: type) type { return &T; }
288
289 fn parseFunc(comptime FieldType: type, comptime Errors: type) type {
290 return fn(&FieldType, []const u8) Errors!void;
291 }
292};
293
294pub const Argument = struct {
295 field: []const u8,
296 help: []const u8,
297 takes_value: ?Parser,
298 required: bool,
299 short: ?u8,
300 long: ?[]const u8,
301 index: ?usize,
302
303 pub fn field(field_name: []const u8) Argument {
304 return Argument {
305 .field = field_name,
306 .help = "",
307 .takes_value = null,
308 .required = false,
309 .short = null,
310 .long = null,
311 .index = null,
312 };
313 }
314
315 pub fn arg(s: []const u8) Argument {
316 return Argument.field(s)
317 .with("short", if (s.len == 1) s[0] else null)
318 .with("long", if (s.len != 1) s else null);
319 }
320
321 pub fn with(argument: &const Argument, comptime field_name: []const u8, value: var) Argument {
322 var res = *argument;
323 @field(res, field_name) = value;
324 return res;
325 }
326};
327
328pub const parse = struct {
329 pub fn int(comptime Int: type, comptime radix: u8) Parser {
330 const func = struct {
331 fn i(field_ptr: &Int, arg: []const u8) !void {
332 *field_ptr = try fmt.parseInt(Int, arg, radix);
333 }
334 }.i;
335 return Parser.init(
336 Int,
337 @typeOf(func).ReturnType.ErrorSet,
338 func
339 );
340 }
341
342 const string = Parser.init(
343 []const u8,
344 error{},
345 struct {
346 fn s(field_ptr: &[]const u8, arg: []const u8) (error{}!void) {
347 *field_ptr = arg;
348 }
349 }.s
350 );
351
352 const boolean = Parser.init(
353 []const u8,
354 error{InvalidBoolArg},
355 struct {
356 fn b(comptime T: type, field_ptr: &T, arg: []const u8) (error{InvalidBoolArg}!void) {
357 if (mem.eql(u8, arg, "true")) {
358 *field_ptr = true;
359 } else if (mem.eql(u8, arg, "false")) {
360 *field_ptr = false;
361 } else {
362 return error.InvalidBoolArg;
363 }
364 }
365 }.b
366 );
367};
368
369
370const Options = struct {
371 str: []const u8,
372 int: i64,
373 uint: u64,
374 a: bool,
375 b: bool,
376 cc: bool,
377
378 pub fn with(op: &const Options, comptime field: []const u8, value: var) Options {
379 var res = *op;
380 @field(res, field) = value;
381 return res;
382 }
383};
384
385const default = Options {
386 .str = "",
387 .int = 0,
388 .uint = 0,
389 .a = false,
390 .b = false,
391 .cc = false,
392};
393
394fn testNoErr(comptime clap: &const Clap(Options), args: []const []const u8, expected: &const Options) void {
395 const actual = clap.parse(args) catch |err| { debug.warn("{}\n", @errorName(err)); unreachable; };
396 assert(mem.eql(u8, expected.str, actual.str));
397 assert(expected.int == actual.int);
398 assert(expected.uint == actual.uint);
399 assert(expected.a == actual.a);
400 assert(expected.b == actual.b);
401 assert(expected.cc == actual.cc);
402}
403
404fn testErr(comptime clap: &const Clap(Options), args: []const []const u8, expected: error) void {
405 if (clap.parse(args)) |actual| {
406 unreachable;
407 } else |err| {
408 assert(err == expected);
409 }
410}
411
412test "clap.parse: short" {
413 const clap = comptime Clap(Options).init(default).with("command",
414 Command.init("").with("arguments",
415 []Argument {
416 Argument.arg("a"),
417 Argument.arg("b"),
418 Argument.field("int")
419 .with("short", 'i')
420 .with("takes_value", parse.int(i64, 10))
421 }
422 )
423 );
424
425 testNoErr(clap, [][]const u8 { "-a" }, default.with("a", true));
426 testNoErr(clap, [][]const u8 { "-a", "-b" }, default.with("a", true).with("b", true));
427 testNoErr(clap, [][]const u8 { "-i=100" }, default.with("int", 100));
428 testNoErr(clap, [][]const u8 { "-i100" }, default.with("int", 100));
429 testNoErr(clap, [][]const u8 { "-i", "100" }, default.with("int", 100));
430 testNoErr(clap, [][]const u8 { "-ab" }, default.with("a", true).with("b", true));
431 testNoErr(clap, [][]const u8 { "-abi", "100" }, default.with("a", true).with("b", true).with("int", 100));
432 testNoErr(clap, [][]const u8 { "-abi=100" }, default.with("a", true).with("b", true).with("int", 100));
433 testNoErr(clap, [][]const u8 { "-abi100" }, default.with("a", true).with("b", true).with("int", 100));
434}
435
436test "clap.parse: long" {
437 const clap = comptime Clap(Options).init(default).with("command",
438 Command.init("").with("arguments",
439 []Argument {
440 Argument.arg("cc"),
441 Argument.arg("int").with("takes_value", parse.int(i64, 10)),
442 Argument.arg("uint").with("takes_value", parse.int(u64, 10)),
443 Argument.arg("str").with("takes_value", parse.string),
444 }
445 )
446 );
447
448 testNoErr(clap, [][]const u8 { "--cc" }, default.with("cc", true));
449 testNoErr(clap, [][]const u8 { "--int", "100" }, default.with("int", 100));
450}
451
452test "clap.parse: value bool" {
453 const clap = comptime Clap(Options).init(default).with("command",
454 Command.init("").with("arguments",
455 []Argument {
456 Argument.field("a"),
457 }
458 )
459 );
460
461 testNoErr(clap, [][]const u8 { "Hello World!" }, default.with("a", true));
462}
463
464test "clap.parse: value str" {
465 const clap = comptime Clap(Options).init(default).with("command",
466 Command.init("").with("arguments",
467 []Argument {
468 Argument.field("str").with("takes_value", parse.string),
469 }
470 )
471 );
472
473 testNoErr(clap, [][]const u8 { "Hello World!" }, default.with("str", "Hello World!"));
474}
475
476test "clap.parse: value int" {
477 const clap = comptime Clap(Options).init(default).with("command",
478 Command.init("").with("arguments",
479 []Argument {
480 Argument.field("int").with("takes_value", parse.int(i64, 10)),
481 }
482 )
483 );
484
485 testNoErr(clap, [][]const u8 { "100" }, default.with("int", 100));
486}
487
488test "clap.parse: index" {
489 const clap = comptime Clap(Options).init(default).with("command",
490 Command.init("").with("arguments",
491 []Argument {
492 Argument.arg("a").with("index", 0),
493 Argument.arg("b").with("index", 1),
494 }
495 )
496 );
497
498 testNoErr(clap, [][]const u8 { "-a", "-b" }, default.with("a", true).with("b", true));
499 testErr(clap, [][]const u8 { "-b", "-a" }, error.InvalidArg);
500}