summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Asherah Connor2020-08-23 13:48:12 +1000
committerGravatar Jimmi Holst Christensen2020-09-06 17:31:27 +0200
commit140ace899ae4ecfafc9b635609b656050c9c355d (patch)
treec8f6e57a5ed51a1040fe1fd88892a0f73d6a50b1
parentparse multiple options (diff)
downloadzig-clap-140ace899ae4ecfafc9b635609b656050c9c355d.tar.gz
zig-clap-140ace899ae4ecfafc9b635609b656050c9c355d.tar.xz
zig-clap-140ace899ae4ecfafc9b635609b656050c9c355d.zip
parse and validate multiple option
-rw-r--r--clap.zig106
-rw-r--r--clap/comptime.zig23
-rw-r--r--clap/streaming.zig34
3 files changed, 111 insertions, 52 deletions
diff --git a/clap.zig b/clap.zig
index 588fa47..1a3659b 100644
--- a/clap.zig
+++ b/clap.zig
@@ -25,6 +25,12 @@ pub const Names = struct {
25 long: ?[]const u8 = null, 25 long: ?[]const u8 = null,
26}; 26};
27 27
28pub const Values = enum {
29 None,
30 One,
31 Many,
32};
33
28/// Represents a parameter for the command line. 34/// Represents a parameter for the command line.
29/// Parameters come in three kinds: 35/// Parameters come in three kinds:
30/// * Short ("-a"): Should be used for the most commonly used parameters in your program. 36/// * Short ("-a"): Should be used for the most commonly used parameters in your program.
@@ -50,7 +56,7 @@ pub fn Param(comptime Id: type) type {
50 return struct { 56 return struct {
51 id: Id = Id{}, 57 id: Id = Id{},
52 names: Names = Names{}, 58 names: Names = Names{},
53 takes_value: bool = false, 59 takes_value: Values = .None,
54 }; 60 };
55} 61}
56 62
@@ -86,8 +92,13 @@ pub fn parseParam(line: []const u8) !Param(Help) {
86 const start = mem.indexOfScalar(u8, help_msg, '<').? + 1; 92 const start = mem.indexOfScalar(u8, help_msg, '<').? + 1;
87 const len = mem.indexOfScalar(u8, help_msg[start..], '>') orelse break :blk; 93 const len = mem.indexOfScalar(u8, help_msg[start..], '>') orelse break :blk;
88 res.id.value = help_msg[start..][0..len]; 94 res.id.value = help_msg[start..][0..len];
89 res.takes_value = true; 95 if (mem.startsWith(u8, help_msg[start + len + 1 ..], "...")) {
90 help_msg = help_msg[start + len + 1 ..]; 96 res.takes_value = .Many;
97 help_msg = help_msg[start + len + 1 + 3 ..];
98 } else {
99 res.takes_value = .One;
100 help_msg = help_msg[start + len + 1 ..];
101 }
91 } 102 }
92 } 103 }
93 104
@@ -110,8 +121,13 @@ pub fn parseParam(line: []const u8) !Param(Help) {
110 const start = mem.indexOfScalar(u8, help_msg, '<').? + 1; 121 const start = mem.indexOfScalar(u8, help_msg, '<').? + 1;
111 const len = mem.indexOfScalar(u8, help_msg[start..], '>') orelse break :blk; 122 const len = mem.indexOfScalar(u8, help_msg[start..], '>') orelse break :blk;
112 res.id.value = help_msg[start..][0..len]; 123 res.id.value = help_msg[start..][0..len];
113 res.takes_value = true; 124 if (mem.startsWith(u8, help_msg[start + len + 1 ..], "...")) {
114 help_msg = help_msg[start + len + 1 ..]; 125 res.takes_value = .Many;
126 help_msg = help_msg[start + len + 1 + 3 ..];
127 } else {
128 res.takes_value = .One;
129 help_msg = help_msg[start + len + 1 ..];
130 }
115 } 131 }
116 } 132 }
117 133
@@ -134,7 +150,20 @@ test "parseParam" {
134 .short = 's', 150 .short = 's',
135 .long = find(text, "long"), 151 .long = find(text, "long"),
136 }, 152 },
137 .takes_value = true, 153 .takes_value = .One,
154 }, try parseParam(text));
155
156 text = "-s, --long <value>... Help text";
157 testing.expectEqual(Param(Help){
158 .id = Help{
159 .msg = find(text, "Help text"),
160 .value = find(text, "value"),
161 },
162 .names = Names{
163 .short = 's',
164 .long = find(text, "long"),
165 },
166 .takes_value = .Many,
138 }, try parseParam(text)); 167 }, try parseParam(text));
139 168
140 text = "--long <value> Help text"; 169 text = "--long <value> Help text";
@@ -147,7 +176,7 @@ test "parseParam" {
147 .short = null, 176 .short = null,
148 .long = find(text, "long"), 177 .long = find(text, "long"),
149 }, 178 },
150 .takes_value = true, 179 .takes_value = .One,
151 }, try parseParam(text)); 180 }, try parseParam(text));
152 181
153 text = "-s <value> Help text"; 182 text = "-s <value> Help text";
@@ -160,7 +189,7 @@ test "parseParam" {
160 .short = 's', 189 .short = 's',
161 .long = null, 190 .long = null,
162 }, 191 },
163 .takes_value = true, 192 .takes_value = .One,
164 }, try parseParam(text)); 193 }, try parseParam(text));
165 194
166 text = "-s, --long Help text"; 195 text = "-s, --long Help text";
@@ -173,7 +202,7 @@ test "parseParam" {
173 .short = 's', 202 .short = 's',
174 .long = find(text, "long"), 203 .long = find(text, "long"),
175 }, 204 },
176 .takes_value = false, 205 .takes_value = .None,
177 }, try parseParam(text)); 206 }, try parseParam(text));
178 207
179 text = "-s Help text"; 208 text = "-s Help text";
@@ -186,7 +215,7 @@ test "parseParam" {
186 .short = 's', 215 .short = 's',
187 .long = null, 216 .long = null,
188 }, 217 },
189 .takes_value = false, 218 .takes_value = .None,
190 }, try parseParam(text)); 219 }, try parseParam(text));
191 220
192 text = "--long Help text"; 221 text = "--long Help text";
@@ -199,7 +228,7 @@ test "parseParam" {
199 .short = null, 228 .short = null,
200 .long = find(text, "long"), 229 .long = find(text, "long"),
201 }, 230 },
202 .takes_value = false, 231 .takes_value = .None,
203 }, try parseParam(text)); 232 }, try parseParam(text));
204 233
205 text = "--long <A | B> Help text"; 234 text = "--long <A | B> Help text";
@@ -212,7 +241,7 @@ test "parseParam" {
212 .short = null, 241 .short = null,
213 .long = find(text, "long"), 242 .long = find(text, "long"),
214 }, 243 },
215 .takes_value = true, 244 .takes_value = .One,
216 }, try parseParam(text)); 245 }, try parseParam(text));
217 246
218 testing.expectError(error.NoParamFound, parseParam("Help")); 247 testing.expectError(error.NoParamFound, parseParam("Help"));
@@ -334,8 +363,11 @@ fn printParam(
334 363
335 try stream.print("--{}", .{l}); 364 try stream.print("--{}", .{l});
336 } 365 }
337 if (param.takes_value) 366 switch (param.takes_value) {
338 try stream.print(" <{}>", .{valueText(context, param)}); 367 .None => {},
368 .One => try stream.print(" <{}>", .{valueText(context, param)}),
369 .Many => try stream.print(" <{}>...", .{valueText(context, param)}),
370 }
339} 371}
340 372
341/// A wrapper around helpFull for simple helpText and valueText functions that 373/// A wrapper around helpFull for simple helpText and valueText functions that
@@ -400,28 +432,30 @@ test "clap.help" {
400 try help( 432 try help(
401 slice_stream.outStream(), 433 slice_stream.outStream(),
402 comptime &[_]Param(Help){ 434 comptime &[_]Param(Help){
403 parseParam("-a Short flag. ") catch unreachable, 435 parseParam("-a Short flag. ") catch unreachable,
404 parseParam("-b <V1> Short option.") catch unreachable, 436 parseParam("-b <V1> Short option.") catch unreachable,
405 parseParam("--aa Long flag. ") catch unreachable, 437 parseParam("--aa Long flag. ") catch unreachable,
406 parseParam("--bb <V2> Long option. ") catch unreachable, 438 parseParam("--bb <V2> Long option. ") catch unreachable,
407 parseParam("-c, --cc Both flag. ") catch unreachable, 439 parseParam("-c, --cc Both flag. ") catch unreachable,
408 parseParam("-d, --dd <V3> Both option. ") catch unreachable, 440 parseParam("-d, --dd <V3> Both option. ") catch unreachable,
441 parseParam("-d, --dd <V3>... Both repeated option. ") catch unreachable,
409 Param(Help){ 442 Param(Help){
410 .id = Help{ 443 .id = Help{
411 .msg = "Positional. This should not appear in the help message.", 444 .msg = "Positional. This should not appear in the help message.",
412 }, 445 },
413 .takes_value = true, 446 .takes_value = .One,
414 }, 447 },
415 }, 448 },
416 ); 449 );
417 450
418 const expected = "" ++ 451 const expected = "" ++
419 "\t-a \tShort flag.\n" ++ 452 "\t-a \tShort flag.\n" ++
420 "\t-b <V1> \tShort option.\n" ++ 453 "\t-b <V1> \tShort option.\n" ++
421 "\t --aa \tLong flag.\n" ++ 454 "\t --aa \tLong flag.\n" ++
422 "\t --bb <V2>\tLong option.\n" ++ 455 "\t --bb <V2> \tLong option.\n" ++
423 "\t-c, --cc \tBoth flag.\n" ++ 456 "\t-c, --cc \tBoth flag.\n" ++
424 "\t-d, --dd <V3>\tBoth option.\n"; 457 "\t-d, --dd <V3> \tBoth option.\n" ++
458 "\t-d, --dd <V3>...\tBoth repeated option.\n";
425 459
426 const actual = slice_stream.getWritten(); 460 const actual = slice_stream.getWritten();
427 if (!mem.eql(u8, actual, expected)) { 461 if (!mem.eql(u8, actual, expected)) {
@@ -458,7 +492,7 @@ pub fn usageFull(
458 const cs = cos.outStream(); 492 const cs = cos.outStream();
459 for (params) |param| { 493 for (params) |param| {
460 const name = param.names.short orelse continue; 494 const name = param.names.short orelse continue;
461 if (param.takes_value) 495 if (param.takes_value != .None)
462 continue; 496 continue;
463 497
464 if (cos.bytes_written == 0) 498 if (cos.bytes_written == 0)
@@ -470,7 +504,7 @@ pub fn usageFull(
470 504
471 var positional: ?Param(Id) = null; 505 var positional: ?Param(Id) = null;
472 for (params) |param| { 506 for (params) |param| {
473 if (!param.takes_value and param.names.short != null) 507 if (param.takes_value == .None and param.names.short != null)
474 continue; 508 continue;
475 509
476 const prefix = if (param.names.short) |_| "-" else "--"; 510 const prefix = if (param.names.short) |_| "-" else "--";
@@ -485,8 +519,11 @@ pub fn usageFull(
485 try cs.writeByte(' '); 519 try cs.writeByte(' ');
486 520
487 try cs.print("[{}{}", .{ prefix, name }); 521 try cs.print("[{}{}", .{ prefix, name });
488 if (param.takes_value) 522 switch (param.takes_value) {
489 try cs.print(" <{}>", .{try valueText(context, param)}); 523 .None => {},
524 .One => try cs.print(" <{}>", .{try valueText(context, param)}),
525 .Many => try cs.print(" <{}>...", .{try valueText(context, param)}),
526 }
490 527
491 try cs.writeByte(']'); 528 try cs.writeByte(']');
492 } 529 }
@@ -575,10 +612,10 @@ test "usage" {
575 .id = Help{ 612 .id = Help{
576 .value = "file", 613 .value = "file",
577 }, 614 },
578 .takes_value = true, 615 .takes_value = .One,
579 }, 616 },
580 }); 617 });
581 try testUsage("[-ab] [-c <value>] [-d <v>] [--e] [--f] [--g <value>] [--h <v>] <file>", comptime &[_]Param(Help){ 618 try testUsage("[-ab] [-c <value>] [-d <v>] [--e] [--f] [--g <value>] [--h <v>] [-i <v>...] <file>", comptime &[_]Param(Help){
582 parseParam("-a") catch unreachable, 619 parseParam("-a") catch unreachable,
583 parseParam("-b") catch unreachable, 620 parseParam("-b") catch unreachable,
584 parseParam("-c <value>") catch unreachable, 621 parseParam("-c <value>") catch unreachable,
@@ -587,11 +624,12 @@ test "usage" {
587 parseParam("--f") catch unreachable, 624 parseParam("--f") catch unreachable,
588 parseParam("--g <value>") catch unreachable, 625 parseParam("--g <value>") catch unreachable,
589 parseParam("--h <v>") catch unreachable, 626 parseParam("--h <v>") catch unreachable,
627 parseParam("-i <v>...") catch unreachable,
590 Param(Help){ 628 Param(Help){
591 .id = Help{ 629 .id = Help{
592 .value = "file", 630 .value = "file",
593 }, 631 },
594 .takes_value = true, 632 .takes_value = .One,
595 }, 633 },
596 }); 634 });
597} 635}
diff --git a/clap/comptime.zig b/clap/comptime.zig
index 90d34e9..cecfcb2 100644
--- a/clap/comptime.zig
+++ b/clap/comptime.zig
@@ -13,7 +13,7 @@ pub fn ComptimeClap(comptime Id: type, comptime params: []const clap.Param(Id))
13 for (params) |param| { 13 for (params) |param| {
14 var index: usize = 0; 14 var index: usize = 0;
15 if (param.names.long != null or param.names.short != null) { 15 if (param.names.long != null or param.names.short != null) {
16 const ptr = if (param.takes_value) &options else &flags; 16 const ptr = if (param.takes_value != .None) &options else &flags;
17 index = ptr.*; 17 index = ptr.*;
18 ptr.* += 1; 18 ptr.* += 1;
19 } 19 }
@@ -52,7 +52,7 @@ pub fn ComptimeClap(comptime Id: type, comptime params: []const clap.Param(Id))
52 const param = arg.param; 52 const param = arg.param;
53 if (param.names.long == null and param.names.short == null) { 53 if (param.names.long == null and param.names.short == null) {
54 try pos.append(arg.value.?); 54 try pos.append(arg.value.?);
55 } else if (param.takes_value) { 55 } else if (param.takes_value != .None) {
56 // If we don't have any optional parameters, then this code should 56 // If we don't have any optional parameters, then this code should
57 // never be reached. 57 // never be reached.
58 debug.assert(res.options.len != 0); 58 debug.assert(res.options.len != 0);
@@ -80,7 +80,7 @@ pub fn ComptimeClap(comptime Id: type, comptime params: []const clap.Param(Id))
80 80
81 pub fn flag(parser: @This(), comptime name: []const u8) bool { 81 pub fn flag(parser: @This(), comptime name: []const u8) bool {
82 const param = comptime findParam(name); 82 const param = comptime findParam(name);
83 if (param.takes_value) 83 if (param.takes_value != .None)
84 @compileError(name ++ " is an option and not a flag."); 84 @compileError(name ++ " is an option and not a flag.");
85 85
86 return parser.flags[param.id]; 86 return parser.flags[param.id];
@@ -88,14 +88,21 @@ pub fn ComptimeClap(comptime Id: type, comptime params: []const clap.Param(Id))
88 88
89 pub fn allOptions(parser: @This(), comptime name: []const u8) [][]const u8 { 89 pub fn allOptions(parser: @This(), comptime name: []const u8) [][]const u8 {
90 const param = comptime findParam(name); 90 const param = comptime findParam(name);
91 if (!param.takes_value) 91 if (param.takes_value == .None)
92 @compileError(name ++ " is a flag and not an option."); 92 @compileError(name ++ " is a flag and not an option.");
93 if (param.takes_value == .One)
94 @compileError(name ++ " takes one option, not multiple.");
93 95
94 return parser.options[param.id].items; 96 return parser.options[param.id].items;
95 } 97 }
96 98
97 pub fn option(parser: @This(), comptime name: []const u8) ?[]const u8 { 99 pub fn option(parser: @This(), comptime name: []const u8) ?[]const u8 {
98 const items = parser.allOptions(name); 100 const param = comptime findParam(name);
101 if (param.takes_value == .None)
102 @compileError(name ++ " is a flag and not an option.");
103 if (param.takes_value == .Many)
104 @compileError(name ++ " takes many options, not one.");
105 const items = parser.options[param.id].items;
99 return if (items.len > 0) items[0] else null; 106 return if (items.len > 0) items[0] else null;
100 } 107 }
101 108
@@ -127,9 +134,9 @@ test "clap.comptime.ComptimeClap" {
127 clap.parseParam("-a, --aa ") catch unreachable, 134 clap.parseParam("-a, --aa ") catch unreachable,
128 clap.parseParam("-b, --bb ") catch unreachable, 135 clap.parseParam("-b, --bb ") catch unreachable,
129 clap.parseParam("-c, --cc <V>") catch unreachable, 136 clap.parseParam("-c, --cc <V>") catch unreachable,
130 clap.parseParam("-d, --dd <V>") catch unreachable, 137 clap.parseParam("-d, --dd <V>...") catch unreachable,
131 clap.Param(clap.Help){ 138 clap.Param(clap.Help){
132 .takes_value = true, 139 .takes_value = .One,
133 }, 140 },
134 }); 141 });
135 142
@@ -151,6 +158,6 @@ test "clap.comptime.ComptimeClap" {
151 testing.expectEqualStrings("0", args.option("--cc").?); 158 testing.expectEqualStrings("0", args.option("--cc").?);
152 testing.expectEqual(@as(usize, 1), args.positionals().len); 159 testing.expectEqual(@as(usize, 1), args.positionals().len);
153 testing.expectEqualStrings("something", args.positionals()[0]); 160 testing.expectEqualStrings("something", args.positionals()[0]);
154 testing.expectEqualStrings("a", args.option("-d").?); 161 testing.expectEqualSlices([]const u8, &[_][]const u8{ "a", "b" }, args.allOptions("-d"));
155 testing.expectEqualSlices([]const u8, &[_][]const u8{ "a", "b" }, args.allOptions("--dd")); 162 testing.expectEqualSlices([]const u8, &[_][]const u8{ "a", "b" }, args.allOptions("--dd"));
156} 163}
diff --git a/clap/streaming.zig b/clap/streaming.zig
index 95ee581..b843bff 100644
--- a/clap/streaming.zig
+++ b/clap/streaming.zig
@@ -75,7 +75,7 @@ pub fn StreamingClap(comptime Id: type, comptime ArgIterator: type) type {
75 75
76 if (!mem.eql(u8, name, match)) 76 if (!mem.eql(u8, name, match))
77 continue; 77 continue;
78 if (!param.takes_value) { 78 if (param.takes_value == .None) {
79 if (maybe_value != null) 79 if (maybe_value != null)
80 return error.DoesntTakeValue; 80 return error.DoesntTakeValue;
81 81
@@ -128,7 +128,7 @@ pub fn StreamingClap(comptime Id: type, comptime ArgIterator: type) type {
128 128
129 // Before we return, we have to set the new state of the clap 129 // Before we return, we have to set the new state of the clap
130 defer { 130 defer {
131 if (arg.len <= next_index or param.takes_value) { 131 if (arg.len <= next_index or param.takes_value != .None) {
132 parser.state = State.Normal; 132 parser.state = State.Normal;
133 } else { 133 } else {
134 parser.state = State{ 134 parser.state = State{
@@ -140,7 +140,7 @@ pub fn StreamingClap(comptime Id: type, comptime ArgIterator: type) type {
140 } 140 }
141 } 141 }
142 142
143 if (!param.takes_value) 143 if (param.takes_value == .None)
144 return Arg(Id){ .param = param }; 144 return Arg(Id){ .param = param };
145 145
146 if (arg.len <= next_index) { 146 if (arg.len <= next_index) {
@@ -194,20 +194,26 @@ test "clap.streaming.StreamingClap: short params" {
194 clap.Param(u8){ 194 clap.Param(u8){
195 .id = 2, 195 .id = 2,
196 .names = clap.Names{ .short = 'c' }, 196 .names = clap.Names{ .short = 'c' },
197 .takes_value = true, 197 .takes_value = .One,
198 },
199 clap.Param(u8){
200 .id = 3,
201 .names = clap.Names{ .short = 'd' },
202 .takes_value = .Many,
198 }, 203 },
199 }; 204 };
200 205
201 const a = &params[0]; 206 const a = &params[0];
202 const b = &params[1]; 207 const b = &params[1];
203 const c = &params[2]; 208 const c = &params[2];
209 const d = &params[3];
204 210
205 testNoErr( 211 testNoErr(
206 &params, 212 &params,
207 &[_][]const u8{ 213 &[_][]const u8{
208 "-a", "-b", "-ab", "-ba", 214 "-a", "-b", "-ab", "-ba",
209 "-c", "0", "-c=0", "-ac", 215 "-c", "0", "-c=0", "-ac",
210 "0", "-ac=0", 216 "0", "-ac=0", "-d=0",
211 }, 217 },
212 &[_]Arg(u8){ 218 &[_]Arg(u8){
213 Arg(u8){ .param = a }, 219 Arg(u8){ .param = a },
@@ -222,6 +228,7 @@ test "clap.streaming.StreamingClap: short params" {
222 Arg(u8){ .param = c, .value = "0" }, 228 Arg(u8){ .param = c, .value = "0" },
223 Arg(u8){ .param = a }, 229 Arg(u8){ .param = a },
224 Arg(u8){ .param = c, .value = "0" }, 230 Arg(u8){ .param = c, .value = "0" },
231 Arg(u8){ .param = d, .value = "0" },
225 }, 232 },
226 ); 233 );
227} 234}
@@ -239,26 +246,33 @@ test "clap.streaming.StreamingClap: long params" {
239 clap.Param(u8){ 246 clap.Param(u8){
240 .id = 2, 247 .id = 2,
241 .names = clap.Names{ .long = "cc" }, 248 .names = clap.Names{ .long = "cc" },
242 .takes_value = true, 249 .takes_value = .One,
250 },
251 clap.Param(u8){
252 .id = 3,
253 .names = clap.Names{ .long = "dd" },
254 .takes_value = .Many,
243 }, 255 },
244 }; 256 };
245 257
246 const aa = &params[0]; 258 const aa = &params[0];
247 const bb = &params[1]; 259 const bb = &params[1];
248 const cc = &params[2]; 260 const cc = &params[2];
261 const dd = &params[3];
249 262
250 testNoErr( 263 testNoErr(
251 &params, 264 &params,
252 &[_][]const u8{ 265 &[_][]const u8{
253 "--aa", "--bb", 266 "--aa", "--bb",
254 "--cc", "0", 267 "--cc", "0",
255 "--cc=0", 268 "--cc=0", "--dd=0",
256 }, 269 },
257 &[_]Arg(u8){ 270 &[_]Arg(u8){
258 Arg(u8){ .param = aa }, 271 Arg(u8){ .param = aa },
259 Arg(u8){ .param = bb }, 272 Arg(u8){ .param = bb },
260 Arg(u8){ .param = cc, .value = "0" }, 273 Arg(u8){ .param = cc, .value = "0" },
261 Arg(u8){ .param = cc, .value = "0" }, 274 Arg(u8){ .param = cc, .value = "0" },
275 Arg(u8){ .param = dd, .value = "0" },
262 }, 276 },
263 ); 277 );
264} 278}
@@ -266,7 +280,7 @@ test "clap.streaming.StreamingClap: long params" {
266test "clap.streaming.StreamingClap: positional params" { 280test "clap.streaming.StreamingClap: positional params" {
267 const params = [_]clap.Param(u8){clap.Param(u8){ 281 const params = [_]clap.Param(u8){clap.Param(u8){
268 .id = 0, 282 .id = 0,
269 .takes_value = true, 283 .takes_value = .One,
270 }}; 284 }};
271 285
272 testNoErr( 286 testNoErr(
@@ -301,11 +315,11 @@ test "clap.streaming.StreamingClap: all params" {
301 .short = 'c', 315 .short = 'c',
302 .long = "cc", 316 .long = "cc",
303 }, 317 },
304 .takes_value = true, 318 .takes_value = .One,
305 }, 319 },
306 clap.Param(u8){ 320 clap.Param(u8){
307 .id = 3, 321 .id = 3,
308 .takes_value = true, 322 .takes_value = .One,
309 }, 323 },
310 }; 324 };
311 325