summaryrefslogtreecommitdiff
path: root/src/extended.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/extended.zig')
-rw-r--r--src/extended.zig303
1 files changed, 120 insertions, 183 deletions
diff --git a/src/extended.zig b/src/extended.zig
index 31e6455..ffcce5b 100644
--- a/src/extended.zig
+++ b/src/extended.zig
@@ -10,206 +10,34 @@ const io = std.io;
10 10
11const assert = debug.assert; 11const assert = debug.assert;
12 12
13const Opaque = @OpaqueType();
14
15pub const Param = struct { 13pub const Param = struct {
16 field: []const u8, 14 field: []const u8,
17 short: ?u8, 15 names: core.Names,
18 long: ?[]const u8, 16 kind: Kind,
19 takes_value: ?Parser,
20 required: bool, 17 required: bool,
21 position: ?usize, 18 position: ?usize,
22 19
23 pub fn short(s: u8) Param { 20 pub const Kind = union(enum) {
24 return Param{ 21 Flag,
25 .field = []u8{s}, 22 Option: Parser,
26 .short = s, 23 SubCommand: Command,
27 .long = null, 24 };
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}; 25};
77 26
27const Opaque = @OpaqueType();
78pub const Command = struct { 28pub const Command = struct {
79 field: []const u8,
80 name: []const u8,
81 params: []const Param, 29 params: []const Param,
82 sub_commands: []const Command,
83 30
84 Result: type, 31 Result: type,
85 defaults: &const Opaque, 32 default: &const Opaque,
86 parent: ?&const Command,
87 33
88 pub fn init(name: []const u8, comptime Result: type, defaults: &const Result, params: []const Param, sub_commands: []const Command) Command { 34 pub fn init(comptime Result: type, default: &const Result, params: []const Param) Command {
89 return Command{ 35 return Command{
90 .field = name,
91 .name = name,
92 .params = params, 36 .params = params,
93 .sub_commands = sub_commands,
94 .Result = Result, 37 .Result = Result,
95 .defaults = @ptrCast(&const Opaque, defaults), 38 .default = @ptrCast(&const Opaque, default),
96 .parent = null,
97 }; 39 };
98 } 40 }
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: var) !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: var) !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 clap = core.Clap(usize, @typeOf(arg_iter.*).Error).init(core_params, arg_iter);
163
164 arg_loop:
165 while (try clap.next()) |arg| : (pos += 1) {
166 inline for(command.params) |param, i| {
167 comptime const field = "result." ++ param.field;
168
169 if (arg.param.id == i and (param.position ?? pos) == pos) {
170 if (param.takes_value) |parser| {
171 try parser.parse(getFieldPtr(&result, field), ??arg.value);
172 } else {
173 getFieldPtr(&result, field).* = true;
174 }
175 handled[i] = true;
176 continue :arg_loop;
177 }
178 }
179
180 inline for(command.sub_commands) |c, i| {
181 comptime const field = "result." ++ c.field;
182 comptime var sub_command = c;
183 sub_command.parent = command;
184
185 if (arg.param.id == i + command.params.len) {
186 getFieldPtr(&result, field).* = try sub_command.parseHelper(&result, allocator, arg_iter);
187 continue :arg_loop;
188 }
189 }
190
191 return error.InvalidArgument;
192 }
193
194 return result.result;
195 }
196
197 fn GetFieldPtrReturn(comptime Struct: type, comptime field: []const u8) type {
198 var inst: Struct = undefined;
199 const dot_index = comptime mem.indexOfScalar(u8, field, '.') ?? {
200 return @typeOf(&@field(inst, field));
201 };
202
203 return GetFieldPtrReturn(@typeOf(@field(inst, field[0..dot_index])), field[dot_index + 1..]);
204 }
205
206 fn getFieldPtr(curr: var, comptime field: []const u8) GetFieldPtrReturn(@typeOf(curr).Child, field) {
207 const dot_index = comptime mem.indexOfScalar(u8, field, '.') ?? {
208 return &@field(curr, field);
209 };
210
211 return getFieldPtr(&@field(curr, field[0..dot_index]), field[dot_index + 1..]);
212 }
213}; 41};
214 42
215pub const Parser = struct { 43pub const Parser = struct {
@@ -261,3 +89,112 @@ pub const Parser = struct {
261 }.s 89 }.s
262 ); 90 );
263}; 91};
92
93pub fn Clap(comptime Result: type) type {
94 return struct {
95 const Self = this;
96
97 default: Result,
98 params: []const Param,
99
100 pub fn parse(
101 comptime clap: &const Self,
102 comptime Error: type,
103 iter: &core.ArgIterator(Error),
104 ) !Result {
105 // We initialize the core.Clap without any params, and fill them out in parseHelper.
106 var c = core.Clap(usize, Error).init([]core.Param(usize){}, iter);
107
108 const top_level_command = comptime Command.init(Result, &clap.default, clap.params);
109 return try parseHelper(top_level_command, Error, &c);
110 }
111
112 fn parseHelper(
113 comptime command: &const Command,
114 comptime Error: type,
115 clap: &core.Clap(usize, Error),
116 ) !command.Result {
117 var result = @ptrCast(&const command.Result, command.default).*;
118
119 var handled = comptime blk: {
120 var res: [command.params.len]bool = undefined;
121 for (command.params) |p, i| {
122 res[i] = !p.required;
123 }
124
125 break :blk res;
126 };
127
128 // We replace the current clap with the commands parameters, so that we preserve the that
129 // claps state. This is important, as core.Clap could be in a Chaining state, and
130 // constructing a new core.Clap would skip the last chaining arguments.
131 clap.params = comptime blk: {
132 var res: [command.params.len]core.Param(usize) = undefined;
133
134 for (command.params) |p, i| {
135 const id = i;
136 res[id] = core.Param(usize) {
137 .id = id,
138 .takes_value = p.kind == Param.Kind.Option,
139 .names = p.names,
140 };
141 }
142
143 break :blk res;
144 };
145
146 var pos: usize = 0;
147
148 arg_loop:
149 while (try clap.next()) |arg| : (pos += 1) {
150 inline for(command.params) |param, i| {
151 if (arg.param.id == i and (param.position ?? pos) == pos) {
152 handled[i] = true;
153
154 switch (param.kind) {
155 Param.Kind.Flag => {
156 getFieldPtr(&result, param.field).* = true;
157 },
158 Param.Kind.Option => |parser| {
159 try parser.parse(getFieldPtr(&result, param.field), ??arg.value);
160 },
161 Param.Kind.SubCommand => |sub_command| {
162 getFieldPtr(&result, param.field).* = try sub_command.parseHelper(Error, clap);
163
164 // After parsing a subcommand, there should be no arguments left.
165 break :arg_loop;
166 },
167 }
168 continue :arg_loop;
169 }
170 }
171
172 return error.InvalidArgument;
173 }
174
175 for (handled) |h| {
176 if (!h)
177 return error.ParamNotHandled;
178 }
179
180 return result;
181 }
182
183 fn GetFieldPtrReturn(comptime Struct: type, comptime field: []const u8) type {
184 var inst: Struct = undefined;
185 const dot_index = comptime mem.indexOfScalar(u8, field, '.') ?? {
186 return @typeOf(&@field(inst, field));
187 };
188
189 return GetFieldPtrReturn(@typeOf(@field(inst, field[0..dot_index])), field[dot_index + 1..]);
190 }
191
192 fn getFieldPtr(curr: var, comptime field: []const u8) GetFieldPtrReturn(@typeOf(curr).Child, field) {
193 const dot_index = comptime mem.indexOfScalar(u8, field, '.') ?? {
194 return &@field(curr, field);
195 };
196
197 return getFieldPtr(&@field(curr, field[0..dot_index]), field[dot_index + 1..]);
198 }
199 };
200}