summaryrefslogtreecommitdiff
path: root/clap.zig
diff options
context:
space:
mode:
authorGravatar Komari Spaghetti2021-05-26 20:46:23 +0200
committerGravatar Komari Spaghetti2021-05-26 20:46:23 +0200
commitbc32ab045926fb07e4c02c2dbab5aeaddd1f6a02 (patch)
treea545acd4583d4d344d1e235c445b9b9b5060514b /clap.zig
parentMerge branch 'master' into zig-master (diff)
parentModernize codebase (diff)
downloadzig-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.zig241
1 files changed, 94 insertions, 147 deletions
diff --git a/clap.zig b/clap.zig
index be2153f..8d721fa 100644
--- a/clap.zig
+++ b/clap.zig
@@ -1,6 +1,7 @@
1const std = @import("std"); 1const std = @import("std");
2 2
3const debug = std.debug; 3const debug = std.debug;
4const heap = std.heap;
4const io = std.io; 5const io = std.io;
5const mem = std.mem; 6const mem = std.mem;
6const testing = std.testing; 7const 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.
27pub const Values = enum { 28pub 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
124fn expectParam(expect: Param(Help), actual: Param(Help)) void { 125fn expectParam(expect: Param(Help), actual: Param(Help)) void {
@@ -135,114 +136,60 @@ fn expectParam(expect: Param(Help), actual: Param(Help)) void {
135 136
136test "parseParam" { 137test "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.
279pub 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.
333pub fn parse( 290pub 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(
350pub fn parseEx( 313pub 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}