summaryrefslogtreecommitdiff
path: root/index.zig
diff options
context:
space:
mode:
Diffstat (limited to 'index.zig')
-rw-r--r--index.zig317
1 files changed, 317 insertions, 0 deletions
diff --git a/index.zig b/index.zig
new file mode 100644
index 0000000..7b82211
--- /dev/null
+++ b/index.zig
@@ -0,0 +1,317 @@
1pub const core = @import("core.zig");
2
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
13pub const Param = struct {
14 field: []const u8,
15 short: ?u8,
16 long: ?[]const u8,
17 takes_value: ?Parser,
18 required: bool,
19 position: ?usize,
20
21 pub fn short(s: u8) Param {
22 return Param{
23 .field = []u8{s},
24 .short = s,
25 .long = null,
26 .takes_value = null,
27 .required = false,
28 .position = null,
29 };
30 }
31
32 pub fn long(l: []const u8) Param {
33 return Param{
34 .field = l,
35 .short = null,
36 .long = l,
37 .takes_value = null,
38 .required = false,
39 .position = null,
40 };
41 }
42
43 pub fn value(f: []const u8) Param {
44 return Param{
45 .field = f,
46 .short = null,
47 .long = null,
48 .takes_value = null,
49 .required = false,
50 .position = null,
51 };
52 }
53
54 /// Initialize a ::Param.
55 /// If ::name.len == 0, then it's a value parameter: "value".
56 /// If ::name.len == 1, then it's a short parameter: "-s".
57 /// If ::name.len > 1, then it's a long parameter: "--long".
58 pub fn smart(name: []const u8) Param {
59 return Param{
60 .field = name,
61 .short = if (name.len == 1) name[0] else null,
62 .long = if (name.len > 1) name else null,
63 .takes_value = null,
64 .required = false,
65 .position = null,
66 };
67 }
68
69 pub fn with(param: &const Param, comptime field_name: []const u8, v: var) Param {
70 var res = *param;
71 @field(res, field_name) = v;
72 return res;
73 }
74};
75
76pub fn Clap(comptime Result: type) type {
77 return struct {
78 const Self = this;
79
80 defaults: Result,
81 params: []const Param,
82
83 pub fn parse(comptime clap: &const Self, allocator: &mem.Allocator, arg_iter: &core.ArgIterator) !Result {
84 var result = clap.defaults;
85 const core_params = comptime blk: {
86 var res: [clap.params.len]core.Param(usize) = undefined;
87
88 for (clap.params) |p, i| {
89 res[i] = core.Param(usize) {
90 .id = i,
91 .command = null,
92 .short = p.short,
93 .long = p.long,
94 .takes_value = p.takes_value != null,
95 };
96 }
97
98 break :blk res;
99 };
100
101 var handled = comptime blk: {
102 var res: [clap.params.len]bool = undefined;
103 for (clap.params) |p, i| {
104 res[i] = !p.required;
105 }
106
107 break :blk res;
108 };
109
110 var pos: usize = 0;
111 var iter = core.Iterator(usize).init(core_params, arg_iter, allocator);
112 defer iter.deinit();
113 while (try iter.next()) |arg| : (pos += 1) {
114 inline for(clap.params) |param, i| {
115 if (arg.id == i) {
116 if (param.position) |expected| {
117 if (expected != pos)
118 return error.InvalidPosition;
119 }
120
121 if (param.takes_value) |parser| {
122 try parser.parse(&@field(result, param.field), ??arg.value);
123 } else {
124 @field(result, param.field) = true;
125 }
126 handled[i] = true;
127 }
128 }
129 }
130
131 return result;
132 }
133 };
134}
135
136pub const Parser = struct {
137 const UnsafeFunction = &const void;
138
139 FieldType: type,
140 Errors: type,
141 func: UnsafeFunction,
142
143 pub fn init(comptime FieldType: type, comptime Errors: type, func: parseFunc(FieldType, Errors)) Parser {
144 return Parser {
145 .FieldType = FieldType,
146 .Errors = Errors,
147 .func = @ptrCast(UnsafeFunction, func),
148 };
149 }
150
151 fn parse(comptime parser: Parser, field_ptr: takePtr(parser.FieldType), arg: []const u8) parser.Errors!void {
152 return @ptrCast(parseFunc(parser.FieldType, parser.Errors), parser.func)(field_ptr, arg);
153 }
154
155 // TODO: This is a workaround, since we don't have pointer reform yet.
156 fn takePtr(comptime T: type) type { return &T; }
157
158 fn parseFunc(comptime FieldType: type, comptime Errors: type) type {
159 return fn(&FieldType, []const u8) Errors!void;
160 }
161
162 pub fn int(comptime Int: type, comptime radix: u8) Parser {
163 const func = struct {
164 fn i(field_ptr: &Int, arg: []const u8) !void {
165 *field_ptr = try fmt.parseInt(Int, arg, radix);
166 }
167 }.i;
168 return Parser.init(
169 Int,
170 @typeOf(func).ReturnType.ErrorSet,
171 func
172 );
173 }
174
175 const string = Parser.init(
176 []const u8,
177 error{},
178 struct {
179 fn s(field_ptr: &[]const u8, arg: []const u8) (error{}!void) {
180 *field_ptr = arg;
181 }
182 }.s
183 );
184};
185
186
187const Options = struct {
188 str: []const u8,
189 int: i64,
190 uint: u64,
191 a: bool,
192 b: bool,
193 cc: bool,
194
195 pub fn with(op: &const Options, comptime field: []const u8, value: var) Options {
196 var res = *op;
197 @field(res, field) = value;
198 return res;
199 }
200};
201
202const default = Options {
203 .str = "",
204 .int = 0,
205 .uint = 0,
206 .a = false,
207 .b = false,
208 .cc = false,
209};
210
211fn testNoErr(comptime clap: &const Clap(Options), args: []const []const u8, expected: &const Options) void {
212 var arg_iter = core.ArgSliceIterator.init(args);
213 const actual = clap.parse(debug.global_allocator, &arg_iter.iter) catch unreachable;
214 assert(mem.eql(u8, expected.str, actual.str));
215 assert(expected.int == actual.int);
216 assert(expected.uint == actual.uint);
217 assert(expected.a == actual.a);
218 assert(expected.b == actual.b);
219 assert(expected.cc == actual.cc);
220}
221
222fn testErr(comptime clap: &const Clap(Options), args: []const []const u8, expected: error) void {
223 var arg_iter = core.ArgSliceIterator.init(args);
224 if (clap.parse(debug.global_allocator, &arg_iter.iter)) |actual| {
225 unreachable;
226 } else |err| {
227 assert(err == expected);
228 }
229}
230
231test "clap.core" {
232 _ = core;
233}
234
235test "clap: short" {
236 const clap = comptime Clap(Options) {
237 .defaults = default,
238 .params = []Param {
239 Param.smart("a"),
240 Param.smart("b"),
241 Param.smart("int")
242 .with("short", 'i')
243 .with("takes_value", Parser.int(i64, 10))
244 }
245 };
246
247 testNoErr(clap, [][]const u8 { "-a" }, default.with("a", true));
248 testNoErr(clap, [][]const u8 { "-a", "-b" }, default.with("a", true).with("b", true));
249 testNoErr(clap, [][]const u8 { "-i=100" }, default.with("int", 100));
250 testNoErr(clap, [][]const u8 { "-i100" }, default.with("int", 100));
251 testNoErr(clap, [][]const u8 { "-i", "100" }, default.with("int", 100));
252 testNoErr(clap, [][]const u8 { "-ab" }, default.with("a", true).with("b", true));
253 testNoErr(clap, [][]const u8 { "-abi", "100" }, default.with("a", true).with("b", true).with("int", 100));
254 testNoErr(clap, [][]const u8 { "-abi=100" }, default.with("a", true).with("b", true).with("int", 100));
255 testNoErr(clap, [][]const u8 { "-abi100" }, default.with("a", true).with("b", true).with("int", 100));
256}
257
258test "clap: long" {
259 const clap = comptime Clap(Options) {
260 .defaults = default,
261 .params = []Param {
262 Param.smart("cc"),
263 Param.smart("int").with("takes_value", Parser.int(i64, 10)),
264 Param.smart("uint").with("takes_value", Parser.int(u64, 10)),
265 Param.smart("str").with("takes_value", Parser.string),
266 }
267 };
268
269 testNoErr(clap, [][]const u8 { "--cc" }, default.with("cc", true));
270 testNoErr(clap, [][]const u8 { "--int", "100" }, default.with("int", 100));
271}
272
273test "clap: value bool" {
274 const clap = comptime Clap(Options) {
275 .defaults = default,
276 .params = []Param {
277 Param.smart("a"),
278 }
279 };
280
281 testNoErr(clap, [][]const u8 { "-a" }, default.with("a", true));
282}
283
284test "clap: value str" {
285 const clap = comptime Clap(Options) {
286 .defaults = default,
287 .params = []Param {
288 Param.smart("str").with("takes_value", Parser.string),
289 }
290 };
291
292 testNoErr(clap, [][]const u8 { "--str", "Hello World!" }, default.with("str", "Hello World!"));
293}
294
295test "clap: value int" {
296 const clap = comptime Clap(Options) {
297 .defaults = default,
298 .params = []Param {
299 Param.smart("int").with("takes_value", Parser.int(i64, 10)),
300 }
301 };
302
303 testNoErr(clap, [][]const u8 { "--int", "100" }, default.with("int", 100));
304}
305
306test "clap: position" {
307 const clap = comptime Clap(Options) {
308 .defaults = default,
309 .params = []Param {
310 Param.smart("a").with("position", 0),
311 Param.smart("b").with("position", 1),
312 }
313 };
314
315 testNoErr(clap, [][]const u8 { "-a", "-b" }, default.with("a", true).with("b", true));
316 testErr(clap, [][]const u8 { "-b", "-a" }, error.InvalidPosition);
317}