diff options
Diffstat (limited to 'clap.zig')
| -rw-r--r-- | clap.zig | 226 |
1 files changed, 193 insertions, 33 deletions
| @@ -57,10 +57,14 @@ pub fn Param(comptime Id: type) type { | |||
| 57 | /// Takes a string and parses it to a Param(Help). | 57 | /// Takes a string and parses it to a Param(Help). |
| 58 | /// This is the reverse of 'help' but for at single parameter only. | 58 | /// This is the reverse of 'help' but for at single parameter only. |
| 59 | pub fn parseParam(line: []const u8) !Param(Help) { | 59 | pub fn parseParam(line: []const u8) !Param(Help) { |
| 60 | var z: usize = 0; | ||
| 60 | var res = Param(Help){ | 61 | var res = Param(Help){ |
| 61 | .id = Help{ | 62 | .id = Help{ |
| 62 | .msg = line[0..0], | 63 | // For testing, i want to be able to easily compare slices just by pointer, |
| 63 | .value = line[0..0], | 64 | // so I slice by a runtime value here, so that zig does not optimize this |
| 65 | // out. Maybe I should write the test better, geeh. | ||
| 66 | .msg = line[z..z], | ||
| 67 | .value = line[z..z], | ||
| 64 | }, | 68 | }, |
| 65 | }; | 69 | }; |
| 66 | 70 | ||
| @@ -119,6 +123,7 @@ pub fn parseParam(line: []const u8) !Param(Help) { | |||
| 119 | } | 123 | } |
| 120 | 124 | ||
| 121 | test "parseParam" { | 125 | test "parseParam" { |
| 126 | var z: usize = 0; | ||
| 122 | var text: []const u8 = "-s, --long <value> Help text"; | 127 | var text: []const u8 = "-s, --long <value> Help text"; |
| 123 | testing.expectEqual(Param(Help){ | 128 | testing.expectEqual(Param(Help){ |
| 124 | .id = Help{ | 129 | .id = Help{ |
| @@ -162,7 +167,7 @@ test "parseParam" { | |||
| 162 | testing.expectEqual(Param(Help){ | 167 | testing.expectEqual(Param(Help){ |
| 163 | .id = Help{ | 168 | .id = Help{ |
| 164 | .msg = find(text, "Help text"), | 169 | .msg = find(text, "Help text"), |
| 165 | .value = text[0..0], | 170 | .value = text[z..z], |
| 166 | }, | 171 | }, |
| 167 | .names = Names{ | 172 | .names = Names{ |
| 168 | .short = 's', | 173 | .short = 's', |
| @@ -175,7 +180,7 @@ test "parseParam" { | |||
| 175 | testing.expectEqual(Param(Help){ | 180 | testing.expectEqual(Param(Help){ |
| 176 | .id = Help{ | 181 | .id = Help{ |
| 177 | .msg = find(text, "Help text"), | 182 | .msg = find(text, "Help text"), |
| 178 | .value = text[0..0], | 183 | .value = text[z..z], |
| 179 | }, | 184 | }, |
| 180 | .names = Names{ | 185 | .names = Names{ |
| 181 | .short = 's', | 186 | .short = 's', |
| @@ -188,7 +193,7 @@ test "parseParam" { | |||
| 188 | testing.expectEqual(Param(Help){ | 193 | testing.expectEqual(Param(Help){ |
| 189 | .id = Help{ | 194 | .id = Help{ |
| 190 | .msg = find(text, "Help text"), | 195 | .msg = find(text, "Help text"), |
| 191 | .value = text[0..0], | 196 | .value = text[z..z], |
| 192 | }, | 197 | }, |
| 193 | .names = Names{ | 198 | .names = Names{ |
| 194 | .short = null, | 199 | .short = null, |
| @@ -265,25 +270,25 @@ pub fn parse( | |||
| 265 | } | 270 | } |
| 266 | 271 | ||
| 267 | /// Will print a help message in the following format: | 272 | /// Will print a help message in the following format: |
| 268 | /// -s, --long <value_text> help_text | 273 | /// -s, --long <valueText> helpText |
| 269 | /// -s, help_text | 274 | /// -s, helpText |
| 270 | /// -s <value_text> help_text | 275 | /// -s <valueText> helpText |
| 271 | /// --long help_text | 276 | /// --long helpText |
| 272 | /// --long <value_text> help_text | 277 | /// --long <valueText> helpText |
| 273 | pub fn helpFull( | 278 | pub fn helpFull( |
| 274 | stream: var, | 279 | stream: var, |
| 275 | comptime Id: type, | 280 | comptime Id: type, |
| 276 | params: []const Param(Id), | 281 | params: []const Param(Id), |
| 277 | comptime Error: type, | 282 | comptime Error: type, |
| 278 | context: var, | 283 | context: var, |
| 279 | help_text: fn (@TypeOf(context), Param(Id)) Error![]const u8, | 284 | helpText: fn (@TypeOf(context), Param(Id)) Error![]const u8, |
| 280 | value_text: fn (@TypeOf(context), Param(Id)) Error![]const u8, | 285 | valueText: fn (@TypeOf(context), Param(Id)) Error![]const u8, |
| 281 | ) !void { | 286 | ) !void { |
| 282 | const max_spacing = blk: { | 287 | const max_spacing = blk: { |
| 283 | var res: usize = 0; | 288 | var res: usize = 0; |
| 284 | for (params) |param| { | 289 | for (params) |param| { |
| 285 | var counting_stream = io.countingOutStream(io.null_out_stream); | 290 | var counting_stream = io.countingOutStream(io.null_out_stream); |
| 286 | try printParam(&counting_stream.outStream(), Id, param, Error, context, value_text); | 291 | try printParam(counting_stream.outStream(), Id, param, Error, context, valueText); |
| 287 | if (res < counting_stream.bytes_written) | 292 | if (res < counting_stream.bytes_written) |
| 288 | res = counting_stream.bytes_written; | 293 | res = counting_stream.bytes_written; |
| 289 | } | 294 | } |
| @@ -297,9 +302,9 @@ pub fn helpFull( | |||
| 297 | 302 | ||
| 298 | var counting_stream = io.countingOutStream(stream); | 303 | var counting_stream = io.countingOutStream(stream); |
| 299 | try stream.print("\t", .{}); | 304 | try stream.print("\t", .{}); |
| 300 | try printParam(&counting_stream.outStream(), Id, param, Error, context, value_text); | 305 | try printParam(counting_stream.outStream(), Id, param, Error, context, valueText); |
| 301 | try stream.writeByteNTimes(' ', max_spacing - counting_stream.bytes_written); | 306 | try stream.writeByteNTimes(' ', max_spacing - counting_stream.bytes_written); |
| 302 | try stream.print("\t{}\n", .{ try help_text(context, param) }); | 307 | try stream.print("\t{}\n", .{try helpText(context, param)}); |
| 303 | } | 308 | } |
| 304 | } | 309 | } |
| 305 | 310 | ||
| @@ -309,10 +314,10 @@ fn printParam( | |||
| 309 | param: Param(Id), | 314 | param: Param(Id), |
| 310 | comptime Error: type, | 315 | comptime Error: type, |
| 311 | context: var, | 316 | context: var, |
| 312 | value_text: fn (@TypeOf(context), Param(Id)) Error![]const u8, | 317 | valueText: fn (@TypeOf(context), Param(Id)) Error![]const u8, |
| 313 | ) @TypeOf(stream.*).Error!void { | 318 | ) !void { |
| 314 | if (param.names.short) |s| { | 319 | if (param.names.short) |s| { |
| 315 | try stream.print("-{c}", .{ s }); | 320 | try stream.print("-{c}", .{s}); |
| 316 | } else { | 321 | } else { |
| 317 | try stream.print(" ", .{}); | 322 | try stream.print(" ", .{}); |
| 318 | } | 323 | } |
| @@ -323,31 +328,31 @@ fn printParam( | |||
| 323 | try stream.print(" ", .{}); | 328 | try stream.print(" ", .{}); |
| 324 | } | 329 | } |
| 325 | 330 | ||
| 326 | try stream.print("--{}", .{ l }); | 331 | try stream.print("--{}", .{l}); |
| 327 | } | 332 | } |
| 328 | if (param.takes_value) | 333 | if (param.takes_value) |
| 329 | try stream.print(" <{}>", .{ value_text(context, param) }); | 334 | try stream.print(" <{}>", .{valueText(context, param)}); |
| 330 | } | 335 | } |
| 331 | 336 | ||
| 332 | /// A wrapper around helpFull for simple help_text and value_text functions that | 337 | /// A wrapper around helpFull for simple helpText and valueText functions that |
| 333 | /// cant return an error or take a context. | 338 | /// cant return an error or take a context. |
| 334 | pub fn helpEx( | 339 | pub fn helpEx( |
| 335 | stream: var, | 340 | stream: var, |
| 336 | comptime Id: type, | 341 | comptime Id: type, |
| 337 | params: []const Param(Id), | 342 | params: []const Param(Id), |
| 338 | help_text: fn (Param(Id)) []const u8, | 343 | helpText: fn (Param(Id)) []const u8, |
| 339 | value_text: fn (Param(Id)) []const u8, | 344 | valueText: fn (Param(Id)) []const u8, |
| 340 | ) !void { | 345 | ) !void { |
| 341 | const Context = struct { | 346 | const Context = struct { |
| 342 | help_text: fn (Param(Id)) []const u8, | 347 | helpText: fn (Param(Id)) []const u8, |
| 343 | value_text: fn (Param(Id)) []const u8, | 348 | valueText: fn (Param(Id)) []const u8, |
| 344 | 349 | ||
| 345 | pub fn help(c: @This(), p: Param(Id)) error{}![]const u8 { | 350 | pub fn help(c: @This(), p: Param(Id)) error{}![]const u8 { |
| 346 | return c.help_text(p); | 351 | return c.helpText(p); |
| 347 | } | 352 | } |
| 348 | 353 | ||
| 349 | pub fn value(c: @This(), p: Param(Id)) error{}![]const u8 { | 354 | pub fn value(c: @This(), p: Param(Id)) error{}![]const u8 { |
| 350 | return c.value_text(p); | 355 | return c.valueText(p); |
| 351 | } | 356 | } |
| 352 | }; | 357 | }; |
| 353 | 358 | ||
| @@ -357,8 +362,8 @@ pub fn helpEx( | |||
| 357 | params, | 362 | params, |
| 358 | error{}, | 363 | error{}, |
| 359 | Context{ | 364 | Context{ |
| 360 | .help_text = help_text, | 365 | .helpText = helpText, |
| 361 | .value_text = value_text, | 366 | .valueText = valueText, |
| 362 | }, | 367 | }, |
| 363 | Context.help, | 368 | Context.help, |
| 364 | Context.value, | 369 | Context.value, |
| @@ -417,17 +422,172 @@ test "clap.help" { | |||
| 417 | const actual = slice_stream.getWritten(); | 422 | const actual = slice_stream.getWritten(); |
| 418 | if (!mem.eql(u8, actual, expected)) { | 423 | if (!mem.eql(u8, actual, expected)) { |
| 419 | debug.warn("\n============ Expected ============\n", .{}); | 424 | debug.warn("\n============ Expected ============\n", .{}); |
| 420 | debug.warn("{}", .{ expected }); | 425 | debug.warn("{}", .{expected}); |
| 426 | debug.warn("============= Actual =============\n", .{}); | ||
| 427 | debug.warn("{}", .{actual}); | ||
| 428 | |||
| 429 | var buffer: [1024 * 2]u8 = undefined; | ||
| 430 | var fba = std.heap.FixedBufferAllocator.init(&buffer); | ||
| 431 | |||
| 432 | debug.warn("============ Expected (escaped) ============\n", .{}); | ||
| 433 | debug.warn("{x}\n", .{expected}); | ||
| 434 | debug.warn("============ Actual (escaped) ============\n", .{}); | ||
| 435 | debug.warn("{x}\n", .{actual}); | ||
| 436 | testing.expect(false); | ||
| 437 | } | ||
| 438 | } | ||
| 439 | |||
| 440 | /// Will print a usage message in the following format: | ||
| 441 | /// [-abc] [--longa] [-d <valueText>] [--longb <valueText>] <valueText> | ||
| 442 | /// | ||
| 443 | /// First all none value taking parameters, which have a short name are | ||
| 444 | /// printed, then non positional parameters and finally the positinal. | ||
| 445 | pub fn usageFull( | ||
| 446 | stream: var, | ||
| 447 | comptime Id: type, | ||
| 448 | params: []const Param(Id), | ||
| 449 | comptime Error: type, | ||
| 450 | context: var, | ||
| 451 | valueText: fn (@TypeOf(context), Param(Id)) Error![]const u8, | ||
| 452 | ) !void { | ||
| 453 | var cos = io.countingOutStream(stream); | ||
| 454 | const cs = cos.outStream(); | ||
| 455 | for (params) |param| { | ||
| 456 | const name = param.names.short orelse continue; | ||
| 457 | if (param.takes_value) | ||
| 458 | continue; | ||
| 459 | |||
| 460 | if (cos.bytes_written == 0) | ||
| 461 | try stream.writeAll("[-"); | ||
| 462 | try cs.writeByte(name); | ||
| 463 | } | ||
| 464 | if (cos.bytes_written != 0) | ||
| 465 | try cs.writeByte(']'); | ||
| 466 | |||
| 467 | var positional: ?Param(Id) = null; | ||
| 468 | for (params) |param| { | ||
| 469 | if (!param.takes_value and param.names.short != null) | ||
| 470 | continue; | ||
| 471 | |||
| 472 | const prefix = if (param.names.short) |_| "-" else "--"; | ||
| 473 | |||
| 474 | // Seems the zig compiler is being a little wierd. I doesn't allow me to write | ||
| 475 | // @as(*const [1]u8, s) VVVVVVVVVVVVVVVVVVVVVVVVVVVVVV | ||
| 476 | const name = if (param.names.short) |*s| @ptrCast([*]const u8, s)[0..1] else param.names.long orelse { | ||
| 477 | positional = param; | ||
| 478 | continue; | ||
| 479 | }; | ||
| 480 | if (cos.bytes_written != 0) | ||
| 481 | try cs.writeByte(' '); | ||
| 482 | |||
| 483 | try cs.print("[{}{}", .{ prefix, name }); | ||
| 484 | if (param.takes_value) | ||
| 485 | try cs.print(" <{}>", .{try valueText(context, param)}); | ||
| 486 | |||
| 487 | try cs.writeByte(']'); | ||
| 488 | } | ||
| 489 | |||
| 490 | if (positional) |p| { | ||
| 491 | if (cos.bytes_written != 0) | ||
| 492 | try cs.writeByte(' '); | ||
| 493 | try cs.print("<{}>", .{try valueText(context, p)}); | ||
| 494 | } | ||
| 495 | } | ||
| 496 | |||
| 497 | /// A wrapper around usageFull for a simple valueText functions that | ||
| 498 | /// cant return an error or take a context. | ||
| 499 | pub fn usageEx( | ||
| 500 | stream: var, | ||
| 501 | comptime Id: type, | ||
| 502 | params: []const Param(Id), | ||
| 503 | valueText: fn (Param(Id)) []const u8, | ||
| 504 | ) !void { | ||
| 505 | const Context = struct { | ||
| 506 | valueText: fn (Param(Id)) []const u8, | ||
| 507 | |||
| 508 | pub fn value(c: @This(), p: Param(Id)) error{}![]const u8 { | ||
| 509 | return c.valueText(p); | ||
| 510 | } | ||
| 511 | }; | ||
| 512 | |||
| 513 | return usageFull( | ||
| 514 | stream, | ||
| 515 | Id, | ||
| 516 | params, | ||
| 517 | error{}, | ||
| 518 | Context{ .valueText = valueText }, | ||
| 519 | Context.value, | ||
| 520 | ); | ||
| 521 | } | ||
| 522 | |||
| 523 | /// A wrapper around usageEx that takes a Param(Help). | ||
| 524 | pub fn usage(stream: var, params: []const Param(Help)) !void { | ||
| 525 | try usageEx(stream, Help, params, getValueSimple); | ||
| 526 | } | ||
| 527 | |||
| 528 | fn testUsage(expected: []const u8, params: []const Param(Help)) !void { | ||
| 529 | var buf: [1024]u8 = undefined; | ||
| 530 | var fbs = io.fixedBufferStream(&buf); | ||
| 531 | try usage(fbs.outStream(), params); | ||
| 532 | |||
| 533 | const actual = fbs.getWritten(); | ||
| 534 | if (!mem.eql(u8, actual, expected)) { | ||
| 535 | debug.warn("\n============ Expected ============\n", .{}); | ||
| 536 | debug.warn("{}\n", .{expected}); | ||
| 421 | debug.warn("============= Actual =============\n", .{}); | 537 | debug.warn("============= Actual =============\n", .{}); |
| 422 | debug.warn("{}", .{ actual }); | 538 | debug.warn("{}\n", .{actual}); |
| 423 | 539 | ||
| 424 | var buffer: [1024 * 2]u8 = undefined; | 540 | var buffer: [1024 * 2]u8 = undefined; |
| 425 | var fba = std.heap.FixedBufferAllocator.init(&buffer); | 541 | var fba = std.heap.FixedBufferAllocator.init(&buffer); |
| 426 | 542 | ||
| 427 | debug.warn("============ Expected (escaped) ============\n", .{}); | 543 | debug.warn("============ Expected (escaped) ============\n", .{}); |
| 428 | debug.warn("{x}\n", .{ expected }); | 544 | debug.warn("{x}\n", .{expected}); |
| 429 | debug.warn("============ Actual (escaped) ============\n", .{}); | 545 | debug.warn("============ Actual (escaped) ============\n", .{}); |
| 430 | debug.warn("{x}\n", .{ actual }); | 546 | debug.warn("{x}\n", .{actual}); |
| 431 | testing.expect(false); | 547 | testing.expect(false); |
| 432 | } | 548 | } |
| 433 | } | 549 | } |
| 550 | |||
| 551 | test "usage" { | ||
| 552 | @setEvalBranchQuota(100000); | ||
| 553 | try testUsage("[-ab]", comptime &[_]Param(Help){ | ||
| 554 | parseParam("-a") catch unreachable, | ||
| 555 | parseParam("-b") catch unreachable, | ||
| 556 | }); | ||
| 557 | try testUsage("[-a <value>] [-b <v>]", comptime &[_]Param(Help){ | ||
| 558 | parseParam("-a <value>") catch unreachable, | ||
| 559 | parseParam("-b <v>") catch unreachable, | ||
| 560 | }); | ||
| 561 | try testUsage("[--a] [--b]", comptime &[_]Param(Help){ | ||
| 562 | parseParam("--a") catch unreachable, | ||
| 563 | parseParam("--b") catch unreachable, | ||
| 564 | }); | ||
| 565 | try testUsage("[--a <value>] [--b <v>]", comptime &[_]Param(Help){ | ||
| 566 | parseParam("--a <value>") catch unreachable, | ||
| 567 | parseParam("--b <v>") catch unreachable, | ||
| 568 | }); | ||
| 569 | try testUsage("<file>", comptime &[_]Param(Help){ | ||
| 570 | Param(Help){ | ||
| 571 | .id = Help{ | ||
| 572 | .value = "file", | ||
| 573 | }, | ||
| 574 | .takes_value = true, | ||
| 575 | }, | ||
| 576 | }); | ||
| 577 | try testUsage("[-ab] [-c <value>] [-d <v>] [--e] [--f] [--g <value>] [--h <v>] <file>", comptime &[_]Param(Help){ | ||
| 578 | parseParam("-a") catch unreachable, | ||
| 579 | parseParam("-b") catch unreachable, | ||
| 580 | parseParam("-c <value>") catch unreachable, | ||
| 581 | parseParam("-d <v>") catch unreachable, | ||
| 582 | parseParam("--e") catch unreachable, | ||
| 583 | parseParam("--f") catch unreachable, | ||
| 584 | parseParam("--g <value>") catch unreachable, | ||
| 585 | parseParam("--h <v>") catch unreachable, | ||
| 586 | Param(Help){ | ||
| 587 | .id = Help{ | ||
| 588 | .value = "file", | ||
| 589 | }, | ||
| 590 | .takes_value = true, | ||
| 591 | }, | ||
| 592 | }); | ||
| 593 | } | ||