summaryrefslogtreecommitdiff
path: root/src/extended.zig
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/extended.zig264
1 files changed, 264 insertions, 0 deletions
diff --git a/src/extended.zig b/src/extended.zig
new file mode 100644
index 0000000..9427b83
--- /dev/null
+++ b/src/extended.zig
@@ -0,0 +1,264 @@
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
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};