diff options
| author | 2021-05-26 20:46:23 +0200 | |
|---|---|---|
| committer | 2021-05-26 20:46:23 +0200 | |
| commit | bc32ab045926fb07e4c02c2dbab5aeaddd1f6a02 (patch) | |
| tree | a545acd4583d4d344d1e235c445b9b9b5060514b /clap.zig | |
| parent | Merge branch 'master' into zig-master (diff) | |
| parent | Modernize codebase (diff) | |
| download | zig-clap-bc32ab045926fb07e4c02c2dbab5aeaddd1f6a02.tar.gz zig-clap-bc32ab045926fb07e4c02c2dbab5aeaddd1f6a02.tar.xz zig-clap-bc32ab045926fb07e4c02c2dbab5aeaddd1f6a02.zip | |
Merge branch 'master' into zig-master
Diffstat (limited to 'clap.zig')
| -rw-r--r-- | clap.zig | 241 |
1 files changed, 94 insertions, 147 deletions
| @@ -1,6 +1,7 @@ | |||
| 1 | const std = @import("std"); | 1 | const std = @import("std"); |
| 2 | 2 | ||
| 3 | const debug = std.debug; | 3 | const debug = std.debug; |
| 4 | const heap = std.heap; | ||
| 4 | const io = std.io; | 5 | const io = std.io; |
| 5 | const mem = std.mem; | 6 | const mem = std.mem; |
| 6 | const testing = std.testing; | 7 | const testing = std.testing; |
| @@ -25,9 +26,9 @@ pub const Names = struct { | |||
| 25 | 26 | ||
| 26 | /// Whether a param takes no value (a flag), one value, or can be specified multiple times. | 27 | /// Whether a param takes no value (a flag), one value, or can be specified multiple times. |
| 27 | pub const Values = enum { | 28 | pub const Values = enum { |
| 28 | None, | 29 | none, |
| 29 | One, | 30 | one, |
| 30 | Many, | 31 | many, |
| 31 | }; | 32 | }; |
| 32 | 33 | ||
| 33 | /// Represents a parameter for the command line. | 34 | /// Represents a parameter for the command line. |
| @@ -55,7 +56,7 @@ pub fn Param(comptime Id: type) type { | |||
| 55 | return struct { | 56 | return struct { |
| 56 | id: Id = Id{}, | 57 | id: Id = Id{}, |
| 57 | names: Names = Names{}, | 58 | names: Names = Names{}, |
| 58 | takes_value: Values = .None, | 59 | takes_value: Values = .none, |
| 59 | }; | 60 | }; |
| 60 | } | 61 | } |
| 61 | 62 | ||
| @@ -109,8 +110,8 @@ fn parseParamRest(line: []const u8) Param(Help) { | |||
| 109 | const len = mem.indexOfScalar(u8, line, '>') orelse break :blk; | 110 | const len = mem.indexOfScalar(u8, line, '>') orelse break :blk; |
| 110 | const takes_many = mem.startsWith(u8, line[len + 1 ..], "..."); | 111 | const takes_many = mem.startsWith(u8, line[len + 1 ..], "..."); |
| 111 | const help_start = len + 1 + @as(usize, 3) * @boolToInt(takes_many); | 112 | const help_start = len + 1 + @as(usize, 3) * @boolToInt(takes_many); |
| 112 | return Param(Help){ | 113 | return .{ |
| 113 | .takes_value = if (takes_many) .Many else .One, | 114 | .takes_value = if (takes_many) .many else .one, |
| 114 | .id = .{ | 115 | .id = .{ |
| 115 | .msg = mem.trim(u8, line[help_start..], " \t"), | 116 | .msg = mem.trim(u8, line[help_start..], " \t"), |
| 116 | .value = line[1..len], | 117 | .value = line[1..len], |
| @@ -118,7 +119,7 @@ fn parseParamRest(line: []const u8) Param(Help) { | |||
| 118 | }; | 119 | }; |
| 119 | } | 120 | } |
| 120 | 121 | ||
| 121 | return Param(Help){ .id = .{ .msg = mem.trim(u8, line, " \t") } }; | 122 | return .{ .id = .{ .msg = mem.trim(u8, line, " \t") } }; |
| 122 | } | 123 | } |
| 123 | 124 | ||
| 124 | fn expectParam(expect: Param(Help), actual: Param(Help)) void { | 125 | fn expectParam(expect: Param(Help), actual: Param(Help)) void { |
| @@ -135,114 +136,60 @@ fn expectParam(expect: Param(Help), actual: Param(Help)) void { | |||
| 135 | 136 | ||
| 136 | test "parseParam" { | 137 | test "parseParam" { |
| 137 | expectParam(Param(Help){ | 138 | expectParam(Param(Help){ |
| 138 | .id = Help{ | 139 | .id = .{ .msg = "Help text", .value = "value" }, |
| 139 | .msg = "Help text", | 140 | .names = .{ .short = 's', .long = "long" }, |
| 140 | .value = "value", | 141 | .takes_value = .one, |
| 141 | }, | ||
| 142 | .names = Names{ | ||
| 143 | .short = 's', | ||
| 144 | .long = "long", | ||
| 145 | }, | ||
| 146 | .takes_value = .One, | ||
| 147 | }, try parseParam("-s, --long <value> Help text")); | 142 | }, try parseParam("-s, --long <value> Help text")); |
| 143 | |||
| 148 | expectParam(Param(Help){ | 144 | expectParam(Param(Help){ |
| 149 | .id = Help{ | 145 | .id = .{ .msg = "Help text", .value = "value" }, |
| 150 | .msg = "Help text", | 146 | .names = .{ .short = 's', .long = "long" }, |
| 151 | .value = "value", | 147 | .takes_value = .many, |
| 152 | }, | ||
| 153 | .names = Names{ | ||
| 154 | .short = 's', | ||
| 155 | .long = "long", | ||
| 156 | }, | ||
| 157 | .takes_value = .Many, | ||
| 158 | }, try parseParam("-s, --long <value>... Help text")); | 148 | }, try parseParam("-s, --long <value>... Help text")); |
| 149 | |||
| 159 | expectParam(Param(Help){ | 150 | expectParam(Param(Help){ |
| 160 | .id = Help{ | 151 | .id = .{ .msg = "Help text", .value = "value" }, |
| 161 | .msg = "Help text", | 152 | .names = .{ .long = "long" }, |
| 162 | .value = "value", | 153 | .takes_value = .one, |
| 163 | }, | ||
| 164 | .names = Names{ | ||
| 165 | .short = null, | ||
| 166 | .long = "long", | ||
| 167 | }, | ||
| 168 | .takes_value = .One, | ||
| 169 | }, try parseParam("--long <value> Help text")); | 154 | }, try parseParam("--long <value> Help text")); |
| 155 | |||
| 170 | expectParam(Param(Help){ | 156 | expectParam(Param(Help){ |
| 171 | .id = Help{ | 157 | .id = .{ .msg = "Help text", .value = "value" }, |
| 172 | .msg = "Help text", | 158 | .names = .{ .short = 's' }, |
| 173 | .value = "value", | 159 | .takes_value = .one, |
| 174 | }, | ||
| 175 | .names = Names{ | ||
| 176 | .short = 's', | ||
| 177 | .long = null, | ||
| 178 | }, | ||
| 179 | .takes_value = .One, | ||
| 180 | }, try parseParam("-s <value> Help text")); | 160 | }, try parseParam("-s <value> Help text")); |
| 161 | |||
| 181 | expectParam(Param(Help){ | 162 | expectParam(Param(Help){ |
| 182 | .id = Help{ | 163 | .id = .{ .msg = "Help text" }, |
| 183 | .msg = "Help text", | 164 | .names = .{ .short = 's', .long = "long" }, |
| 184 | .value = "", | ||
| 185 | }, | ||
| 186 | .names = Names{ | ||
| 187 | .short = 's', | ||
| 188 | .long = "long", | ||
| 189 | }, | ||
| 190 | .takes_value = .None, | ||
| 191 | }, try parseParam("-s, --long Help text")); | 165 | }, try parseParam("-s, --long Help text")); |
| 166 | |||
| 192 | expectParam(Param(Help){ | 167 | expectParam(Param(Help){ |
| 193 | .id = Help{ | 168 | .id = .{ .msg = "Help text" }, |
| 194 | .msg = "Help text", | 169 | .names = .{ .short = 's' }, |
| 195 | .value = "", | ||
| 196 | }, | ||
| 197 | .names = Names{ | ||
| 198 | .short = 's', | ||
| 199 | .long = null, | ||
| 200 | }, | ||
| 201 | .takes_value = .None, | ||
| 202 | }, try parseParam("-s Help text")); | 170 | }, try parseParam("-s Help text")); |
| 171 | |||
| 203 | expectParam(Param(Help){ | 172 | expectParam(Param(Help){ |
| 204 | .id = Help{ | 173 | .id = .{ .msg = "Help text" }, |
| 205 | .msg = "Help text", | 174 | .names = .{ .long = "long" }, |
| 206 | .value = "", | ||
| 207 | }, | ||
| 208 | .names = Names{ | ||
| 209 | .short = null, | ||
| 210 | .long = "long", | ||
| 211 | }, | ||
| 212 | .takes_value = .None, | ||
| 213 | }, try parseParam("--long Help text")); | 175 | }, try parseParam("--long Help text")); |
| 176 | |||
| 214 | expectParam(Param(Help){ | 177 | expectParam(Param(Help){ |
| 215 | .id = Help{ | 178 | .id = .{ .msg = "Help text", .value = "A | B" }, |
| 216 | .msg = "Help text", | 179 | .names = .{ .long = "long" }, |
| 217 | .value = "A | B", | 180 | .takes_value = .one, |
| 218 | }, | ||
| 219 | .names = Names{ | ||
| 220 | .short = null, | ||
| 221 | .long = "long", | ||
| 222 | }, | ||
| 223 | .takes_value = .One, | ||
| 224 | }, try parseParam("--long <A | B> Help text")); | 181 | }, try parseParam("--long <A | B> Help text")); |
| 182 | |||
| 225 | expectParam(Param(Help){ | 183 | expectParam(Param(Help){ |
| 226 | .id = Help{ | 184 | .id = .{ .msg = "Help text", .value = "A" }, |
| 227 | .msg = "Help text", | 185 | .names = .{}, |
| 228 | .value = "A", | 186 | .takes_value = .one, |
| 229 | }, | ||
| 230 | .names = Names{ | ||
| 231 | .short = null, | ||
| 232 | .long = null, | ||
| 233 | }, | ||
| 234 | .takes_value = .One, | ||
| 235 | }, try parseParam("<A> Help text")); | 187 | }, try parseParam("<A> Help text")); |
| 188 | |||
| 236 | expectParam(Param(Help){ | 189 | expectParam(Param(Help){ |
| 237 | .id = Help{ | 190 | .id = .{ .msg = "Help text", .value = "A" }, |
| 238 | .msg = "Help text", | 191 | .names = .{}, |
| 239 | .value = "A", | 192 | .takes_value = .many, |
| 240 | }, | ||
| 241 | .names = Names{ | ||
| 242 | .short = null, | ||
| 243 | .long = null, | ||
| 244 | }, | ||
| 245 | .takes_value = .Many, | ||
| 246 | }, try parseParam("<A>... Help text")); | 193 | }, try parseParam("<A>... Help text")); |
| 247 | 194 | ||
| 248 | testing.expectError(error.TrailingComma, parseParam("--long, Help")); | 195 | testing.expectError(error.TrailingComma, parseParam("--long, Help")); |
| @@ -307,7 +254,6 @@ pub fn Args(comptime Id: type, comptime params: []const Param(Id)) type { | |||
| 307 | exe_arg: ?[]const u8, | 254 | exe_arg: ?[]const u8, |
| 308 | 255 | ||
| 309 | pub fn deinit(a: *@This()) void { | 256 | pub fn deinit(a: *@This()) void { |
| 310 | a.clap.deinit(); | ||
| 311 | a.arena.deinit(); | 257 | a.arena.deinit(); |
| 312 | } | 258 | } |
| 313 | 259 | ||
| @@ -329,20 +275,37 @@ pub fn Args(comptime Id: type, comptime params: []const Param(Id)) type { | |||
| 329 | }; | 275 | }; |
| 330 | } | 276 | } |
| 331 | 277 | ||
| 278 | /// Options that can be set to customize the behavior of parsing. | ||
| 279 | pub const ParseOptions = struct { | ||
| 280 | /// The allocator used for all memory allocations. Defaults to the `heap.page_allocator`. | ||
| 281 | /// Note: You should probably override this allocator if you are calling `parseEx`. Unlike | ||
| 282 | /// `parse`, `parseEx` does not wrap the allocator so the heap allocator can be | ||
| 283 | /// quite expensive. (TODO: Can we pick a better default? For `parse`, this allocator | ||
| 284 | /// is fine, as it wraps it in an arena) | ||
| 285 | allocator: *mem.Allocator = heap.page_allocator, | ||
| 286 | diagnostic: ?*Diagnostic = null, | ||
| 287 | }; | ||
| 288 | |||
| 332 | /// Same as `parseEx` but uses the `args.OsIterator` by default. | 289 | /// Same as `parseEx` but uses the `args.OsIterator` by default. |
| 333 | pub fn parse( | 290 | pub fn parse( |
| 334 | comptime Id: type, | 291 | comptime Id: type, |
| 335 | comptime params: []const Param(Id), | 292 | comptime params: []const Param(Id), |
| 336 | allocator: *mem.Allocator, | 293 | opt: ParseOptions, |
| 337 | diag: ?*Diagnostic, | ||
| 338 | ) !Args(Id, params) { | 294 | ) !Args(Id, params) { |
| 339 | var iter = try args.OsIterator.init(allocator); | 295 | var iter = try args.OsIterator.init(opt.allocator); |
| 340 | const clap = try parseEx(Id, params, allocator, &iter, diag); | 296 | var res = Args(Id, params){ |
| 341 | return Args(Id, params){ | ||
| 342 | .arena = iter.arena, | 297 | .arena = iter.arena, |
| 343 | .clap = clap, | ||
| 344 | .exe_arg = iter.exe_arg, | 298 | .exe_arg = iter.exe_arg, |
| 299 | .clap = undefined, | ||
| 345 | }; | 300 | }; |
| 301 | |||
| 302 | // Let's reuse the arena from the `OSIterator` since we already have | ||
| 303 | // it. | ||
| 304 | res.clap = try parseEx(Id, params, &iter, .{ | ||
| 305 | .allocator = &res.arena.allocator, | ||
| 306 | .diagnostic = opt.diagnostic, | ||
| 307 | }); | ||
| 308 | return res; | ||
| 346 | } | 309 | } |
| 347 | 310 | ||
| 348 | /// Parses the command line arguments passed into the program based on an | 311 | /// Parses the command line arguments passed into the program based on an |
| @@ -350,12 +313,11 @@ pub fn parse( | |||
| 350 | pub fn parseEx( | 313 | pub fn parseEx( |
| 351 | comptime Id: type, | 314 | comptime Id: type, |
| 352 | comptime params: []const Param(Id), | 315 | comptime params: []const Param(Id), |
| 353 | allocator: *mem.Allocator, | ||
| 354 | iter: anytype, | 316 | iter: anytype, |
| 355 | diag: ?*Diagnostic, | 317 | opt: ParseOptions, |
| 356 | ) !ComptimeClap(Id, params) { | 318 | ) !ComptimeClap(Id, params) { |
| 357 | const Clap = ComptimeClap(Id, params); | 319 | const Clap = ComptimeClap(Id, params); |
| 358 | return try Clap.parse(allocator, iter, diag); | 320 | return try Clap.parse(iter, opt); |
| 359 | } | 321 | } |
| 360 | 322 | ||
| 361 | /// Will print a help message in the following format: | 323 | /// Will print a help message in the following format: |
| @@ -376,10 +338,10 @@ pub fn helpFull( | |||
| 376 | const max_spacing = blk: { | 338 | const max_spacing = blk: { |
| 377 | var res: usize = 0; | 339 | var res: usize = 0; |
| 378 | for (params) |param| { | 340 | for (params) |param| { |
| 379 | var counting_stream = io.countingWriter(io.null_writer); | 341 | var cs = io.countingOutStream(io.null_writer); |
| 380 | try printParam(counting_stream.writer(), Id, param, Error, context, valueText); | 342 | try printParam(cs.writer(), Id, param, Error, context, valueText); |
| 381 | if (res < counting_stream.bytes_written) | 343 | if (res < cs.bytes_written) |
| 382 | res = @intCast(usize, counting_stream.bytes_written); | 344 | res = @intCast(usize, cs.bytes_written); |
| 383 | } | 345 | } |
| 384 | 346 | ||
| 385 | break :blk res; | 347 | break :blk res; |
| @@ -389,11 +351,11 @@ pub fn helpFull( | |||
| 389 | if (param.names.short == null and param.names.long == null) | 351 | if (param.names.short == null and param.names.long == null) |
| 390 | continue; | 352 | continue; |
| 391 | 353 | ||
| 392 | var counting_stream = io.countingWriter(stream); | 354 | var cs = io.countingWriter(stream); |
| 393 | try stream.print("\t", .{}); | 355 | try stream.print("\t", .{}); |
| 394 | try printParam(counting_stream.writer(), Id, param, Error, context, valueText); | 356 | try printParam(cs.writer(), Id, param, Error, context, valueText); |
| 395 | try stream.writeByteNTimes(' ', max_spacing - @intCast(usize, counting_stream.bytes_written)); | 357 | try stream.writeByteNTimes(' ', max_spacing - @intCast(usize, cs.bytes_written)); |
| 396 | try stream.print("\t{s}\n", .{try helpText(context, param)}); | 358 | try stream.print("\t{}\n", .{try helpText(context, param)}); |
| 397 | } | 359 | } |
| 398 | } | 360 | } |
| 399 | 361 | ||
| @@ -421,9 +383,9 @@ fn printParam( | |||
| 421 | } | 383 | } |
| 422 | 384 | ||
| 423 | switch (param.takes_value) { | 385 | switch (param.takes_value) { |
| 424 | .None => {}, | 386 | .none => {}, |
| 425 | .One => try stream.print(" <{s}>", .{valueText(context, param)}), | 387 | .one => try stream.print(" <{s}>", .{valueText(context, param)}), |
| 426 | .Many => try stream.print(" <{s}>...", .{valueText(context, param)}), | 388 | .many => try stream.print(" <{s}>...", .{valueText(context, param)}), |
| 427 | } | 389 | } |
| 428 | } | 390 | } |
| 429 | 391 | ||
| @@ -489,19 +451,14 @@ test "clap.help" { | |||
| 489 | try help( | 451 | try help( |
| 490 | slice_stream.writer(), | 452 | slice_stream.writer(), |
| 491 | comptime &[_]Param(Help){ | 453 | comptime &[_]Param(Help){ |
| 492 | parseParam("-a Short flag. ") catch unreachable, | 454 | parseParam("-a Short flag.") catch unreachable, |
| 493 | parseParam("-b <V1> Short option.") catch unreachable, | 455 | parseParam("-b <V1> Short option.") catch unreachable, |
| 494 | parseParam("--aa Long flag. ") catch unreachable, | 456 | parseParam("--aa Long flag.") catch unreachable, |
| 495 | parseParam("--bb <V2> Long option. ") catch unreachable, | 457 | parseParam("--bb <V2> Long option.") catch unreachable, |
| 496 | parseParam("-c, --cc Both flag. ") catch unreachable, | 458 | parseParam("-c, --cc Both flag.") catch unreachable, |
| 497 | parseParam("-d, --dd <V3> Both option. ") catch unreachable, | 459 | parseParam("-d, --dd <V3> Both option.") catch unreachable, |
| 498 | parseParam("-d, --dd <V3>... Both repeated option. ") catch unreachable, | 460 | parseParam("-d, --dd <V3>... Both repeated option.") catch unreachable, |
| 499 | Param(Help){ | 461 | parseParam("<P> Positional. This should not appear in the help message.") catch unreachable, |
| 500 | .id = Help{ | ||
| 501 | .msg = "Positional. This should not appear in the help message.", | ||
| 502 | }, | ||
| 503 | .takes_value = .One, | ||
| 504 | }, | ||
| 505 | }, | 462 | }, |
| 506 | ); | 463 | ); |
| 507 | 464 | ||
| @@ -534,7 +491,7 @@ pub fn usageFull( | |||
| 534 | const cs = cos.writer(); | 491 | const cs = cos.writer(); |
| 535 | for (params) |param| { | 492 | for (params) |param| { |
| 536 | const name = param.names.short orelse continue; | 493 | const name = param.names.short orelse continue; |
| 537 | if (param.takes_value != .None) | 494 | if (param.takes_value != .none) |
| 538 | continue; | 495 | continue; |
| 539 | 496 | ||
| 540 | if (cos.bytes_written == 0) | 497 | if (cos.bytes_written == 0) |
| @@ -546,7 +503,7 @@ pub fn usageFull( | |||
| 546 | 503 | ||
| 547 | var positional: ?Param(Id) = null; | 504 | var positional: ?Param(Id) = null; |
| 548 | for (params) |param| { | 505 | for (params) |param| { |
| 549 | if (param.takes_value == .None and param.names.short != null) | 506 | if (param.takes_value == .none and param.names.short != null) |
| 550 | continue; | 507 | continue; |
| 551 | 508 | ||
| 552 | const prefix = if (param.names.short) |_| "-" else "--"; | 509 | const prefix = if (param.names.short) |_| "-" else "--"; |
| @@ -562,9 +519,9 @@ pub fn usageFull( | |||
| 562 | 519 | ||
| 563 | try cs.print("[{s}{s}", .{ prefix, name }); | 520 | try cs.print("[{s}{s}", .{ prefix, name }); |
| 564 | switch (param.takes_value) { | 521 | switch (param.takes_value) { |
| 565 | .None => {}, | 522 | .none => {}, |
| 566 | .One => try cs.print(" <{s}>", .{try valueText(context, param)}), | 523 | .one => try cs.print(" <{s}>", .{try valueText(context, param)}), |
| 567 | .Many => try cs.print(" <{s}>...", .{try valueText(context, param)}), | 524 | .many => try cs.print(" <{s}>...", .{try valueText(context, param)}), |
| 568 | } | 525 | } |
| 569 | 526 | ||
| 570 | try cs.writeByte(']'); | 527 | try cs.writeByte(']'); |
| @@ -634,12 +591,7 @@ test "usage" { | |||
| 634 | parseParam("--b <v>") catch unreachable, | 591 | parseParam("--b <v>") catch unreachable, |
| 635 | }); | 592 | }); |
| 636 | try testUsage("<file>", comptime &[_]Param(Help){ | 593 | try testUsage("<file>", comptime &[_]Param(Help){ |
| 637 | Param(Help){ | 594 | parseParam("<file>") catch unreachable, |
| 638 | .id = Help{ | ||
| 639 | .value = "file", | ||
| 640 | }, | ||
| 641 | .takes_value = .One, | ||
| 642 | }, | ||
| 643 | }); | 595 | }); |
| 644 | try testUsage("[-ab] [-c <value>] [-d <v>] [--e] [--f] [--g <value>] [--h <v>] [-i <v>...] <file>", comptime &[_]Param(Help){ | 596 | try testUsage("[-ab] [-c <value>] [-d <v>] [--e] [--f] [--g <value>] [--h <v>] [-i <v>...] <file>", comptime &[_]Param(Help){ |
| 645 | parseParam("-a") catch unreachable, | 597 | parseParam("-a") catch unreachable, |
| @@ -651,11 +603,6 @@ test "usage" { | |||
| 651 | parseParam("--g <value>") catch unreachable, | 603 | parseParam("--g <value>") catch unreachable, |
| 652 | parseParam("--h <v>") catch unreachable, | 604 | parseParam("--h <v>") catch unreachable, |
| 653 | parseParam("-i <v>...") catch unreachable, | 605 | parseParam("-i <v>...") catch unreachable, |
| 654 | Param(Help){ | 606 | parseParam("<file>") catch unreachable, |
| 655 | .id = Help{ | ||
| 656 | .value = "file", | ||
| 657 | }, | ||
| 658 | .takes_value = .One, | ||
| 659 | }, | ||
| 660 | }); | 607 | }); |
| 661 | } | 608 | } |