diff options
Diffstat (limited to 'clap.zig')
| -rw-r--r-- | clap.zig | 265 |
1 files changed, 48 insertions, 217 deletions
| @@ -13,8 +13,8 @@ test "clap" { | |||
| 13 | testing.refAllDecls(@This()); | 13 | testing.refAllDecls(@This()); |
| 14 | } | 14 | } |
| 15 | 15 | ||
| 16 | pub const ComptimeClap = @import("clap/comptime.zig").ComptimeClap; | 16 | pub const streaming = @import("clap/streaming.zig"); |
| 17 | pub const StreamingClap = @import("clap/streaming.zig").StreamingClap; | 17 | pub const untyped = @import("clap/untyped.zig"); |
| 18 | 18 | ||
| 19 | /// The names a ::Param can have. | 19 | /// The names a ::Param can have. |
| 20 | pub const Names = struct { | 20 | pub const Names = struct { |
| @@ -23,6 +23,23 @@ pub const Names = struct { | |||
| 23 | 23 | ||
| 24 | /// '--' prefix | 24 | /// '--' prefix |
| 25 | long: ?[]const u8 = null, | 25 | long: ?[]const u8 = null, |
| 26 | |||
| 27 | pub fn longest(names: *const Names) Longest { | ||
| 28 | if (names.long) |long| | ||
| 29 | return .{ .kind = .long, .name = long }; | ||
| 30 | if (names.short) |*short| { | ||
| 31 | // TODO: Zig cannot figure out @as(*const [1]u8, short) in the ano literal | ||
| 32 | const casted: *const [1]u8 = short; | ||
| 33 | return .{ .kind = .short, .name = casted }; | ||
| 34 | } | ||
| 35 | |||
| 36 | return .{ .kind = .positinal, .name = "" }; | ||
| 37 | } | ||
| 38 | |||
| 39 | pub const Longest = struct { | ||
| 40 | kind: enum { long, short, positinal }, | ||
| 41 | name: []const u8, | ||
| 42 | }; | ||
| 26 | }; | 43 | }; |
| 27 | 44 | ||
| 28 | /// Whether a param takes no value (a flag), one value, or can be specified multiple times. | 45 | /// Whether a param takes no value (a flag), one value, or can be specified multiple times. |
| @@ -61,155 +78,6 @@ pub fn Param(comptime Id: type) type { | |||
| 61 | }; | 78 | }; |
| 62 | } | 79 | } |
| 63 | 80 | ||
| 64 | /// Takes a string and parses it to a Param(Help). | ||
| 65 | /// This is the reverse of 'help' but for at single parameter only. | ||
| 66 | pub fn parseParam(line: []const u8) !Param(Help) { | ||
| 67 | // This function become a lot less ergonomic to use once you hit the eval branch quota. To | ||
| 68 | // avoid this we pick a sane default. Sadly, the only sane default is the biggest possible | ||
| 69 | // value. If we pick something a lot smaller and a user hits the quota after that, they have | ||
| 70 | // no way of overriding it, since we set it here. | ||
| 71 | // We can recosider this again if: | ||
| 72 | // * We get parseParams: https://github.com/Hejsil/zig-clap/issues/39 | ||
| 73 | // * We get a larger default branch quota in the zig compiler (stage 2). | ||
| 74 | // * Someone points out how this is a really bad idea. | ||
| 75 | @setEvalBranchQuota(std.math.maxInt(u32)); | ||
| 76 | |||
| 77 | var found_comma = false; | ||
| 78 | var it = mem.tokenize(u8, line, " \t"); | ||
| 79 | var param_str = it.next() orelse return error.NoParamFound; | ||
| 80 | |||
| 81 | const short_name = if (!mem.startsWith(u8, param_str, "--") and | ||
| 82 | mem.startsWith(u8, param_str, "-")) | ||
| 83 | blk: { | ||
| 84 | found_comma = param_str[param_str.len - 1] == ','; | ||
| 85 | if (found_comma) | ||
| 86 | param_str = param_str[0 .. param_str.len - 1]; | ||
| 87 | |||
| 88 | if (param_str.len != 2) | ||
| 89 | return error.InvalidShortParam; | ||
| 90 | |||
| 91 | const short_name = param_str[1]; | ||
| 92 | if (!found_comma) { | ||
| 93 | var res = parseParamRest(it.rest()); | ||
| 94 | res.names.short = short_name; | ||
| 95 | return res; | ||
| 96 | } | ||
| 97 | |||
| 98 | param_str = it.next() orelse return error.NoParamFound; | ||
| 99 | break :blk short_name; | ||
| 100 | } else null; | ||
| 101 | |||
| 102 | const long_name = if (mem.startsWith(u8, param_str, "--")) blk: { | ||
| 103 | if (param_str[param_str.len - 1] == ',') | ||
| 104 | return error.TrailingComma; | ||
| 105 | |||
| 106 | break :blk param_str[2..]; | ||
| 107 | } else if (found_comma) { | ||
| 108 | return error.TrailingComma; | ||
| 109 | } else if (short_name == null) { | ||
| 110 | return parseParamRest(mem.trimLeft(u8, line, " \t")); | ||
| 111 | } else null; | ||
| 112 | |||
| 113 | var res = parseParamRest(it.rest()); | ||
| 114 | res.names.long = long_name; | ||
| 115 | res.names.short = short_name; | ||
| 116 | return res; | ||
| 117 | } | ||
| 118 | |||
| 119 | fn parseParamRest(line: []const u8) Param(Help) { | ||
| 120 | if (mem.startsWith(u8, line, "<")) blk: { | ||
| 121 | const len = mem.indexOfScalar(u8, line, '>') orelse break :blk; | ||
| 122 | const takes_many = mem.startsWith(u8, line[len + 1 ..], "..."); | ||
| 123 | const help_start = len + 1 + @as(usize, 3) * @boolToInt(takes_many); | ||
| 124 | return .{ | ||
| 125 | .takes_value = if (takes_many) .many else .one, | ||
| 126 | .id = .{ | ||
| 127 | .msg = mem.trim(u8, line[help_start..], " \t"), | ||
| 128 | .value = line[1..len], | ||
| 129 | }, | ||
| 130 | }; | ||
| 131 | } | ||
| 132 | |||
| 133 | return .{ .id = .{ .msg = mem.trim(u8, line, " \t") } }; | ||
| 134 | } | ||
| 135 | |||
| 136 | fn expectParam(expect: Param(Help), actual: Param(Help)) !void { | ||
| 137 | try testing.expectEqualStrings(expect.id.msg, actual.id.msg); | ||
| 138 | try testing.expectEqualStrings(expect.id.value, actual.id.value); | ||
| 139 | try testing.expectEqual(expect.names.short, actual.names.short); | ||
| 140 | try testing.expectEqual(expect.takes_value, actual.takes_value); | ||
| 141 | if (expect.names.long) |long| { | ||
| 142 | try testing.expectEqualStrings(long, actual.names.long.?); | ||
| 143 | } else { | ||
| 144 | try testing.expectEqual(@as(?[]const u8, null), actual.names.long); | ||
| 145 | } | ||
| 146 | } | ||
| 147 | |||
| 148 | test "parseParam" { | ||
| 149 | try expectParam(Param(Help){ | ||
| 150 | .id = .{ .msg = "Help text", .value = "value" }, | ||
| 151 | .names = .{ .short = 's', .long = "long" }, | ||
| 152 | .takes_value = .one, | ||
| 153 | }, try parseParam("-s, --long <value> Help text")); | ||
| 154 | |||
| 155 | try expectParam(Param(Help){ | ||
| 156 | .id = .{ .msg = "Help text", .value = "value" }, | ||
| 157 | .names = .{ .short = 's', .long = "long" }, | ||
| 158 | .takes_value = .many, | ||
| 159 | }, try parseParam("-s, --long <value>... Help text")); | ||
| 160 | |||
| 161 | try expectParam(Param(Help){ | ||
| 162 | .id = .{ .msg = "Help text", .value = "value" }, | ||
| 163 | .names = .{ .long = "long" }, | ||
| 164 | .takes_value = .one, | ||
| 165 | }, try parseParam("--long <value> Help text")); | ||
| 166 | |||
| 167 | try expectParam(Param(Help){ | ||
| 168 | .id = .{ .msg = "Help text", .value = "value" }, | ||
| 169 | .names = .{ .short = 's' }, | ||
| 170 | .takes_value = .one, | ||
| 171 | }, try parseParam("-s <value> Help text")); | ||
| 172 | |||
| 173 | try expectParam(Param(Help){ | ||
| 174 | .id = .{ .msg = "Help text" }, | ||
| 175 | .names = .{ .short = 's', .long = "long" }, | ||
| 176 | }, try parseParam("-s, --long Help text")); | ||
| 177 | |||
| 178 | try expectParam(Param(Help){ | ||
| 179 | .id = .{ .msg = "Help text" }, | ||
| 180 | .names = .{ .short = 's' }, | ||
| 181 | }, try parseParam("-s Help text")); | ||
| 182 | |||
| 183 | try expectParam(Param(Help){ | ||
| 184 | .id = .{ .msg = "Help text" }, | ||
| 185 | .names = .{ .long = "long" }, | ||
| 186 | }, try parseParam("--long Help text")); | ||
| 187 | |||
| 188 | try expectParam(Param(Help){ | ||
| 189 | .id = .{ .msg = "Help text", .value = "A | B" }, | ||
| 190 | .names = .{ .long = "long" }, | ||
| 191 | .takes_value = .one, | ||
| 192 | }, try parseParam("--long <A | B> Help text")); | ||
| 193 | |||
| 194 | try expectParam(Param(Help){ | ||
| 195 | .id = .{ .msg = "Help text", .value = "A" }, | ||
| 196 | .names = .{}, | ||
| 197 | .takes_value = .one, | ||
| 198 | }, try parseParam("<A> Help text")); | ||
| 199 | |||
| 200 | try expectParam(Param(Help){ | ||
| 201 | .id = .{ .msg = "Help text", .value = "A" }, | ||
| 202 | .names = .{}, | ||
| 203 | .takes_value = .many, | ||
| 204 | }, try parseParam("<A>... Help text")); | ||
| 205 | |||
| 206 | try testing.expectError(error.TrailingComma, parseParam("--long, Help")); | ||
| 207 | try testing.expectError(error.TrailingComma, parseParam("-s, Help")); | ||
| 208 | try testing.expectError(error.InvalidShortParam, parseParam("-ss Help")); | ||
| 209 | try testing.expectError(error.InvalidShortParam, parseParam("-ss <value> Help")); | ||
| 210 | try testing.expectError(error.InvalidShortParam, parseParam("- Help")); | ||
| 211 | } | ||
| 212 | |||
| 213 | /// Optional diagnostics used for reporting useful errors | 81 | /// Optional diagnostics used for reporting useful errors |
| 214 | pub const Diagnostic = struct { | 82 | pub const Diagnostic = struct { |
| 215 | arg: []const u8 = "", | 83 | arg: []const u8 = "", |
| @@ -306,7 +174,7 @@ test "Diagnostic.report" { | |||
| 306 | pub fn Args(comptime Id: type, comptime params: []const Param(Id)) type { | 174 | pub fn Args(comptime Id: type, comptime params: []const Param(Id)) type { |
| 307 | return struct { | 175 | return struct { |
| 308 | arena: std.heap.ArenaAllocator, | 176 | arena: std.heap.ArenaAllocator, |
| 309 | clap: ComptimeClap(Id, params), | 177 | clap: untyped.Clap(Id, params), |
| 310 | exe_arg: ?[]const u8, | 178 | exe_arg: ?[]const u8, |
| 311 | 179 | ||
| 312 | pub fn deinit(a: *@This()) void { | 180 | pub fn deinit(a: *@This()) void { |
| @@ -342,43 +210,6 @@ pub const ParseOptions = struct { | |||
| 342 | diagnostic: ?*Diagnostic = null, | 210 | diagnostic: ?*Diagnostic = null, |
| 343 | }; | 211 | }; |
| 344 | 212 | ||
| 345 | /// Same as `parseEx` but uses the `args.OsIterator` by default. | ||
| 346 | pub fn parse( | ||
| 347 | comptime Id: type, | ||
| 348 | comptime params: []const Param(Id), | ||
| 349 | opt: ParseOptions, | ||
| 350 | ) !Args(Id, params) { | ||
| 351 | var arena = heap.ArenaAllocator.init(opt.allocator); | ||
| 352 | errdefer arena.deinit(); | ||
| 353 | |||
| 354 | var iter = try process.ArgIterator.initWithAllocator(arena.allocator()); | ||
| 355 | const exe_arg = iter.next(); | ||
| 356 | |||
| 357 | const clap = try parseEx(Id, params, &iter, .{ | ||
| 358 | // Let's reuse the arena from the `OSIterator` since we already have it. | ||
| 359 | .allocator = arena.allocator(), | ||
| 360 | .diagnostic = opt.diagnostic, | ||
| 361 | }); | ||
| 362 | |||
| 363 | return Args(Id, params){ | ||
| 364 | .exe_arg = exe_arg, | ||
| 365 | .arena = arena, | ||
| 366 | .clap = clap, | ||
| 367 | }; | ||
| 368 | } | ||
| 369 | |||
| 370 | /// Parses the command line arguments passed into the program based on an | ||
| 371 | /// array of `Param`s. | ||
| 372 | pub fn parseEx( | ||
| 373 | comptime Id: type, | ||
| 374 | comptime params: []const Param(Id), | ||
| 375 | iter: anytype, | ||
| 376 | opt: ParseOptions, | ||
| 377 | ) !ComptimeClap(Id, params) { | ||
| 378 | const Clap = ComptimeClap(Id, params); | ||
| 379 | return try Clap.parse(iter, opt); | ||
| 380 | } | ||
| 381 | |||
| 382 | /// Will print a help message in the following format: | 213 | /// Will print a help message in the following format: |
| 383 | /// -s, --long <valueText> helpText | 214 | /// -s, --long <valueText> helpText |
| 384 | /// -s, helpText | 215 | /// -s, helpText |
| @@ -526,15 +357,15 @@ test "clap.help" { | |||
| 526 | try help( | 357 | try help( |
| 527 | slice_stream.writer(), | 358 | slice_stream.writer(), |
| 528 | comptime &.{ | 359 | comptime &.{ |
| 529 | parseParam("-a Short flag.") catch unreachable, | 360 | untyped.parseParam("-a Short flag.") catch unreachable, |
| 530 | parseParam("-b <V1> Short option.") catch unreachable, | 361 | untyped.parseParam("-b <V1> Short option.") catch unreachable, |
| 531 | parseParam("--aa Long flag.") catch unreachable, | 362 | untyped.parseParam("--aa Long flag.") catch unreachable, |
| 532 | parseParam("--bb <V2> Long option.") catch unreachable, | 363 | untyped.parseParam("--bb <V2> Long option.") catch unreachable, |
| 533 | parseParam("-c, --cc Both flag.") catch unreachable, | 364 | untyped.parseParam("-c, --cc Both flag.") catch unreachable, |
| 534 | parseParam("--complicate Flag with a complicated and\nvery long description that\nspans multiple lines.") catch unreachable, | 365 | untyped.parseParam("--complicate Flag with a complicated and\nvery long description that\nspans multiple lines.") catch unreachable, |
| 535 | parseParam("-d, --dd <V3> Both option.") catch unreachable, | 366 | untyped.parseParam("-d, --dd <V3> Both option.") catch unreachable, |
| 536 | parseParam("-d, --dd <V3>... Both repeated option.") catch unreachable, | 367 | untyped.parseParam("-d, --dd <V3>... Both repeated option.") catch unreachable, |
| 537 | parseParam( | 368 | untyped.parseParam( |
| 538 | "<P> Positional. This should not appear in the help message.", | 369 | "<P> Positional. This should not appear in the help message.", |
| 539 | ) catch unreachable, | 370 | ) catch unreachable, |
| 540 | }, | 371 | }, |
| @@ -667,37 +498,37 @@ fn testUsage(expected: []const u8, params: []const Param(Help)) !void { | |||
| 667 | test "usage" { | 498 | test "usage" { |
| 668 | @setEvalBranchQuota(100000); | 499 | @setEvalBranchQuota(100000); |
| 669 | try testUsage("[-ab]", &.{ | 500 | try testUsage("[-ab]", &.{ |
| 670 | try parseParam("-a"), | 501 | try untyped.parseParam("-a"), |
| 671 | try parseParam("-b"), | 502 | try untyped.parseParam("-b"), |
| 672 | }); | 503 | }); |
| 673 | try testUsage("[-a <value>] [-b <v>]", &.{ | 504 | try testUsage("[-a <value>] [-b <v>]", &.{ |
| 674 | try parseParam("-a <value>"), | 505 | try untyped.parseParam("-a <value>"), |
| 675 | try parseParam("-b <v>"), | 506 | try untyped.parseParam("-b <v>"), |
| 676 | }); | 507 | }); |
| 677 | try testUsage("[--a] [--b]", &.{ | 508 | try testUsage("[--a] [--b]", &.{ |
| 678 | try parseParam("--a"), | 509 | try untyped.parseParam("--a"), |
| 679 | try parseParam("--b"), | 510 | try untyped.parseParam("--b"), |
| 680 | }); | 511 | }); |
| 681 | try testUsage("[--a <value>] [--b <v>]", &.{ | 512 | try testUsage("[--a <value>] [--b <v>]", &.{ |
| 682 | try parseParam("--a <value>"), | 513 | try untyped.parseParam("--a <value>"), |
| 683 | try parseParam("--b <v>"), | 514 | try untyped.parseParam("--b <v>"), |
| 684 | }); | 515 | }); |
| 685 | try testUsage("<file>", &.{ | 516 | try testUsage("<file>", &.{ |
| 686 | try parseParam("<file>"), | 517 | try untyped.parseParam("<file>"), |
| 687 | }); | 518 | }); |
| 688 | try testUsage( | 519 | try testUsage( |
| 689 | "[-ab] [-c <value>] [-d <v>] [--e] [--f] [--g <value>] [--h <v>] [-i <v>...] <file>", | 520 | "[-ab] [-c <value>] [-d <v>] [--e] [--f] [--g <value>] [--h <v>] [-i <v>...] <file>", |
| 690 | &.{ | 521 | &.{ |
| 691 | try parseParam("-a"), | 522 | try untyped.parseParam("-a"), |
| 692 | try parseParam("-b"), | 523 | try untyped.parseParam("-b"), |
| 693 | try parseParam("-c <value>"), | 524 | try untyped.parseParam("-c <value>"), |
| 694 | try parseParam("-d <v>"), | 525 | try untyped.parseParam("-d <v>"), |
| 695 | try parseParam("--e"), | 526 | try untyped.parseParam("--e"), |
| 696 | try parseParam("--f"), | 527 | try untyped.parseParam("--f"), |
| 697 | try parseParam("--g <value>"), | 528 | try untyped.parseParam("--g <value>"), |
| 698 | try parseParam("--h <v>"), | 529 | try untyped.parseParam("--h <v>"), |
| 699 | try parseParam("-i <v>..."), | 530 | try untyped.parseParam("-i <v>..."), |
| 700 | try parseParam("<file>"), | 531 | try untyped.parseParam("<file>"), |
| 701 | }, | 532 | }, |
| 702 | ); | 533 | ); |
| 703 | } | 534 | } |