summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--clap.zig500
-rw-r--r--core.zig67
-rw-r--r--example.exebin0 -> 182272 bytes
-rw-r--r--example.pdbbin0 -> 241664 bytes
-rw-r--r--example.zig73
-rw-r--r--extended.zig274
6 files changed, 337 insertions, 577 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}
diff --git a/core.zig b/core.zig
index d9dc804..8b953a9 100644
--- a/core.zig
+++ b/core.zig
@@ -131,10 +131,10 @@ pub const OsArgIterator = struct {
131 }; 131 };
132 } 132 }
133 133
134 fn nextFn(iter: &ArgIterator, allocator: &mem.Allocator) ArgIterator.Error![]const u8 { 134 fn nextFn(iter: &ArgIterator, allocator: &mem.Allocator) ArgIterator.Error!?[]const u8 {
135 const self = @fieldParentPtr(OsArgIterator, "iter", iter); 135 const self = @fieldParentPtr(OsArgIterator, "iter", iter);
136 if (builtin.os == builtin.Os.Windows) { 136 if (builtin.os == builtin.Os.windows) {
137 return try self.args.next(allocator); 137 return try self.args.next(allocator) ?? return null;
138 } else { 138 } else {
139 return self.args.nextPoxix(); 139 return self.args.nextPoxix();
140 } 140 }
@@ -161,22 +161,19 @@ pub fn Iterator(comptime Id: type) type {
161 params: []const Param(Id), 161 params: []const Param(Id),
162 inner: &ArgIterator, 162 inner: &ArgIterator,
163 state: State, 163 state: State,
164 command: []const u8,
165 164
166 pub fn init(params: []const Param(Id), inner: &ArgIterator, allocator: &mem.Allocator) !Self { 165 pub fn init(params: []const Param(Id), inner: &ArgIterator, allocator: &mem.Allocator) Self {
167 var res = Self { 166 var res = Self {
168 .arena = heap.ArenaAllocator.init(allocator), 167 .arena = heap.ArenaAllocator.init(allocator),
169 .params = params, 168 .params = params,
170 .inner = inner, 169 .inner = inner,
171 .state = State.Normal, 170 .state = State.Normal,
172 .command = undefined,
173 }; 171 };
174 res.command = (try res.innerNext()) ?? unreachable;
175 172
176 return res; 173 return res;
177 } 174 }
178 175
179 pub fn deinit(iter: &const Self) void { 176 pub fn deinit(iter: &Self) void {
180 iter.arena.deinit(); 177 iter.arena.deinit();
181 } 178 }
182 179
@@ -315,7 +312,7 @@ pub fn Iterator(comptime Id: type) type {
315 312
316fn testNoErr(params: []const Param(u8), args: []const []const u8, ids: []const u8, values: []const ?[]const u8) void { 313fn testNoErr(params: []const Param(u8), args: []const []const u8, ids: []const u8, values: []const ?[]const u8) void {
317 var arg_iter = ArgSliceIterator.init(args); 314 var arg_iter = ArgSliceIterator.init(args);
318 var iter = Iterator(u8).init(params, &arg_iter.iter, debug.global_allocator) catch unreachable; 315 var iter = Iterator(u8).init(params, &arg_iter.iter, debug.global_allocator);
319 316
320 var i: usize = 0; 317 var i: usize = 0;
321 while (iter.next() catch unreachable) |arg| : (i += 1) { 318 while (iter.next() catch unreachable) |arg| : (i += 1) {
@@ -337,15 +334,15 @@ test "clap.parse: short" {
337 Param(u8).init(2, "c", true), 334 Param(u8).init(2, "c", true),
338 }; 335 };
339 336
340 testNoErr(params, [][]const u8 { "command", "-a" }, []u8{0}, []?[]const u8{null}); 337 testNoErr(params, [][]const u8 { "-a" }, []u8{0}, []?[]const u8{null});
341 testNoErr(params, [][]const u8 { "command", "-a", "-b" }, []u8{0,1}, []?[]const u8{null,null}); 338 testNoErr(params, [][]const u8 { "-a", "-b" }, []u8{0,1}, []?[]const u8{null,null});
342 testNoErr(params, [][]const u8 { "command", "-ab" }, []u8{0,1}, []?[]const u8{null,null}); 339 testNoErr(params, [][]const u8 { "-ab" }, []u8{0,1}, []?[]const u8{null,null});
343 testNoErr(params, [][]const u8 { "command", "-c=100" }, []u8{2}, []?[]const u8{"100"}); 340 testNoErr(params, [][]const u8 { "-c=100" }, []u8{2}, []?[]const u8{"100"});
344 testNoErr(params, [][]const u8 { "command", "-c100" }, []u8{2}, []?[]const u8{"100"}); 341 testNoErr(params, [][]const u8 { "-c100" }, []u8{2}, []?[]const u8{"100"});
345 testNoErr(params, [][]const u8 { "command", "-c", "100" }, []u8{2}, []?[]const u8{"100"}); 342 testNoErr(params, [][]const u8 { "-c", "100" }, []u8{2}, []?[]const u8{"100"});
346 testNoErr(params, [][]const u8 { "command", "-abc", "100" }, []u8{0,1,2}, []?[]const u8{null,null,"100"}); 343 testNoErr(params, [][]const u8 { "-abc", "100" }, []u8{0,1,2}, []?[]const u8{null,null,"100"});
347 testNoErr(params, [][]const u8 { "command", "-abc=100" }, []u8{0,1,2}, []?[]const u8{null,null,"100"}); 344 testNoErr(params, [][]const u8 { "-abc=100" }, []u8{0,1,2}, []?[]const u8{null,null,"100"});
348 testNoErr(params, [][]const u8 { "command", "-abc100" }, []u8{0,1,2}, []?[]const u8{null,null,"100"}); 345 testNoErr(params, [][]const u8 { "-abc100" }, []u8{0,1,2}, []?[]const u8{null,null,"100"});
349} 346}
350 347
351test "clap.parse: long" { 348test "clap.parse: long" {
@@ -355,10 +352,10 @@ test "clap.parse: long" {
355 Param(u8).init(2, "cc", true), 352 Param(u8).init(2, "cc", true),
356 }; 353 };
357 354
358 testNoErr(params, [][]const u8 { "command", "--aa" }, []u8{0}, []?[]const u8{null}); 355 testNoErr(params, [][]const u8 { "--aa" }, []u8{0}, []?[]const u8{null});
359 testNoErr(params, [][]const u8 { "command", "--aa", "--bb" }, []u8{0,1}, []?[]const u8{null,null}); 356 testNoErr(params, [][]const u8 { "--aa", "--bb" }, []u8{0,1}, []?[]const u8{null,null});
360 testNoErr(params, [][]const u8 { "command", "--cc=100" }, []u8{2}, []?[]const u8{"100"}); 357 testNoErr(params, [][]const u8 { "--cc=100" }, []u8{2}, []?[]const u8{"100"});
361 testNoErr(params, [][]const u8 { "command", "--cc", "100" }, []u8{2}, []?[]const u8{"100"}); 358 testNoErr(params, [][]const u8 { "--cc", "100" }, []u8{2}, []?[]const u8{"100"});
362} 359}
363 360
364test "clap.parse: both" { 361test "clap.parse: both" {
@@ -368,17 +365,17 @@ test "clap.parse: both" {
368 Param(u8).both(2, 'c', "cc", true), 365 Param(u8).both(2, 'c', "cc", true),
369 }; 366 };
370 367
371 testNoErr(params, [][]const u8 { "command", "-a" }, []u8{0}, []?[]const u8{null}); 368 testNoErr(params, [][]const u8 { "-a" }, []u8{0}, []?[]const u8{null});
372 testNoErr(params, [][]const u8 { "command", "-a", "-b" }, []u8{0,1}, []?[]const u8{null,null}); 369 testNoErr(params, [][]const u8 { "-a", "-b" }, []u8{0,1}, []?[]const u8{null,null});
373 testNoErr(params, [][]const u8 { "command", "-ab" }, []u8{0,1}, []?[]const u8{null,null}); 370 testNoErr(params, [][]const u8 { "-ab" }, []u8{0,1}, []?[]const u8{null,null});
374 testNoErr(params, [][]const u8 { "command", "-c=100" }, []u8{2}, []?[]const u8{"100"}); 371 testNoErr(params, [][]const u8 { "-c=100" }, []u8{2}, []?[]const u8{"100"});
375 testNoErr(params, [][]const u8 { "command", "-c100" }, []u8{2}, []?[]const u8{"100"}); 372 testNoErr(params, [][]const u8 { "-c100" }, []u8{2}, []?[]const u8{"100"});
376 testNoErr(params, [][]const u8 { "command", "-c", "100" }, []u8{2}, []?[]const u8{"100"}); 373 testNoErr(params, [][]const u8 { "-c", "100" }, []u8{2}, []?[]const u8{"100"});
377 testNoErr(params, [][]const u8 { "command", "-abc", "100" }, []u8{0,1,2}, []?[]const u8{null,null,"100"}); 374 testNoErr(params, [][]const u8 { "-abc", "100" }, []u8{0,1,2}, []?[]const u8{null,null,"100"});
378 testNoErr(params, [][]const u8 { "command", "-abc=100" }, []u8{0,1,2}, []?[]const u8{null,null,"100"}); 375 testNoErr(params, [][]const u8 { "-abc=100" }, []u8{0,1,2}, []?[]const u8{null,null,"100"});
379 testNoErr(params, [][]const u8 { "command", "-abc100" }, []u8{0,1,2}, []?[]const u8{null,null,"100"}); 376 testNoErr(params, [][]const u8 { "-abc100" }, []u8{0,1,2}, []?[]const u8{null,null,"100"});
380 testNoErr(params, [][]const u8 { "command", "--aa" }, []u8{0}, []?[]const u8{null}); 377 testNoErr(params, [][]const u8 { "--aa" }, []u8{0}, []?[]const u8{null});
381 testNoErr(params, [][]const u8 { "command", "--aa", "--bb" }, []u8{0,1}, []?[]const u8{null,null}); 378 testNoErr(params, [][]const u8 { "--aa", "--bb" }, []u8{0,1}, []?[]const u8{null,null});
382 testNoErr(params, [][]const u8 { "command", "--cc=100" }, []u8{2}, []?[]const u8{"100"}); 379 testNoErr(params, [][]const u8 { "--cc=100" }, []u8{2}, []?[]const u8{"100"});
383 testNoErr(params, [][]const u8 { "command", "--cc", "100" }, []u8{2}, []?[]const u8{"100"}); 380 testNoErr(params, [][]const u8 { "--cc", "100" }, []u8{2}, []?[]const u8{"100"});
384} 381}
diff --git a/example.exe b/example.exe
new file mode 100644
index 0000000..760b0ec
--- /dev/null
+++ b/example.exe
Binary files differ
diff --git a/example.pdb b/example.pdb
new file mode 100644
index 0000000..6ec0856
--- /dev/null
+++ b/example.pdb
Binary files differ
diff --git a/example.zig b/example.zig
index 4d63b02..14b5487 100644
--- a/example.zig
+++ b/example.zig
@@ -1,12 +1,13 @@
1const std = @import("std"); 1const std = @import("std");
2const clap = @import("clap.zig"); 2const core = @import("core.zig");
3const clap = @import("extended.zig");
3 4
4const debug = std.debug; 5const debug = std.debug;
5const os = std.os; 6const os = std.os;
6 7
7const Clap = clap.Clap; 8const Clap = clap.Clap;
8const Command = clap.Command; 9const Param = clap.Param;
9const Argument = clap.Argument; 10const Parser = clap.Parser;
10 11
11const Options = struct { 12const Options = struct {
12 print_values: bool, 13 print_values: bool,
@@ -34,46 +35,34 @@ const Options = struct {
34// d = V=5 35// d = V=5
35 36
36pub fn main() !void { 37pub fn main() !void {
37 const parser = comptime Clap(Options).init( 38 const parser = comptime Clap(Options) {
38 Options { 39 .defaults = Options {
39 .print_values = false, 40 .print_values = false,
40 .a = 0, 41 .a = 0,
41 .b = 0, 42 .b = 0,
42 .c = 0, 43 .c = 0,
43 .d = "", 44 .d = "",
44 } 45 },
45 ) 46 .params = []Param {
46 .with("program_name", "My Test Command") 47 Param.init("a")
47 .with("author", "Hejsil") 48 .with("takes_value", Parser.int(i64, 10)),
48 .with("version", "v1") 49 Param.init("b")
49 .with("about", "Prints some values to the screen... Maybe.") 50 .with("takes_value", Parser.int(u64, 10)),
50 .with("command", Command.init("command") 51 Param.init("c")
51 .with("arguments", 52 .with("takes_value", Parser.int(u8, 10)),
52 []Argument { 53 Param.init("d")
53 Argument.arg("a") 54 .with("takes_value", Parser.string),
54 .with("help", "Set the a field of Option.") 55 Param.init("print_values")
55 .with("takes_value", clap.parse.int(i64, 10)), 56 .with("short", 'p')
56 Argument.arg("b") 57 .with("long", "print-values"),
57 .with("help", "Set the b field of Option.") 58 }
58 .with("takes_value", clap.parse.int(u64, 10)), 59 };
59 Argument.arg("c")
60 .with("help", "Set the c field of Option.")
61 .with("takes_value", clap.parse.int(u8, 10)),
62 Argument.arg("d")
63 .with("help", "Set the d field of Option.")
64 .with("takes_value", clap.parse.string),
65 Argument.field("print_values")
66 .with("help", "Print all not 0 values.")
67 .with("short", 'p')
68 .with("long", "print-values"),
69 }
70 )
71 );
72 60
73 const args = try os.argsAlloc(debug.global_allocator); 61 var arg_iter = core.OsArgIterator.init();
74 defer os.argsFree(debug.global_allocator, args); 62 const iter = &arg_iter.iter;
63 const command = iter.next(debug.global_allocator);
75 64
76 const options = try parser.parse(args[1..]); 65 const options = try parser.parse(debug.global_allocator, iter);
77 66
78 if (options.print_values) { 67 if (options.print_values) {
79 if (options.a != 0) debug.warn("a = {}\n", options.a); 68 if (options.a != 0) debug.warn("a = {}\n", options.a);
diff --git a/extended.zig b/extended.zig
new file mode 100644
index 0000000..a5c8e89
--- /dev/null
+++ b/extended.zig
@@ -0,0 +1,274 @@
1const builtin = @import("builtin");
2const std = @import("std");
3const core = @import("core.zig");
4
5const mem = std.mem;
6const fmt = std.fmt;
7const debug = std.debug;
8const io = std.io;
9
10const assert = debug.assert;
11
12pub const Param = struct {
13 field: []const u8,
14 short: ?u8,
15 long: ?[]const u8,
16 takes_value: ?Parser,
17 required: bool,
18 position: ?usize,
19
20 pub fn init(name: []const u8) Param {
21 return Param {
22 .field = name,
23 .short = if (name.len == 1) name[0] else null,
24 .long = if (name.len > 1) name else null,
25 .takes_value = null,
26 .required = false,
27 .position = null,
28 };
29 }
30
31 pub fn with(param: &const Param, comptime field_name: []const u8, value: var) Param {
32 var res = *param;
33 @field(res, field_name) = value;
34 return res;
35 }
36};
37
38pub fn Clap(comptime Result: type) type {
39 return struct {
40 const Self = this;
41
42 defaults: Result,
43 params: []const Param,
44
45 pub fn parse(comptime clap: &const Self, allocator: &mem.Allocator, arg_iter: &core.ArgIterator) !Result {
46 var result = clap.defaults;
47 const core_params = comptime blk: {
48 var res: [clap.params.len]core.Param(usize) = undefined;
49
50 for (clap.params) |p, i| {
51 res[i] = core.Param(usize) {
52 .id = i,
53 .short = p.short,
54 .long = p.long,
55 .takes_value = p.takes_value != null,
56 };
57 }
58
59 break :blk res;
60 };
61
62 var handled = comptime blk: {
63 var res: [clap.params.len]bool = undefined;
64 for (clap.params) |p, i| {
65 res[i] = !p.required;
66 }
67
68 break :blk res;
69 };
70
71 var pos: usize = 0;
72 var iter = core.Iterator(usize).init(core_params, arg_iter, allocator);
73 defer iter.deinit();
74 while (try iter.next()) |arg| : (pos += 1) {
75 inline for(clap.params) |param, i| {
76 if (arg.id == i) {
77 if (param.position) |expected| {
78 if (expected != pos)
79 return error.InvalidPosition;
80 }
81
82 if (param.takes_value) |parser| {
83 try parser.parse(&@field(result, param.field), ??arg.value);
84 } else {
85 @field(result, param.field) = true;
86 }
87 handled[i] = true;
88 }
89 }
90 }
91
92 return result;
93 }
94 };
95}
96
97pub const Parser = struct {
98 const UnsafeFunction = &const void;
99
100 FieldType: type,
101 Errors: type,
102 func: UnsafeFunction,
103
104 pub fn init(comptime FieldType: type, comptime Errors: type, func: parseFunc(FieldType, Errors)) Parser {
105 return Parser {
106 .FieldType = FieldType,
107 .Errors = Errors,
108 .func = @ptrCast(UnsafeFunction, func),
109 };
110 }
111
112 fn parse(comptime parser: Parser, field_ptr: takePtr(parser.FieldType), arg: []const u8) parser.Errors!void {
113 return @ptrCast(parseFunc(parser.FieldType, parser.Errors), parser.func)(field_ptr, arg);
114 }
115
116 // TODO: This is a workaround, since we don't have pointer reform yet.
117 fn takePtr(comptime T: type) type { return &T; }
118
119 fn parseFunc(comptime FieldType: type, comptime Errors: type) type {
120 return fn(&FieldType, []const u8) Errors!void;
121 }
122
123 pub fn int(comptime Int: type, comptime radix: u8) Parser {
124 const func = struct {
125 fn i(field_ptr: &Int, arg: []const u8) !void {
126 *field_ptr = try fmt.parseInt(Int, arg, radix);
127 }
128 }.i;
129 return Parser.init(
130 Int,
131 @typeOf(func).ReturnType.ErrorSet,
132 func
133 );
134 }
135
136 const string = Parser.init(
137 []const u8,
138 error{},
139 struct {
140 fn s(field_ptr: &[]const u8, arg: []const u8) (error{}!void) {
141 *field_ptr = arg;
142 }
143 }.s
144 );
145};
146
147
148const Options = struct {
149 str: []const u8,
150 int: i64,
151 uint: u64,
152 a: bool,
153 b: bool,
154 cc: bool,
155
156 pub fn with(op: &const Options, comptime field: []const u8, value: var) Options {
157 var res = *op;
158 @field(res, field) = value;
159 return res;
160 }
161};
162
163const default = Options {
164 .str = "",
165 .int = 0,
166 .uint = 0,
167 .a = false,
168 .b = false,
169 .cc = false,
170};
171
172fn testNoErr(comptime clap: &const Clap(Options), args: []const []const u8, expected: &const Options) void {
173 var arg_iter = core.ArgSliceIterator.init(args);
174 const actual = clap.parse(debug.global_allocator, &arg_iter.iter) catch unreachable;
175 assert(mem.eql(u8, expected.str, actual.str));
176 assert(expected.int == actual.int);
177 assert(expected.uint == actual.uint);
178 assert(expected.a == actual.a);
179 assert(expected.b == actual.b);
180 assert(expected.cc == actual.cc);
181}
182
183fn testErr(comptime clap: &const Clap(Options), args: []const []const u8, expected: error) void {
184 var arg_iter = core.ArgSliceIterator.init(args);
185 if (clap.parse(debug.global_allocator, &arg_iter.iter)) |actual| {
186 unreachable;
187 } else |err| {
188 assert(err == expected);
189 }
190}
191
192test "clap.parse: short" {
193 const clap = comptime Clap(Options) {
194 .defaults = default,
195 .params = []Param {
196 Param.init("a"),
197 Param.init("b"),
198 Param.init("int")
199 .with("short", 'i')
200 .with("takes_value", Parser.int(i64, 10))
201 }
202 };
203
204 testNoErr(clap, [][]const u8 { "-a" }, default.with("a", true));
205 testNoErr(clap, [][]const u8 { "-a", "-b" }, default.with("a", true).with("b", true));
206 testNoErr(clap, [][]const u8 { "-i=100" }, default.with("int", 100));
207 testNoErr(clap, [][]const u8 { "-i100" }, default.with("int", 100));
208 testNoErr(clap, [][]const u8 { "-i", "100" }, default.with("int", 100));
209 testNoErr(clap, [][]const u8 { "-ab" }, default.with("a", true).with("b", true));
210 testNoErr(clap, [][]const u8 { "-abi", "100" }, default.with("a", true).with("b", true).with("int", 100));
211 testNoErr(clap, [][]const u8 { "-abi=100" }, default.with("a", true).with("b", true).with("int", 100));
212 testNoErr(clap, [][]const u8 { "-abi100" }, default.with("a", true).with("b", true).with("int", 100));
213}
214
215test "clap.parse: long" {
216 const clap = comptime Clap(Options) {
217 .defaults = default,
218 .params = []Param {
219 Param.init("cc"),
220 Param.init("int").with("takes_value", Parser.int(i64, 10)),
221 Param.init("uint").with("takes_value", Parser.int(u64, 10)),
222 Param.init("str").with("takes_value", Parser.string),
223 }
224 };
225
226 testNoErr(clap, [][]const u8 { "--cc" }, default.with("cc", true));
227 testNoErr(clap, [][]const u8 { "--int", "100" }, default.with("int", 100));
228}
229
230test "clap.parse: value bool" {
231 const clap = comptime Clap(Options) {
232 .defaults = default,
233 .params = []Param {
234 Param.init("a"),
235 }
236 };
237
238 testNoErr(clap, [][]const u8 { "-a" }, default.with("a", true));
239}
240
241test "clap.parse: value str" {
242 const clap = comptime Clap(Options) {
243 .defaults = default,
244 .params = []Param {
245 Param.init("str").with("takes_value", Parser.string),
246 }
247 };
248
249 testNoErr(clap, [][]const u8 { "--str", "Hello World!" }, default.with("str", "Hello World!"));
250}
251
252test "clap.parse: value int" {
253 const clap = comptime Clap(Options) {
254 .defaults = default,
255 .params = []Param {
256 Param.init("int").with("takes_value", Parser.int(i64, 10)),
257 }
258 };
259
260 testNoErr(clap, [][]const u8 { "--int", "100" }, default.with("int", 100));
261}
262
263test "clap.parse: position" {
264 const clap = comptime Clap(Options) {
265 .defaults = default,
266 .params = []Param {
267 Param.init("a").with("position", 0),
268 Param.init("b").with("position", 1),
269 }
270 };
271
272 testNoErr(clap, [][]const u8 { "-a", "-b" }, default.with("a", true).with("b", true));
273 testErr(clap, [][]const u8 { "-b", "-a" }, error.InvalidPosition);
274}