summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--clap.zig676
1 files changed, 388 insertions, 288 deletions
diff --git a/clap.zig b/clap.zig
index c966b18..b3f859b 100644
--- a/clap.zig
+++ b/clap.zig
@@ -1,4 +1,5 @@
1const std = @import("std"); 1const builtin = @import("builtin");
2const std = @import("std");
2 3
3const mem = std.mem; 4const mem = std.mem;
4const fmt = std.fmt; 5const fmt = std.fmt;
@@ -7,418 +8,517 @@ const io = std.io;
7 8
8const assert = debug.assert; 9const assert = debug.assert;
9 10
10// TODO: 11pub fn Clap(comptime Result: type) type {
11// * Inform the caller which argument caused the error.
12pub fn Option(comptime Result: type, comptime ParseError: type) type {
13 return struct { 12 return struct {
14 const Self = this; 13 const Self = this;
15 14
16 pub const Kind = enum { 15 program_name: []const u8,
17 Optional, 16 author: []const u8,
18 Required, 17 version: []const u8,
19 IgnoresRequired 18 about: []const u8,
20 }; 19 command: Command,
21 20 defaults: Result,
22 parse: fn(&Result, []const u8) ParseError!void,
23 help: []const u8,
24 kind: Kind,
25 takes_value: bool,
26 short: ?u8,
27 long: ?[]const u8,
28
29 pub fn init(parse_fn: fn(&Result, []const u8) ParseError!void) Self {
30 return Self {
31 .parse = parse_fn,
32 .help = "",
33 .kind = Kind.Optional,
34 .takes_value = false,
35 .short = null,
36 .long = null,
37 };
38 }
39 21
40 pub fn setHelp(option: &const Self, help_str: []const u8) Self { 22 pub fn parse(comptime clap: &const Self, arguments: []const []const u8) !Result {
41 var res = *option; res.help = help_str; 23 return clap.command.parse(Result, clap.defaults, arguments);
42 return res;
43 } 24 }
44 25
45 pub fn setKind(option: &const Self, kind: Kind) Self { 26 pub const Builder = struct {
46 var res = *option; res.kind = kind; 27 result: Self,
47 return res; 28
48 } 29 pub fn init(defaults: &const Result) Builder {
30 return Builder {
31 .result = Self {
32 .program_name = "",
33 .author = "",
34 .version = "",
35 .about = "",
36 .command = Command.Builder.init("").build(),
37 .defaults = *defaults,
38 }
39 };
40 }
49 41
50 pub fn takesValue(option: &const Self, takes_value: bool) Self { 42 pub fn programName(builder: &const Builder, name: []const u8) Builder {
51 var res = *option; res.takes_value = takes_value; 43 var res = *builder;
52 return res; 44 res.result.program_name = name;
53 } 45 return res;
46 }
54 47
55 pub fn setShort(option: &const Self, short: u8) Self { 48 pub fn author(builder: &const Builder, name: []const u8) Builder {
56 var res = *option; res.short = short; 49 var res = *builder;
57 return res; 50 res.result.author = name;
58 } 51 return res;
52 }
59 53
60 pub fn setLong(option: &const Self, long: []const u8) Self { 54 pub fn version(builder: &const Builder, version: []const u8) Builder {
61 var res = *option; res.long = long; 55 var res = *builder;
62 return res; 56 res.result.author = version;
63 } 57 return res;
64 }; 58 }
65}
66 59
67pub fn Parser(comptime Result: type, comptime ParseError: type, comptime defaults: &const Result, 60 pub fn about(builder: &const Builder, text: []const u8) Builder {
68 comptime options: []const Option(Result, ParseError)) type { 61 var res = *builder;
62 res.result.about = text;
63 return res;
64 }
69 65
70 const OptionT = Option(Result, ParseError); 66 pub fn command(builder: &const Builder, cmd: &const Command) Builder {
71 const Arg = struct { 67 var res = *builder;
72 const Kind = enum { Long, Short, None }; 68 res.result.command = *cmd;
69 return res;
70 }
73 71
74 arg: []const u8, 72 pub fn build(builder: &const Builder) Self {
75 kind: Kind, 73 return builder.result;
76 after_eql: ?[]const u8, 74 }
75 };
77 }; 76 };
77}
78 78
79 const Iterator = struct { 79pub const Command = struct {
80 slice: []const []const u8, 80 field: ?[]const u8,
81 name: []const u8,
82 arguments: []const Argument,
83 sub_commands: []const Command,
81 84
82 pub fn next(it: &this) ?[]const u8 { 85 pub fn parse(comptime command: &const Command, comptime Result: type, defaults: &const Result, arguments: []const []const u8) !Result {
83 if (it.slice.len == 0) 86 const Arg = struct {
84 return null; 87 const Kind = enum { Long, Short, Value };
85 88
86 defer it.slice = it.slice[1..]; 89 arg: []const u8,
87 return it.slice[0]; 90 kind: Kind,
88 } 91 after_eql: ?[]const u8,
89 }; 92 };
90 93
91 // NOTE: For now, a bitfield is used to keep track of the required arguments. 94 const Iterator = struct {
92 // This limits the user to 128 required arguments, which is more than 95 index: usize,
93 // enough. 96 slice: []const []const u8,
94 const required_mask = comptime blk: {
95 var required_index : u128 = 0;
96 var required_res : u128 = 0;
97 for (options) |option, i| {
98 if (option.kind == OptionT.Kind.Required) {
99 required_res |= 0x1 << required_index;
100 required_index += 1;
101 }
102 }
103 97
104 break :blk required_res; 98 pub fn next(it: &this) ?[]const u8 {
105 }; 99 if (it.index >= it.slice.len)
100 return null;
106 101
107 return struct { 102 defer it.index += 1;
108 fn newRequired(option: &const OptionT, old_required: u128, index: usize) u128 { 103 return it.slice[it.index];
109 switch (option.kind) {
110 OptionT.Kind.Required => {
111 return old_required & ~(u128(1) << u7(index));
112 },
113 OptionT.Kind.IgnoresRequired => return 0,
114 else => return old_required,
115 } 104 }
116 } 105 };
117
118 pub fn parse(args: []const []const u8) !Result {
119 var result = *defaults;
120 var required = required_mask;
121
122 var it = Iterator { .slice = args };
123 while (it.next()) |item| {
124 const arg_info = blk: {
125 var arg = item;
126 var kind = Arg.Kind.None;
127
128 if (mem.startsWith(u8, arg, "--")) {
129 arg = arg[2..];
130 kind = Arg.Kind.Long;
131 } else if (mem.startsWith(u8, arg, "-")) {
132 arg = arg[1..];
133 kind = Arg.Kind.Short;
134 }
135 106
136 if (kind == Arg.Kind.None) 107 // NOTE: For now, a bitfield is used to keep track of the required arguments.
137 break :blk Arg { .arg = arg, .kind = kind, .after_eql = null }; 108 // This limits the user to 128 required arguments, which should be more
109 // than enough.
110 var required = comptime blk: {
111 var required_index : u128 = 0;
112 var required_res : u128 = 0;
113 for (command.arguments) |option| {
114 if (option.required) {
115 required_res |= 0x1 << required_index;
116 required_index += 1;
117 }
118 }
138 119
120 break :blk required_res;
121 };
139 122
140 if (mem.indexOfScalar(u8, arg, '=')) |index| { 123 var result = *defaults;
141 break :blk Arg { .arg = arg[0..index], .kind = kind, .after_eql = arg[index + 1..] };
142 } else {
143 break :blk Arg { .arg = arg, .kind = kind, .after_eql = null };
144 }
145 };
146 const arg = arg_info.arg;
147 const kind = arg_info.kind;
148 const after_eql = arg_info.after_eql;
149 124
150 success: { 125 var it = Iterator { .index = 0, .slice = arguments };
126 while (it.next()) |item| {
127 const arg_info = blk: {
128 var arg = item;
129 var kind = Arg.Kind.Value;
151 130
152 switch (kind) { 131 if (mem.startsWith(u8, arg, "--")) {
153 Arg.Kind.None => { 132 arg = arg[2..];
154 var required_index = usize(0); 133 kind = Arg.Kind.Long;
155 inline for (options) |option| { 134 } else if (mem.startsWith(u8, arg, "-")) {
156 defer if (option.kind == OptionT.Kind.Required) required_index += 1; 135 arg = arg[1..];
157 if (option.short != null) continue; 136 kind = Arg.Kind.Short;
158 if (option.long != null) continue; 137 }
159 138
160 try option.parse(&result, arg); 139 if (kind == Arg.Kind.Value)
161 required = newRequired(option, required, required_index); 140 break :blk Arg { .arg = arg, .kind = kind, .after_eql = null };
162 break :success;
163 }
164 },
165 Arg.Kind.Short => {
166 if (arg.len == 0) return error.FoundShortOptionWithNoName;
167 short_arg_loop: for (arg[0..arg.len - 1]) |short_arg| {
168 var required_index = usize(0);
169 inline for (options) |option| {
170 defer if (option.kind == OptionT.Kind.Required) required_index += 1;
171 const short = option.short ?? continue;
172 if (short_arg == short) {
173 if (option.takes_value) return error.OptionMissingValue;
174
175 try option.parse(&result, []u8{});
176 required = newRequired(option, required, required_index);
177 continue :short_arg_loop;
178 }
179 }
180 141
181 return error.InvalidArgument;
182 }
183 142
184 const last_arg = arg[arg.len - 1]; 143 if (mem.indexOfScalar(u8, arg, '=')) |index| {
144 break :blk Arg { .arg = arg[0..index], .kind = kind, .after_eql = arg[index + 1..] };
145 } else {
146 break :blk Arg { .arg = arg, .kind = kind, .after_eql = null };
147 }
148 };
149 const arg = arg_info.arg;
150 const kind = arg_info.kind;
151 const after_eql = arg_info.after_eql;
152
153 success: {
154 switch (kind) {
155 // TODO: Handle subcommands
156 Arg.Kind.Value => {
157 var required_index = usize(0);
158 inline for (command.arguments) |option| {
159 defer if (option.required) required_index += 1;
160 if (option.short != null) continue;
161 if (option.long != null) continue;
162
163 try option.parse(&result, arg);
164 required = newRequired(option, required, required_index);
165 break :success;
166 }
167 },
168 Arg.Kind.Short => {
169 if (arg.len == 0) return error.FoundShortOptionWithNoName;
170 short_arg_loop: for (arg[0..arg.len - 1]) |short_arg| {
185 var required_index = usize(0); 171 var required_index = usize(0);
186 inline for (options) |option| { 172 inline for (command.arguments) |option| {
187 defer if (option.kind == OptionT.Kind.Required) required_index += 1; 173 defer if (option.required) required_index += 1;
188 const short = option.short ?? continue; 174 const short = option.short ?? continue;
175 if (short_arg == short) {
176 if (option.takes_value) return error.OptionMissingValue;
189 177
190 if (last_arg == short) { 178 *getFieldPtr(Result, &result, option.field) = true;
191 if (option.takes_value) {
192 const value = after_eql ?? it.next() ?? return error.OptionMissingValue;
193 try option.parse(&result, value);
194 } else {
195 try option.parse(&result, []u8{});
196 }
197
198 required = newRequired(option, required, required_index); 179 required = newRequired(option, required, required_index);
199 break :success; 180 continue :short_arg_loop;
200 } 181 }
201 } 182 }
202 },
203 Arg.Kind.Long => {
204 var required_index = usize(0);
205 inline for (options) |option| {
206 defer if (option.kind == OptionT.Kind.Required) required_index += 1;
207 const long = option.long ?? continue;
208 if (mem.eql(u8, arg, long)) {
209 if (option.takes_value) {
210 const value = after_eql ?? it.next() ?? return error.OptionMissingValue;
211 try option.parse(&result, value);
212 } else {
213 try option.parse(&result, []u8{});
214 }
215 183
216 required = newRequired(option, required, required_index); 184 return error.InvalidArgument;
217 break :success; 185 }
186
187 const last_arg = arg[arg.len - 1];
188 var required_index = usize(0);
189 inline for (command.arguments) |option| {
190 defer if (option.required) required_index += 1;
191 const short = option.short ?? continue;
192
193 if (last_arg == short) {
194 if (option.takes_value) {
195 const value = after_eql ?? it.next() ?? return error.OptionMissingValue;
196 *getFieldPtr(Result, &result, option.field) = try strToValue(FieldType(Result, option.field), value);
197 } else {
198 *getFieldPtr(Result, &result, option.field) = true;
218 } 199 }
200
201 required = newRequired(option, required, required_index);
202 break :success;
219 } 203 }
220 } 204 }
221 } 205 },
206 Arg.Kind.Long => {
207 var required_index = usize(0);
208 inline for (command.arguments) |option| {
209 defer if (option.required) required_index += 1;
210 const long = option.long ?? continue;
211
212 if (mem.eql(u8, arg, long)) {
213 if (option.takes_value) {
214 const value = after_eql ?? it.next() ?? return error.OptionMissingValue;
215 *getFieldPtr(Result, &result, option.field) = try strToValue(FieldType(Result, option.field), value);
216 } else {
217 *getFieldPtr(Result, &result, option.field) = true;
218 }
222 219
223 return error.InvalidArgument; 220 required = newRequired(option, required, required_index);
221 break :success;
222 }
223 }
224 }
224 } 225 }
225 }
226 226
227 if (required != 0) { 227 return error.InvalidArgument;
228 return error.RequiredArgumentWasntHandled;
229 } 228 }
229 }
230 230
231 return result; 231 if (required != 0) {
232 return error.RequiredArgumentWasntHandled;
232 } 233 }
233 234
234 // TODO: 235 return result;
235 // * Usage 236 }
236 // * Description
237 pub fn help(out_stream: var) !void {
238 const equal_value : []const u8 = "=OPTION";
239 const longest_long = comptime blk: {
240 var res = usize(0);
241 for (options) |option| {
242 const long = option.long ?? continue;
243 var len = long.len;
244
245 if (option.takes_value)
246 len += equal_value.len;
247
248 if (res < len)
249 res = len;
250 }
251 237
252 break :blk res; 238 fn FieldType(comptime Result: type, comptime field: []const u8) type {
253 }; 239 var i = usize(0);
240 inline while (i < @memberCount(Result)) : (i += 1) {
241 if (mem.eql(u8, @memberName(Result, i), field))
242 return @memberType(Result, i);
243 }
254 244
255 for (options) |option| { 245 @compileError("Field not found!");
256 if (option.short == null and option.long == null) continue; 246 }
257 247
258 try out_stream.print(" "); 248 fn getFieldPtr(comptime Result: type, res: &Result, comptime field: []const u8) &FieldType(Result, field) {
259 if (option.short) |short| { 249 return @intToPtr(&FieldType(Result, field), @ptrToInt(res) + @offsetOf(Result, field));
260 try out_stream.print("-{c}", short); 250 }
261 } else {
262 try out_stream.print(" ");
263 }
264 251
265 if (option.short != null and option.long != null) { 252 fn strToValue(comptime Result: type, str: []const u8) !Result {
266 try out_stream.print(", "); 253 const TypeId = builtin.TypeId;
267 } else { 254 switch (@typeId(Result)) {
268 try out_stream.print(" "); 255 TypeId.Type, TypeId.Void, TypeId.NoReturn, TypeId.Pointer,
269 } 256 TypeId.Array, TypeId.Struct, TypeId.UndefinedLiteral,
257 TypeId.NullLiteral, TypeId.ErrorUnion, TypeId.ErrorSet,
258 TypeId.Union, TypeId.Fn, TypeId.Namespace, TypeId.Block,
259 TypeId.BoundFn, TypeId.ArgTuple, TypeId.Opaque, TypeId.Promise => @compileError("Type not supported!"),
260
261 TypeId.Bool => {
262 if (mem.eql(u8, "true", str))
263 return true;
264 if (mem.eql(u8, "false", str))
265 return false;
266
267 return error.CannotParseStringAsBool;
268 },
269 TypeId.Int, TypeId.IntLiteral => return fmt.parseInt(Result, str, 10),
270 TypeId.Float, TypeId.FloatLiteral => return fmt.parseFloat(Result, str),
271 TypeId.Nullable => {
272 if (mem.eql(u8, "null", str))
273 return null;
274
275 return strToValue(Result.Child, str);
276 },
277 TypeId.Enum => @compileError("TODO: Implement str to enum"),
278 }
279 }
270 280
271 // We need to ident by: 281 fn newRequired(argument: &const Argument, old_required: u128, index: usize) u128 {
272 // "--<longest_long> ".len 282 if (argument.required)
273 const missing_spaces = comptime blk: { 283 return old_required & ~(u128(1) << u7(index));
274 var res = longest_long + 3;
275 if (option.long) |long| {
276 try out_stream.print("--{}", long);
277 res -= 2 + long.len;
278
279 if (option.takes_value) {
280 try out_stream.print("{}", equal_value);
281 res -= equal_value.len;
282 }
283 }
284 284
285 break :blk res; 285 return old_required;
286 }; 286 }
287 287
288 var i = usize(0); 288 pub const Builder = struct {
289 while (i < missing_spaces) : (i += 1) { 289 result: Command,
290 try out_stream.print(" "); 290
291 pub fn init(command_name: []const u8) Builder {
292 return Builder {
293 .result = Command {
294 .field = null,
295 .name = command_name,
296 .arguments = []Argument{ },
297 .sub_commands = []Command{ },
291 } 298 }
292 try out_stream.print("{}\n", option.help); 299 };
293 } 300 }
301
302 pub fn field(builder: &const Builder, field_name: []const u8) Builder {
303 var res = *builder;
304 res.result.field = field_name;
305 return res;
306 }
307
308 pub fn name(builder: &const Builder, n: []const u8) Builder {
309 var res = *builder;
310 res.result.name = n;
311 return res;
312 }
313
314 pub fn arguments(builder: &const Builder, args: []const Argument) Builder {
315 var res = *builder;
316 res.result.arguments = args;
317 return res;
318 }
319
320 pub fn subCommands(builder: &const Builder, commands: []const Command) Builder {
321 var res = *builder;
322 res.result.commands = commands;
323 return res;
324 }
325
326 pub fn build(builder: &const Builder) Command {
327 return builder.result;
294 } 328 }
295 }; 329 };
296} 330};
331
332pub const Argument = struct {
333 field: []const u8,
334 help: []const u8,
335 takes_value: bool,
336 required: bool,
337 short: ?u8,
338 long: ?[]const u8,
339
340 pub const Builder = struct {
341 result: Argument,
342
343 pub fn init(field_name: []const u8) Builder {
344 return Builder {
345 .result = Argument {
346 .field = field_name,
347 .help = "",
348 .takes_value = false,
349 .required = false,
350 .short = null,
351 .long = null,
352 }
353 };
354 }
297 355
298test "clap.parse.Example" { 356 pub fn field(builder: &const Builder, field_name: []const u8) Builder {
299 const Color = struct { 357 var res = *builder;
300 const Self = this; 358 res.result.field = field_name;
359 return res;
360 }
301 361
302 r: u8, g: u8, b: u8, 362 pub fn help(builder: &const Builder, text: []const u8) Builder {
363 var res = *builder;
364 res.result.help = text;
365 return res;
366 }
367
368 pub fn takesValue(builder: &const Builder, takes_value: bool) Builder {
369 var res = *builder;
370 res.result.takes_value = takes_value;
371 return res;
372 }
303 373
304 fn rFromStr(color: &Self, str: []const u8) !void { 374 pub fn required(builder: &const Builder, is_required: bool) Builder {
305 color.r = try fmt.parseInt(u8, str, 10); 375 var res = *builder;
376 res.result.required = is_required;
377 return res;
306 } 378 }
307 379
308 fn gFromStr(color: &Self, str: []const u8) !void { 380 pub fn short(builder: &const Builder, name: u8) Builder {
309 color.g = try fmt.parseInt(u8, str, 10); 381 var res = *builder;
382 res.result.short = name;
383 return res;
310 } 384 }
311 385
312 fn bFromStr(color: &Self, str: []const u8) !void { 386 pub fn long(builder: &const Builder, name: []const u8) Builder {
313 color.b = try fmt.parseInt(u8, str, 10); 387 var res = *builder;
388 res.result.long = name;
389 return res;
314 } 390 }
315 391
316 // TODO: There is a segfault when we try to use the error set: @typeOf(fmt.parseInt).ReturnType.ErrorSet 392 pub fn build(builder: &const Builder) Argument {
317 fn setMax(color: &Self, str: []const u8) !void { 393 return builder.result;
318 color.r = try fmt.parseInt(u8, "255", 10);
319 color.g = try fmt.parseInt(u8, "255", 10);
320 color.b = try fmt.parseInt(u8, "255", 10);
321 } 394 }
322 }; 395 };
323 const Error = @typeOf(Color.setMax).ReturnType.ErrorSet; 396};
397
398test "clap.parse.Example" {
399 const Color = struct {
400 r: u8, g: u8, b: u8, max: bool
401 };
324 402
325 const Case = struct { args: []const []const u8, res: Color, err: ?error }; 403 const Case = struct { args: []const []const u8, res: Color, err: ?error };
326 const cases = []Case { 404 const cases = []Case {
327 Case { 405 Case {
328 .args = [][]const u8 { "-r", "100", "-g", "100", "-b", "100", }, 406 .args = [][]const u8 { "-r", "100", "-g", "100", "-b", "100", },
329 .res = Color { .r = 100, .g = 100, .b = 100 }, 407 .res = Color { .r = 100, .g = 100, .b = 100, .max = false },
330 .err = null, 408 .err = null,
331 }, 409 },
332 Case { 410 Case {
333 .args = [][]const u8 { "--red", "100", "-g", "100", "--blue", "50", }, 411 .args = [][]const u8 { "--red", "100", "-g", "100", "--blue", "50", },
334 .res = Color { .r = 100, .g = 100, .b = 50 }, 412 .res = Color { .r = 100, .g = 100, .b = 50, .max = false },
335 .err = null, 413 .err = null,
336 }, 414 },
337 Case { 415 Case {
338 .args = [][]const u8 { "--red=100", "-g=100", "--blue=50", }, 416 .args = [][]const u8 { "--red=100", "-g=100", "--blue=50", },
339 .res = Color { .r = 100, .g = 100, .b = 50 }, 417 .res = Color { .r = 100, .g = 100, .b = 50, .max = false },
340 .err = null, 418 .err = null,
341 }, 419 },
342 Case { 420 Case {
343 .args = [][]const u8 { "-g", "200", "--blue", "100", "--red", "100", }, 421 .args = [][]const u8 { "-g", "200", "--blue", "100", "--red", "100", },
344 .res = Color { .r = 100, .g = 200, .b = 100 }, 422 .res = Color { .r = 100, .g = 200, .b = 100, .max = false },
345 .err = null, 423 .err = null,
346 }, 424 },
347 Case { 425 Case {
348 .args = [][]const u8 { "-r", "200", "-r", "255" }, 426 .args = [][]const u8 { "-r", "200", "-r", "255" },
349 .res = Color { .r = 255, .g = 0, .b = 0 }, 427 .res = Color { .r = 255, .g = 0, .b = 0, .max = false },
350 .err = null, 428 .err = null,
351 }, 429 },
352 Case { 430 Case {
353 .args = [][]const u8 { "-mr", "100" }, 431 .args = [][]const u8 { "-mr", "100" },
354 .res = Color { .r = 100, .g = 255, .b = 255 }, 432 .res = Color { .r = 100, .g = 0, .b = 0, .max = true },
355 .err = null, 433 .err = null,
356 }, 434 },
357 Case { 435 Case {
358 .args = [][]const u8 { "-mr=100" }, 436 .args = [][]const u8 { "-mr=100" },
359 .res = Color { .r = 100, .g = 255, .b = 255 }, 437 .res = Color { .r = 100, .g = 0, .b = 0, .max = true },
360 .err = null, 438 .err = null,
361 }, 439 },
362 Case { 440 Case {
363 .args = [][]const u8 { "-g", "200", "-b", "255" }, 441 .args = [][]const u8 { "-g", "200", "-b", "255" },
364 .res = Color { .r = 0, .g = 0, .b = 0 }, 442 .res = Color { .r = 0, .g = 0, .b = 0, .max = false },
365 .err = error.RequiredArgumentWasntHandled, 443 .err = error.RequiredArgumentWasntHandled,
366 }, 444 },
367 Case { 445 Case {
368 .args = [][]const u8 { "-p" }, 446 .args = [][]const u8 { "-p" },
369 .res = Color { .r = 0, .g = 0, .b = 0 }, 447 .res = Color { .r = 0, .g = 0, .b = 0, .max = false },
370 .err = error.InvalidArgument, 448 .err = error.InvalidArgument,
371 }, 449 },
372 Case { 450 Case {
373 .args = [][]const u8 { "-g" }, 451 .args = [][]const u8 { "-g" },
374 .res = Color { .r = 0, .g = 0, .b = 0 }, 452 .res = Color { .r = 0, .g = 0, .b = 0, .max = false },
375 .err = error.OptionMissingValue, 453 .err = error.OptionMissingValue,
376 }, 454 },
377 Case { 455 Case {
378 .args = [][]const u8 { "-" }, 456 .args = [][]const u8 { "-" },
379 .res = Color { .r = 0, .g = 0, .b = 0 }, 457 .res = Color { .r = 0, .g = 0, .b = 0, .max = false },
380 .err = error.FoundShortOptionWithNoName, 458 .err = error.FoundShortOptionWithNoName,
381 }, 459 },
382 Case { 460 Case {
383 .args = [][]const u8 { "-rg", "100" }, 461 .args = [][]const u8 { "-rg", "100" },
384 .res = Color { .r = 0, .g = 0, .b = 0 }, 462 .res = Color { .r = 0, .g = 0, .b = 0, .max = false },
385 .err = error.OptionMissingValue, 463 .err = error.OptionMissingValue,
386 }, 464 },
387 }; 465 };
388 466
389 const COption = Option(Color, Error); 467 const clap = comptime Clap(Color).Builder
390 const Clap = Parser(Color, Error, 468 .init(
391 Color { .r = 0, .g = 0, .b = 0 }, 469 Color {
392 comptime []COption { 470 .r = 0,
393 COption.init(Color.rFromStr) 471 .b = 0,
394 .setHelp("The amount of red in our color") 472 .g = 0,
395 .setShort('r') 473 .max = false,
396 .setLong("red") 474 }
397 .takesValue(true) 475 )
398 .setKind(COption.Kind.Required), 476 .command(
399 COption.init(Color.gFromStr) 477 Command.Builder
400 .setHelp("The amount of green in our color") 478 .init("color")
401 .setShort('g') 479 .arguments(
402 .setLong("green") 480 []Argument {
403 .takesValue(true), 481 Argument.Builder
404 COption.init(Color.bFromStr) 482 .init("r")
405 .setHelp("The amount of blue in our color") 483 .help("The amount of red in our color")
406 .setShort('b') 484 .short('r')
407 .setLong("blue") 485 .long("red")
408 .takesValue(true), 486 .takesValue(true)
409 COption.init(Color.setMax) 487 .required(true)
410 .setHelp("Set all values to max") 488 .build(),
411 .setShort('m') 489 Argument.Builder
412 .setLong("max"), 490 .init("g")
413 } 491 .help("The amount of green in our color")
414 ); 492 .short('g')
493 .long("green")
494 .takesValue(true)
495 .build(),
496 Argument.Builder
497 .init("b")
498 .help("The amount of blue in our color")
499 .short('b')
500 .long("blue")
501 .takesValue(true)
502 .build(),
503 Argument.Builder
504 .init("max")
505 .help("Set all values to max")
506 .short('m')
507 .long("max")
508 .build(),
509 }
510 )
511 .build()
512 )
513 .build();
415 514
416 for (cases) |case, i| { 515 for (cases) |case, i| {
417 if (Clap.parse(case.args)) |res| { 516 if (clap.parse(case.args)) |res| {
418 assert(case.err == null); 517 assert(case.err == null);
419 assert(res.r == case.res.r); 518 assert(res.r == case.res.r);
420 assert(res.g == case.res.g); 519 assert(res.g == case.res.g);
421 assert(res.b == case.res.b); 520 assert(res.b == case.res.b);
521 assert(res.max == case.res.max);
422 } else |err| { 522 } else |err| {
423 assert(err == ??case.err); 523 assert(err == ??case.err);
424 } 524 }