summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--clap.zig556
1 files changed, 244 insertions, 312 deletions
diff --git a/clap.zig b/clap.zig
index f664da0..6ba3132 100644
--- a/clap.zig
+++ b/clap.zig
@@ -19,6 +19,23 @@ pub fn Clap(comptime Result: type) type {
19 command: Command, 19 command: Command,
20 defaults: Result, 20 defaults: Result,
21 21
22 pub fn init(defaults: &const Result) Self {
23 return Self {
24 .program_name = "",
25 .author = "",
26 .version = "",
27 .about = "",
28 .command = Command.init(""),
29 .defaults = *defaults,
30 };
31 }
32
33 pub fn with(parser: &const Self, comptime field: []const u8, value: var) Self {
34 var res = *parser;
35 @field(res, field) = value;
36 return res;
37 }
38
22 pub fn parse(comptime clap: &const Self, arguments: []const []const u8) !Result { 39 pub fn parse(comptime clap: &const Self, arguments: []const []const u8) !Result {
23 return parseCommand(CommandList { .command = clap.command, .prev = null }, clap.defaults, arguments); 40 return parseCommand(CommandList { .command = clap.command, .prev = null }, clap.defaults, arguments);
24 } 41 }
@@ -108,12 +125,18 @@ pub fn Clap(comptime Result: type) type {
108 if (option.short != null) continue; 125 if (option.short != null) continue;
109 if (option.long != null) continue; 126 if (option.long != null) continue;
110 127
111 try option.parse(&result, arg); 128 if (option.takes_value) |parser| {
129 try parser.parse(&@field(result, option.field), arg);
130 } else {
131 @field(result, option.field) = true;
132 }
133
112 required = newRequired(option, required, required_index); 134 required = newRequired(option, required, required_index);
113 break :success; 135 break :success;
114 } 136 }
115 }, 137 },
116 Arg.Kind.Short => { 138 Arg.Kind.Short => {
139 const arg_len = arg.len;
117 if (arg.len == 0) return error.FoundShortOptionWithNoName; 140 if (arg.len == 0) return error.FoundShortOptionWithNoName;
118 short_arg_loop: for (arg[0..arg.len - 1]) |short_arg| { 141 short_arg_loop: for (arg[0..arg.len - 1]) |short_arg| {
119 var required_index = usize(0); 142 var required_index = usize(0);
@@ -121,9 +144,9 @@ pub fn Clap(comptime Result: type) type {
121 defer if (option.required) required_index += 1; 144 defer if (option.required) required_index += 1;
122 const short = option.short ?? continue; 145 const short = option.short ?? continue;
123 if (short_arg == short) { 146 if (short_arg == short) {
124 if (option.takes_value) return error.OptionMissingValue; 147 if (option.takes_value) |_| return error.OptionMissingValue;
125 148
126 *getFieldPtr(Result, &result, option.field) = true; 149 @field(result, option.field) = true;
127 required = newRequired(option, required, required_index); 150 required = newRequired(option, required, required_index);
128 continue :short_arg_loop; 151 continue :short_arg_loop;
129 } 152 }
@@ -139,11 +162,11 @@ pub fn Clap(comptime Result: type) type {
139 const short = option.short ?? continue; 162 const short = option.short ?? continue;
140 163
141 if (last_arg == short) { 164 if (last_arg == short) {
142 if (option.takes_value) { 165 if (option.takes_value) |parser| {
143 const value = after_eql ?? it.next() ?? return error.OptionMissingValue; 166 const value = after_eql ?? it.next() ?? return error.OptionMissingValue;
144 *getFieldPtr(Result, &result, option.field) = try strToValue(FieldType(Result, option.field), value); 167 try parser.parse(&@field(result, option.field), value);
145 } else { 168 } else {
146 *getFieldPtr(Result, &result, option.field) = true; 169 @field(result, option.field) = true;
147 } 170 }
148 171
149 required = newRequired(option, required, required_index); 172 required = newRequired(option, required, required_index);
@@ -158,11 +181,11 @@ pub fn Clap(comptime Result: type) type {
158 const long = option.long ?? continue; 181 const long = option.long ?? continue;
159 182
160 if (mem.eql(u8, arg, long)) { 183 if (mem.eql(u8, arg, long)) {
161 if (option.takes_value) { 184 if (option.takes_value) |parser| {
162 const value = after_eql ?? it.next() ?? return error.OptionMissingValue; 185 const value = after_eql ?? it.next() ?? return error.OptionMissingValue;
163 *getFieldPtr(Result, &result, option.field) = try strToValue(FieldType(Result, option.field), value); 186 try parser.parse(&@field(result, option.field), value);
164 } else { 187 } else {
165 *getFieldPtr(Result, &result, option.field) = true; 188 @field(result, option.field) = true;
166 } 189 }
167 190
168 required = newRequired(option, required, required_index); 191 required = newRequired(option, required, required_index);
@@ -183,106 +206,12 @@ pub fn Clap(comptime Result: type) type {
183 return result; 206 return result;
184 } 207 }
185 208
186 fn FieldType(comptime T: type, comptime field: []const u8) type {
187 var i = usize(0);
188 inline while (i < @memberCount(T)) : (i += 1) {
189 if (mem.eql(u8, @memberName(T, i), field))
190 return @memberType(T, i);
191 }
192
193 @compileError("Field not found!");
194 }
195
196 fn getFieldPtr(comptime T: type, res: &T, comptime field: []const u8) &FieldType(T, field) {
197 return @intToPtr(&FieldType(T, field), @ptrToInt(res) + @offsetOf(T, field));
198 }
199
200 fn strToValue(comptime T: type, str: []const u8) !T {
201 const TypeId = builtin.TypeId;
202 switch (@typeId(T)) {
203 TypeId.Type, TypeId.Void, TypeId.NoReturn, TypeId.Pointer,
204 TypeId.Array, TypeId.Struct, TypeId.UndefinedLiteral,
205 TypeId.NullLiteral, TypeId.ErrorUnion, TypeId.ErrorSet,
206 TypeId.Union, TypeId.Fn, TypeId.Namespace, TypeId.Block,
207 TypeId.BoundFn, TypeId.ArgTuple, TypeId.Opaque, TypeId.Promise => @compileError("Type not supported!"),
208
209 TypeId.Bool => {
210 if (mem.eql(u8, "true", str))
211 return true;
212 if (mem.eql(u8, "false", str))
213 return false;
214
215 return error.CannotParseStringAsBool;
216 },
217 TypeId.Int, TypeId.IntLiteral => return fmt.parseInt(T, str, 10),
218 TypeId.Float, TypeId.FloatLiteral => @compileError("TODO: Implement str to float"),
219 TypeId.Nullable => {
220 if (mem.eql(u8, "null", str))
221 return null;
222
223 return strToValue(T.Child, str);
224 },
225 TypeId.Enum => @compileError("TODO: Implement str to enum"),
226 }
227 }
228
229 fn newRequired(argument: &const Argument, old_required: u128, index: usize) u128 { 209 fn newRequired(argument: &const Argument, old_required: u128, index: usize) u128 {
230 if (argument.required) 210 if (argument.required)
231 return old_required & ~(u128(1) << u7(index)); 211 return old_required & ~(u128(1) << u7(index));
232 212
233 return old_required; 213 return old_required;
234 } 214 }
235
236 pub const Builder = struct {
237 result: Self,
238
239 pub fn init(defaults: &const Result) Builder {
240 return Builder {
241 .result = Self {
242 .program_name = "",
243 .author = "",
244 .version = "",
245 .about = "",
246 .command = Command.Builder.init("").build(),
247 .defaults = *defaults,
248 }
249 };
250 }
251
252 pub fn programName(builder: &const Builder, name: []const u8) Builder {
253 var res = *builder;
254 res.result.program_name = name;
255 return res;
256 }
257
258 pub fn author(builder: &const Builder, name: []const u8) Builder {
259 var res = *builder;
260 res.result.author = name;
261 return res;
262 }
263
264 pub fn version(builder: &const Builder, version_str: []const u8) Builder {
265 var res = *builder;
266 res.result.author = version_str;
267 return res;
268 }
269
270 pub fn about(builder: &const Builder, text: []const u8) Builder {
271 var res = *builder;
272 res.result.about = text;
273 return res;
274 }
275
276 pub fn command(builder: &const Builder, cmd: &const Command) Builder {
277 var res = *builder;
278 res.result.command = *cmd;
279 return res;
280 }
281
282 pub fn build(builder: &const Builder) Self {
283 return builder.result;
284 }
285 };
286 }; 215 };
287} 216}
288 217
@@ -292,242 +221,245 @@ pub const Command = struct {
292 arguments: []const Argument, 221 arguments: []const Argument,
293 sub_commands: []const Command, 222 sub_commands: []const Command,
294 223
295 pub const Builder = struct { 224 pub fn init(command_name: []const u8) Command {
296 result: Command, 225 return Command {
226 .field = null,
227 .name = command_name,
228 .arguments = []Argument{ },
229 .sub_commands = []Command{ },
230 };
231 }
297 232
298 pub fn init(command_name: []const u8) Builder { 233 pub fn with(command: &const Command, comptime field: []const u8, value: var) Command {
299 return Builder { 234 var res = *command;
300 .result = Command { 235 @field(res, field) = value;
301 .field = null, 236 return res;
302 .name = command_name, 237 }
303 .arguments = []Argument{ }, 238};
304 .sub_commands = []Command{ },
305 }
306 };
307 }
308 239
309 pub fn field(builder: &const Builder, field_name: []const u8) Builder { 240const Parser = struct {
310 var res = *builder; 241 const UnsafeFunction = &const void;
311 res.result.field = field_name;
312 return res;
313 }
314 242
315 pub fn name(builder: &const Builder, n: []const u8) Builder { 243 FieldType: type,
316 var res = *builder; 244 Errors: type,
317 res.result.name = n; 245 func: UnsafeFunction,
318 return res;
319 }
320 246
321 pub fn arguments(builder: &const Builder, args: []const Argument) Builder { 247 fn init(comptime FieldType: type, comptime Errors: type, func: parseFunc(FieldType, Errors)) Parser {
322 var res = *builder; 248 return Parser {
323 res.result.arguments = args; 249 .FieldType = FieldType,
324 return res; 250 .Errors = Errors,
325 } 251 .func = @ptrCast(UnsafeFunction, func),
252 };
253 }
326 254
327 pub fn subCommands(builder: &const Builder, commands: []const Command) Builder { 255 fn parse(comptime parser: Parser, field_ptr: takePtr(parser.FieldType), arg: []const u8) parser.Errors!void {
328 var res = *builder; 256 return @ptrCast(parseFunc(parser.FieldType, parser.Errors), parser.func)(field_ptr, arg);
329 res.result.commands = commands; 257 }
330 return res;
331 }
332 258
333 pub fn build(builder: &const Builder) Command { 259 // TODO: This is a workaround, since we don't have pointer reform yet.
334 return builder.result; 260 fn takePtr(comptime T: type) type { return &T; }
335 } 261
336 }; 262 fn parseFunc(comptime FieldType: type, comptime Errors: type) type {
263 return fn(&FieldType, []const u8) Errors!void;
264 }
337}; 265};
338 266
339pub const Argument = struct { 267pub const Argument = struct {
340 field: []const u8, 268 field: []const u8,
341 help: []const u8, 269 help: []const u8,
342 takes_value: bool, 270 takes_value: ?Parser,
343 required: bool, 271 required: bool,
344 short: ?u8, 272 short: ?u8,
345 long: ?[]const u8, 273 long: ?[]const u8,
346 274
347 pub const Builder = struct { 275 pub fn field(field_name: []const u8) Argument {
348 result: Argument, 276 return Argument {
349 277 .field = field_name,
350 pub fn init(field_name: []const u8) Builder { 278 .help = "",
351 return Builder { 279 .takes_value = null,
352 .result = Argument { 280 .required = false,
353 .field = field_name, 281 .short = null,
354 .help = "", 282 .long = null,
355 .takes_value = false, 283 };
356 .required = false, 284 }
357 .short = null,
358 .long = null,
359 }
360 };
361 }
362 285
363 pub fn field(builder: &const Builder, field_name: []const u8) Builder { 286 pub fn arg(s: []const u8) Argument {
364 var res = *builder; 287 return Argument {
365 res.result.field = field_name; 288 .field = s,
366 return res; 289 .help = "",
367 } 290 .takes_value = null,
291 .required = false,
292 .short = if (s.len == 1) s[0] else null,
293 .long = if (s.len != 1) s else null,
294 };
295 }
368 296
369 pub fn help(builder: &const Builder, text: []const u8) Builder { 297 pub fn with(argument: &const Argument, comptime field_name: []const u8, value: var) Argument {
370 var res = *builder; 298 var res = *argument;
371 res.result.help = text; 299 @field(res, field_name) = value;
372 return res; 300 return res;
373 } 301 }
302};
374 303
375 pub fn takesValue(builder: &const Builder, takes_value: bool) Builder { 304pub const parse = struct {
376 var res = *builder; 305 pub fn int(comptime Int: type, comptime radix: u8) Parser {
377 res.result.takes_value = takes_value; 306 const func = struct {
378 return res; 307 fn i(field_ptr: &Int, arg: []const u8) !void {
379 } 308 *field_ptr = try fmt.parseInt(Int, arg, radix);
309 }
310 }.i;
311 return Parser.init(
312 Int,
313 @typeOf(func).ReturnType.ErrorSet,
314 func
315 );
316 }
380 317
381 pub fn required(builder: &const Builder, is_required: bool) Builder { 318 const string = Parser.init(
382 var res = *builder; 319 []const u8,
383 res.result.required = is_required; 320 error{},
384 return res; 321 struct {
385 } 322 fn s(field_ptr: &[]const u8, arg: []const u8) (error{}!void) {
323 *field_ptr = arg;
324 }
325 }.s
326 );
327
328 const boolean = Parser.init(
329 []const u8,
330 error{InvalidBoolArg},
331 struct {
332 fn b(comptime T: type, field_ptr: &T, arg: []const u8) (error{InvalidBoolArg}!void) {
333 if (mem.eql(u8, arg, "true")) {
334 *field_ptr = true;
335 } else if (mem.eql(u8, arg, "false")) {
336 *field_ptr = false;
337 } else {
338 return error.InvalidBoolArg;
339 }
340 }
341 }.b
342 );
343};
386 344
387 pub fn short(builder: &const Builder, name: u8) Builder {
388 var res = *builder;
389 res.result.short = name;
390 return res;
391 }
392 345
393 pub fn long(builder: &const Builder, name: []const u8) Builder { 346const Options = struct {
394 var res = *builder; 347 str: []const u8,
395 res.result.long = name; 348 int: i64,
396 return res; 349 uint: u64,
397 } 350 a: bool,
351 b: bool,
352 cc: bool,
398 353
399 pub fn build(builder: &const Builder) Argument { 354 pub fn with(op: &const Options, comptime field: []const u8, value: var) Options {
400 return builder.result; 355 var res = *op;
401 } 356 @field(res, field) = value;
402 }; 357 return res;
358 }
403}; 359};
404 360
405test "clap.parse.Example" { 361const default = Options {
406 const Color = struct { 362 .str = "",
407 r: u8, g: u8, b: u8, max: bool 363 .int = 0,
408 }; 364 .uint = 0,
365 .a = false,
366 .b = false,
367 .cc = false,
368};
409 369
410 const Case = struct { args: []const []const u8, res: Color, err: ?error }; 370fn testNoErr(comptime clap: &const Clap(Options), args: []const []const u8, expected: &const Options) void {
411 const cases = []Case { 371 const actual = clap.parse(args) catch |err| { debug.warn("{}\n", @errorName(err)); unreachable; };
412 Case { 372 assert(mem.eql(u8, expected.str, actual.str));
413 .args = [][]const u8 { "-r", "100", "-g", "100", "-b", "100", }, 373 assert(expected.int == actual.int);
414 .res = Color { .r = 100, .g = 100, .b = 100, .max = false }, 374 assert(expected.uint == actual.uint);
415 .err = null, 375 assert(expected.a == actual.a);
416 }, 376 assert(expected.b == actual.b);
417 Case { 377 assert(expected.cc == actual.cc);
418 .args = [][]const u8 { "--red", "100", "-g", "100", "--blue", "50", }, 378}
419 .res = Color { .r = 100, .g = 100, .b = 50, .max = false },
420 .err = null,
421 },
422 Case {
423 .args = [][]const u8 { "--red=100", "-g=100", "--blue=50", },
424 .res = Color { .r = 100, .g = 100, .b = 50, .max = false },
425 .err = null,
426 },
427 Case {
428 .args = [][]const u8 { "-g", "200", "--blue", "100", "--red", "100", },
429 .res = Color { .r = 100, .g = 200, .b = 100, .max = false },
430 .err = null,
431 },
432 Case {
433 .args = [][]const u8 { "-r", "200", "-r", "255" },
434 .res = Color { .r = 255, .g = 0, .b = 0, .max = false },
435 .err = null,
436 },
437 Case {
438 .args = [][]const u8 { "-mr", "100" },
439 .res = Color { .r = 100, .g = 0, .b = 0, .max = true },
440 .err = null,
441 },
442 Case {
443 .args = [][]const u8 { "-mr=100" },
444 .res = Color { .r = 100, .g = 0, .b = 0, .max = true },
445 .err = null,
446 },
447 Case {
448 .args = [][]const u8 { "-g", "200", "-b", "255" },
449 .res = Color { .r = 0, .g = 0, .b = 0, .max = false },
450 .err = error.RequiredArgumentWasntHandled,
451 },
452 Case {
453 .args = [][]const u8 { "-p" },
454 .res = Color { .r = 0, .g = 0, .b = 0, .max = false },
455 .err = error.InvalidArgument,
456 },
457 Case {
458 .args = [][]const u8 { "-g" },
459 .res = Color { .r = 0, .g = 0, .b = 0, .max = false },
460 .err = error.OptionMissingValue,
461 },
462 Case {
463 .args = [][]const u8 { "-" },
464 .res = Color { .r = 0, .g = 0, .b = 0, .max = false },
465 .err = error.FoundShortOptionWithNoName,
466 },
467 Case {
468 .args = [][]const u8 { "-rg", "100" },
469 .res = Color { .r = 0, .g = 0, .b = 0, .max = false },
470 .err = error.OptionMissingValue,
471 },
472 };
473 379
474 const clap = comptime Clap(Color).Builder 380fn testErr(args: []const []const u8, expected: error) void {
475 .init( 381 if (clap.parse(case.args)) |actual| {
476 Color { 382 unreachable;
477 .r = 0, 383 } else |err| {
478 .b = 0, 384 assert(err == expected);
479 .g = 0, 385 }
480 .max = false, 386}
387
388test "clap.parse: short" {
389 @breakpoint();
390 const clap = comptime Clap(Options).init(default).with("command",
391 Command.init("").with("arguments",
392 []Argument {
393 Argument.arg("a"),
394 Argument.arg("b"),
395 Argument.field("int")
396 .with("short", 'i')
397 .with("takes_value", parse.int(i64, 10))
481 } 398 }
482 ) 399 )
483 .command( 400 );
484 Command.Builder 401
485 .init("color") 402 testNoErr(clap, [][]const u8 { "-a" }, default.with("a", true));
486 .arguments( 403 testNoErr(clap, [][]const u8 { "-a", "-b" }, default.with("a", true).with("b", true));
487 []Argument { 404 testNoErr(clap, [][]const u8 { "-i=100" }, default.with("int", 100));
488 Argument.Builder 405 testNoErr(clap, [][]const u8 { "-i", "100" }, default.with("int", 100));
489 .init("r") 406 testNoErr(clap, [][]const u8 { "-ab" }, default.with("a", true).with("b", true));
490 .help("The amount of red in our color") 407 testNoErr(clap, [][]const u8 { "-abi 100" }, default.with("a", true).with("b", true).with("int", 100));
491 .short('r') 408 testNoErr(clap, [][]const u8 { "-abi=100" }, default.with("a", true).with("b", true).with("int", 100));
492 .long("red") 409}
493 .takesValue(true) 410
494 .required(true) 411test "clap.parse: long" {
495 .build(), 412 @breakpoint();
496 Argument.Builder 413 const clap = comptime Clap(Options).init(default).with("command",
497 .init("g") 414 Command.init("").with("arguments",
498 .help("The amount of green in our color") 415 []Argument {
499 .short('g') 416 Argument.arg("cc"),
500 .long("green") 417 Argument.arg("int").with("takes_value", parse.int(i64, 10)),
501 .takesValue(true) 418 Argument.arg("uint").with("takes_value", parse.int(u64, 10)),
502 .build(), 419 Argument.arg("str").with("takes_value", parse.string),
503 Argument.Builder 420 }
504 .init("b")
505 .help("The amount of blue in our color")
506 .short('b')
507 .long("blue")
508 .takesValue(true)
509 .build(),
510 Argument.Builder
511 .init("max")
512 .help("Set all values to max")
513 .short('m')
514 .long("max")
515 .build(),
516 }
517 )
518 .build()
519 ) 421 )
520 .build(); 422 );
521 423
522 for (cases) |case, i| { 424 testNoErr(clap, [][]const u8 { "--cc" }, default.with("cc", true));
523 if (clap.parse(case.args)) |res| { 425 testNoErr(clap, [][]const u8 { "--int", "100" }, default.with("int", 100));
524 assert(case.err == null); 426}
525 assert(res.r == case.res.r); 427
526 assert(res.g == case.res.g); 428test "clap.parse: value bool" {
527 assert(res.b == case.res.b); 429 @breakpoint();
528 assert(res.max == case.res.max); 430 const clap = comptime Clap(Options).init(default).with("command",
529 } else |err| { 431 Command.init("").with("arguments",
530 assert(err == ??case.err); 432 []Argument {
531 } 433 Argument.field("a"),
532 } 434 }
435 )
436 );
437
438 testNoErr(clap, [][]const u8 { "Hello World!" }, default.with("a", true));
439}
440
441test "clap.parse: value str" {
442 @breakpoint();
443 const clap = comptime Clap(Options).init(default).with("command",
444 Command.init("").with("arguments",
445 []Argument {
446 Argument.field("str").with("takes_value", parse.string),
447 }
448 )
449 );
450
451 testNoErr(clap, [][]const u8 { "Hello World!" }, default.with("str", "Hello World!"));
452}
453
454test "clap.parse: value int" {
455 @breakpoint();
456 const clap = comptime Clap(Options).init(default).with("command",
457 Command.init("").with("arguments",
458 []Argument {
459 Argument.field("int").with("takes_value", parse.int(i64, 10)),
460 }
461 )
462 );
463
464 testNoErr(clap, [][]const u8 { "100" }, default.with("int", 100));
533} 465}