summaryrefslogtreecommitdiff
path: root/index.zig
diff options
context:
space:
mode:
authorGravatar Jimmi HC2018-05-31 11:03:33 +0200
committerGravatar Jimmi HC2018-05-31 11:03:33 +0200
commitfa67eb8b0c1bab8aed441dab33ecf1686f1eb42f (patch)
treed37ad690e760b16e0fc91b22f6764b30514c7295 /index.zig
parentFixed code handling position (diff)
downloadzig-clap-fa67eb8b0c1bab8aed441dab33ecf1686f1eb42f.tar.gz
zig-clap-fa67eb8b0c1bab8aed441dab33ecf1686f1eb42f.tar.xz
zig-clap-fa67eb8b0c1bab8aed441dab33ecf1686f1eb42f.zip
Support for basic sub commands
They cannot access parent params yet
Diffstat (limited to '')
-rw-r--r--index.zig400
1 files changed, 268 insertions, 132 deletions
diff --git a/index.zig b/index.zig
index 02a7658..c7aff0f 100644
--- a/index.zig
+++ b/index.zig
@@ -10,6 +10,8 @@ const io = std.io;
10 10
11const assert = debug.assert; 11const assert = debug.assert;
12 12
13const Opaque = @OpaqueType();
14
13pub const Param = struct { 15pub const Param = struct {
14 field: []const u8, 16 field: []const u8,
15 short: ?u8, 17 short: ?u8,
@@ -67,88 +69,149 @@ pub const Param = struct {
67 } 69 }
68 70
69 pub fn with(param: &const Param, comptime field_name: []const u8, v: var) Param { 71 pub fn with(param: &const Param, comptime field_name: []const u8, v: var) Param {
70 var res = *param; 72 var res = param.*;
71 @field(res, field_name) = v; 73 @field(res, field_name) = v;
72 return res; 74 return res;
73 } 75 }
74}; 76};
75 77
76pub fn Clap(comptime Result: type) type { 78pub const Command = struct {
77 return struct { 79 field: []const u8,
78 const Self = this; 80 name: []const u8,
79 81 params: []const Param,
80 defaults: Result, 82 sub_commands: []const Command,
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 83
98 break :blk res; 84 Result: type,
99 }; 85 defaults: &const Opaque,
86 parent: ?&const Command,
100 87
101 var handled = comptime blk: { 88 pub fn init(name: []const u8, comptime Result: type, defaults: &const Result, params: []const Param, sub_commands: []const Command) Command {
102 var res: [clap.params.len]bool = undefined; 89 return Command{
103 for (clap.params) |p, i| { 90 .field = name,
104 res[i] = !p.required; 91 .name = name,
105 } 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 };
106 117
107 break :blk res; 118 var result = Result{
108 }; 119 .parent = parent,
109 120 .result = @ptrCast(&const command.Result, command.defaults).*,
110 var pos: usize = 0; 121 };
111 var iter = core.Iterator(usize).init(core_params, arg_iter, allocator); 122
112 defer iter.deinit(); 123 // In order for us to wrap the core api, we have to translate clap.Param into core.Param.
113 124 const core_params = comptime blk: {
114 arg_loop: 125 var res: [command.params.len + command.sub_commands.len]core.Param(usize) = undefined;
115 while (try iter.next()) |arg| : (pos += 1) { 126
116 inline for(clap.params) |param, i| { 127 for (command.params) |p, i| {
117 if (arg.id == i and (param.position ?? pos) == pos) { 128 const id = i;
118 if (param.takes_value) |parser| { 129 res[id] = core.Param(usize) {
119 try parser.parse(getFieldPtr(&result, param.field), ??arg.value); 130 .id = id,
120 } else { 131 .command = null,
121 *getFieldPtr(&result, param.field) = true; 132 .short = p.short,
122 } 133 .long = p.long,
123 handled[i] = true; 134 .takes_value = p.takes_value != null,
124 continue :arg_loop; 135 };
136 }
137
138 for (command.sub_commands) |c, i| {
139 const id = i + command.params.len;
140 res[id] = core.Param(usize) {
141 .id = id,
142 .command = c.name,
143 .short = null,
144 .long = null,
145 .takes_value = false,
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.Iterator(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;
125 } 175 }
176 handled[i] = true;
177 continue :arg_loop;
126 } 178 }
179 }
127 180
128 return error.InvalidArgument; 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 }
129 } 190 }
130 191
131 return result; 192 return error.InvalidArgument;
132 } 193 }
133 194
134 fn GetFieldPtrReturn(comptime Struct: type, comptime field: []const u8) type { 195 return result.result;
135 var inst: Struct = undefined; 196 }
136 const dot_index = comptime mem.indexOfScalar(u8, field, '.') ?? {
137 return @typeOf(&@field(inst, field));
138 };
139 197
140 return GetFieldPtrReturn(@typeOf(@field(inst, field[0..dot_index])), field[dot_index + 1..]); 198 fn GetFieldPtrReturn(comptime Struct: type, comptime field: []const u8) type {
141 } 199 var inst: Struct = undefined;
200 const dot_index = comptime mem.indexOfScalar(u8, field, '.') ?? {
201 return @typeOf(&@field(inst, field));
202 };
142 203
143 fn getFieldPtr(curr: var, comptime field: []const u8) GetFieldPtrReturn(@typeOf(curr).Child, field) { 204 return GetFieldPtrReturn(@typeOf(@field(inst, field[0..dot_index])), field[dot_index + 1..]);
144 const dot_index = comptime mem.indexOfScalar(u8, field, '.') ?? { 205 }
145 return &@field(curr, field);
146 };
147 206
148 return getFieldPtr(&@field(curr, field[0..dot_index]), field[dot_index + 1..]); 207 fn getFieldPtr(curr: var, comptime field: []const u8) GetFieldPtrReturn(@typeOf(curr).Child, field) {
149 } 208 const dot_index = comptime mem.indexOfScalar(u8, field, '.') ?? {
150 }; 209 return &@field(curr, field);
151} 210 };
211
212 return getFieldPtr(&@field(curr, field[0..dot_index]), field[dot_index + 1..]);
213 }
214};
152 215
153pub const Parser = struct { 216pub const Parser = struct {
154 const UnsafeFunction = &const void; 217 const UnsafeFunction = &const void;
@@ -179,7 +242,7 @@ pub const Parser = struct {
179 pub fn int(comptime Int: type, comptime radix: u8) Parser { 242 pub fn int(comptime Int: type, comptime radix: u8) Parser {
180 const func = struct { 243 const func = struct {
181 fn i(field_ptr: &Int, arg: []const u8) !void { 244 fn i(field_ptr: &Int, arg: []const u8) !void {
182 *field_ptr = try fmt.parseInt(Int, arg, radix); 245 field_ptr.* = try fmt.parseInt(Int, arg, radix);
183 } 246 }
184 }.i; 247 }.i;
185 return Parser.init( 248 return Parser.init(
@@ -194,7 +257,7 @@ pub const Parser = struct {
194 error{}, 257 error{},
195 struct { 258 struct {
196 fn s(field_ptr: &[]const u8, arg: []const u8) (error{}!void) { 259 fn s(field_ptr: &[]const u8, arg: []const u8) (error{}!void) {
197 *field_ptr = arg; 260 field_ptr.* = arg;
198 } 261 }
199 }.s 262 }.s
200 ); 263 );
@@ -208,9 +271,22 @@ const Options = struct {
208 a: bool, 271 a: bool,
209 b: bool, 272 b: bool,
210 cc: bool, 273 cc: bool,
274 sub: &const SubOptions,
211 275
212 pub fn with(op: &const Options, comptime field: []const u8, value: var) Options { 276 pub fn with(op: &const Options, comptime field: []const u8, value: var) Options {
213 var res = *op; 277 var res = op.*;
278 @field(res, field) = value;
279 return res;
280 }
281};
282
283const SubOptions = struct {
284 a: bool,
285 b: u64,
286 qq: bool,
287
288 pub fn with(op: &const SubOptions, comptime field: []const u8, value: var) SubOptions {
289 var res = op.*;
214 @field(res, field) = value; 290 @field(res, field) = value;
215 return res; 291 return res;
216 } 292 }
@@ -223,117 +299,142 @@ const default = Options {
223 .a = false, 299 .a = false,
224 .b = false, 300 .b = false,
225 .cc = false, 301 .cc = false,
302 .sub = SubOptions{
303 .a = false,
304 .b = 0,
305 .qq = false,
306 },
226}; 307};
227 308
228fn testNoErr(comptime clap: &const Clap(Options), args: []const []const u8, expected: &const Options) void { 309fn testNoErr(comptime command: &const Command, args: []const []const u8, expected: &const command.Result) void {
229 var arg_iter = core.ArgSliceIterator.init(args); 310 var arg_iter = core.ArgSliceIterator.init(args);
230 const actual = clap.parse(debug.global_allocator, &arg_iter.iter) catch unreachable; 311 const actual = command.parse(debug.global_allocator, &arg_iter.iter) catch unreachable;
231 assert(mem.eql(u8, expected.str, actual.str)); 312 assert(mem.eql(u8, expected.str, actual.str));
232 assert(expected.int == actual.int); 313 assert(expected.int == actual.int);
233 assert(expected.uint == actual.uint); 314 assert(expected.uint == actual.uint);
234 assert(expected.a == actual.a); 315 assert(expected.a == actual.a);
235 assert(expected.b == actual.b); 316 assert(expected.b == actual.b);
236 assert(expected.cc == actual.cc); 317 assert(expected.cc == actual.cc);
318 assert(expected.sub.a == actual.sub.a);
319 assert(expected.sub.b == actual.sub.b);
237} 320}
238 321
239fn testErr(comptime clap: &const Clap(Options), args: []const []const u8, expected: error) void { 322fn testErr(comptime command: &const Command, args: []const []const u8, expected: error) void {
240 var arg_iter = core.ArgSliceIterator.init(args); 323 var arg_iter = core.ArgSliceIterator.init(args);
241 if (clap.parse(debug.global_allocator, &arg_iter.iter)) |actual| { 324 if (command.parse(debug.global_allocator, &arg_iter.iter)) |actual| {
242 unreachable; 325 unreachable;
243 } else |err| { 326 } else |err| {
244 assert(err == expected); 327 assert(err == expected);
245 } 328 }
246} 329}
247 330
248test "clap.core" { 331test "command.core" {
249 _ = core; 332 _ = core;
250} 333}
251 334
252test "clap: short" { 335test "command: short" {
253 const clap = comptime Clap(Options) { 336 const command = comptime Command.init(
254 .defaults = default, 337 "",
255 .params = []Param { 338 Options,
339 default,
340 []Param {
256 Param.smart("a"), 341 Param.smart("a"),
257 Param.smart("b"), 342 Param.smart("b"),
258 Param.smart("int") 343 Param.smart("int")
259 .with("short", 'i') 344 .with("short", 'i')
260 .with("takes_value", Parser.int(i64, 10)) 345 .with("takes_value", Parser.int(i64, 10)),
261 } 346 },
262 }; 347 []Command{},
348 );
263 349
264 testNoErr(clap, [][]const u8 { "-a" }, default.with("a", true)); 350 testNoErr(command, [][]const u8 { "-a" }, default.with("a", true));
265 testNoErr(clap, [][]const u8 { "-a", "-b" }, default.with("a", true).with("b", true)); 351 testNoErr(command, [][]const u8 { "-a", "-b" }, default.with("a", true).with("b", true));
266 testNoErr(clap, [][]const u8 { "-i=100" }, default.with("int", 100)); 352 testNoErr(command, [][]const u8 { "-i=100" }, default.with("int", 100));
267 testNoErr(clap, [][]const u8 { "-i100" }, default.with("int", 100)); 353 testNoErr(command, [][]const u8 { "-i100" }, default.with("int", 100));
268 testNoErr(clap, [][]const u8 { "-i", "100" }, default.with("int", 100)); 354 testNoErr(command, [][]const u8 { "-i", "100" }, default.with("int", 100));
269 testNoErr(clap, [][]const u8 { "-ab" }, default.with("a", true).with("b", true)); 355 testNoErr(command, [][]const u8 { "-ab" }, default.with("a", true).with("b", true));
270 testNoErr(clap, [][]const u8 { "-abi", "100" }, default.with("a", true).with("b", true).with("int", 100)); 356 testNoErr(command, [][]const u8 { "-abi", "100" }, default.with("a", true).with("b", true).with("int", 100));
271 testNoErr(clap, [][]const u8 { "-abi=100" }, default.with("a", true).with("b", true).with("int", 100)); 357 testNoErr(command, [][]const u8 { "-abi=100" }, default.with("a", true).with("b", true).with("int", 100));
272 testNoErr(clap, [][]const u8 { "-abi100" }, default.with("a", true).with("b", true).with("int", 100)); 358 testNoErr(command, [][]const u8 { "-abi100" }, default.with("a", true).with("b", true).with("int", 100));
273} 359}
274 360
275test "clap: long" { 361test "command: long" {
276 const clap = comptime Clap(Options) { 362 const command = comptime Command.init(
277 .defaults = default, 363 "",
278 .params = []Param { 364 Options,
365 default,
366 []Param {
279 Param.smart("cc"), 367 Param.smart("cc"),
280 Param.smart("int").with("takes_value", Parser.int(i64, 10)), 368 Param.smart("int").with("takes_value", Parser.int(i64, 10)),
281 Param.smart("uint").with("takes_value", Parser.int(u64, 10)), 369 Param.smart("uint").with("takes_value", Parser.int(u64, 10)),
282 Param.smart("str").with("takes_value", Parser.string), 370 Param.smart("str").with("takes_value", Parser.string),
283 } 371 },
284 }; 372 []Command{},
373 );
285 374
286 testNoErr(clap, [][]const u8 { "--cc" }, default.with("cc", true)); 375 testNoErr(command, [][]const u8 { "--cc" }, default.with("cc", true));
287 testNoErr(clap, [][]const u8 { "--int", "100" }, default.with("int", 100)); 376 testNoErr(command, [][]const u8 { "--int", "100" }, default.with("int", 100));
288} 377}
289 378
290test "clap: value bool" { 379test "command: value bool" {
291 const clap = comptime Clap(Options) { 380 const command = comptime Command.init(
292 .defaults = default, 381 "",
293 .params = []Param { 382 Options,
383 default,
384 []Param {
294 Param.smart("a"), 385 Param.smart("a"),
295 } 386 },
296 }; 387 []Command{},
388 );
297 389
298 testNoErr(clap, [][]const u8 { "-a" }, default.with("a", true)); 390 testNoErr(command, [][]const u8 { "-a" }, default.with("a", true));
299} 391}
300 392
301test "clap: value str" { 393test "command: value str" {
302 const clap = comptime Clap(Options) { 394 const command = comptime Command.init(
303 .defaults = default, 395 "",
304 .params = []Param { 396 Options,
397 default,
398 []Param {
305 Param.smart("str").with("takes_value", Parser.string), 399 Param.smart("str").with("takes_value", Parser.string),
306 } 400 },
307 }; 401 []Command{},
402 );
308 403
309 testNoErr(clap, [][]const u8 { "--str", "Hello World!" }, default.with("str", "Hello World!")); 404 testNoErr(command, [][]const u8 { "--str", "Hello World!" }, default.with("str", "Hello World!"));
310} 405}
311 406
312test "clap: value int" { 407test "command: value int" {
313 const clap = comptime Clap(Options) { 408 const command = comptime Command.init(
314 .defaults = default, 409 "",
315 .params = []Param { 410 Options,
411 default,
412 []Param {
316 Param.smart("int").with("takes_value", Parser.int(i64, 10)), 413 Param.smart("int").with("takes_value", Parser.int(i64, 10)),
317 } 414 },
318 }; 415 []Command{},
416 );
319 417
320 testNoErr(clap, [][]const u8 { "--int", "100" }, default.with("int", 100)); 418 testNoErr(command, [][]const u8 { "--int", "100" }, default.with("int", 100));
321} 419}
322 420
323test "clap: position" { 421test "command: position" {
324 const clap = comptime Clap(Options) { 422 const command = comptime Command.init(
325 .defaults = default, 423 "",
326 .params = []Param { 424 Options,
425 default,
426 []Param {
327 Param.smart("a").with("position", 0), 427 Param.smart("a").with("position", 0),
328 Param.smart("b").with("position", 1), 428 Param.smart("b").with("position", 1),
329 } 429 },
330 }; 430 []Command{},
431 );
331 432
332 testNoErr(clap, [][]const u8 { "-a", "-b" }, default.with("a", true).with("b", true)); 433 testNoErr(command, [][]const u8 { "-a", "-b" }, default.with("a", true).with("b", true));
333 testErr(clap, [][]const u8 { "-b", "-a" }, error.InvalidArgument); 434 testErr(command, [][]const u8 { "-b", "-a" }, error.InvalidArgument);
334} 435}
335 436
336test "clap: sub fields" { 437test "command: sub fields" {
337 const B = struct { 438 const B = struct {
338 a: bool, 439 a: bool,
339 }; 440 };
@@ -341,15 +442,50 @@ test "clap: sub fields" {
341 b: B, 442 b: B,
342 }; 443 };
343 444
344 const clap = comptime Clap(A) { 445 const command = comptime Command.init(
345 .defaults = A { .b = B { .a = false } }, 446 "",
346 .params = []Param { 447 A,
448 A { .b = B { .a = false } },
449 []Param {
347 Param.short('a') 450 Param.short('a')
348 .with("field", "b.a"), 451 .with("field", "b.a"),
349 } 452 },
350 }; 453 []Command{},
454 );
351 455
352 var arg_iter = core.ArgSliceIterator.init([][]const u8{ "-a" }); 456 var arg_iter = core.ArgSliceIterator.init([][]const u8{ "-a" });
353 const res = clap.parse(debug.global_allocator, &arg_iter.iter) catch unreachable; 457 const res = command.parse(debug.global_allocator, &arg_iter.iter) catch unreachable;
354 debug.assert(res.b.a == true); 458 debug.assert(res.b.a == true);
355} 459}
460
461test "command: sub commands" {
462 const command = comptime Command.init(
463 "",
464 Options,
465 default,
466 []Param {
467 Param.smart("a"),
468 Param.smart("b"),
469 },
470 []Command{
471 Command.init(
472 "sub",
473 SubOptions,
474 default.sub,
475 []Param {
476 Param.smart("a"),
477 Param.smart("b")
478 .with("takes_value", Parser.int(u64, 10)),
479 },
480 []Command{},
481 ),
482 },
483 );
484
485 debug.warn("{c}", ??command.params[0].short);
486
487 testNoErr(command, [][]const u8 { "sub", "-a" }, default.with("sub", default.sub.with("a", true)));
488 testNoErr(command, [][]const u8 { "sub", "-b", "100" }, default.with("sub", default.sub.with("b", 100)));
489 testNoErr(command, [][]const u8 { "-a", "sub", "-a" }, default.with("a", true).with("sub", default.sub.with("a", true)));
490 testErr(command, [][]const u8 { "-qq", "sub" }, error.InvalidArgument);
491}