summaryrefslogtreecommitdiff
path: root/extended.zig
diff options
context:
space:
mode:
Diffstat (limited to 'extended.zig')
-rw-r--r--extended.zig274
1 files changed, 274 insertions, 0 deletions
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}