summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Jimmi Holst Christensen2022-03-23 20:05:28 +0100
committerGravatar Komari Spaghetti2022-03-23 21:48:20 +0100
commit5166a15378a9c32d9b680417807000ba65d06141 (patch)
treebf5b37a04d9bd41128971e7bfd5e1dccef30bb36
parentRefactor parseParam into a state machine (diff)
downloadzig-clap-5166a15378a9c32d9b680417807000ba65d06141.tar.gz
zig-clap-5166a15378a9c32d9b680417807000ba65d06141.tar.xz
zig-clap-5166a15378a9c32d9b680417807000ba65d06141.zip
Add parseParams and friends
Diffstat (limited to '')
-rw-r--r--README.md54
-rw-r--r--clap.zig730
-rw-r--r--example/README.md.template4
-rw-r--r--example/help.zig9
-rw-r--r--example/simple-ex.zig15
-rw-r--r--example/simple.zig15
-rw-r--r--example/usage.zig11
7 files changed, 447 insertions, 391 deletions
diff --git a/README.md b/README.md
index 55a43d9..5686d54 100644
--- a/README.md
+++ b/README.md
@@ -32,13 +32,14 @@ const io = std.io;
32 32
33pub fn main() !void { 33pub fn main() !void {
34 // First we specify what parameters our program can take. 34 // First we specify what parameters our program can take.
35 // We can use `parseParam` to parse a string to a `Param(Help)` 35 // We can use `parseParamsComptime` to parse a string into an array of `Param(Help)`
36 const params = comptime [_]clap.Param(clap.Help){ 36 const params = comptime clap.parseParamsComptime(
37 clap.parseParam("-h, --help Display this help and exit.") catch unreachable, 37 \\-h, --help Display this help and exit.
38 clap.parseParam("-n, --number <usize> An option parameter, which takes a value.") catch unreachable, 38 \\-n, --number <usize> An option parameter, which takes a value.
39 clap.parseParam("-s, --string <str>... An option parameter which can be specified multiple times.") catch unreachable, 39 \\-s, --string <str>... An option parameter which can be specified multiple times.
40 clap.parseParam("<str>...") catch unreachable, 40 \\<str>...
41 }; 41 \\
42 );
42 43
43 // Initalize our diagnostics, which can be used for reporting useful errors. 44 // Initalize our diagnostics, which can be used for reporting useful errors.
44 // This is optional. You can also pass `.{}` to `clap.parse` if you don't 45 // This is optional. You can also pass `.{}` to `clap.parse` if you don't
@@ -73,8 +74,8 @@ The fields in `args` are typed. The type is based on the name of the value the p
73Since `--number` takes a `usize` the field `res.args.number` has the type `usize`. 74Since `--number` takes a `usize` the field `res.args.number` has the type `usize`.
74 75
75Note that this is only the case because `clap.parsers.default` has a field called `usize` which 76Note that this is only the case because `clap.parsers.default` has a field called `usize` which
76contains a parser that returns `usize`. You can pass in something other than `clap.parsers.default` 77contains a parser that returns `usize`. You can pass in something other than
77if you want some other mapping. 78`clap.parsers.default` if you want some other mapping.
78 79
79```zig 80```zig
80const clap = @import("clap"); 81const clap = @import("clap");
@@ -86,13 +87,14 @@ const process = std.process;
86 87
87pub fn main() !void { 88pub fn main() !void {
88 // First we specify what parameters our program can take. 89 // First we specify what parameters our program can take.
89 // We can use `parseParam` to parse a string to a `Param(Help)` 90 // We can use `parseParamsComptime` to parse a string into an array of `Param(Help)`
90 const params = comptime [_]clap.Param(clap.Help){ 91 const params = comptime clap.parseParamsComptime(
91 clap.parseParam("-h, --help Display this help and exit.") catch unreachable, 92 \\-h, --help Display this help and exit.
92 clap.parseParam("-n, --number <INT> An option parameter, which takes a value.") catch unreachable, 93 \\-n, --number <INT> An option parameter, which takes a value.
93 clap.parseParam("-s, --string <STR>... An option parameter which can be specified multiple times.") catch unreachable, 94 \\-s, --string <STR>... An option parameter which can be specified multiple times.
94 clap.parseParam("<FILE>...") catch unreachable, 95 \\<FILE>...
95 }; 96 \\
97 );
96 98
97 // Declare our own parsers which are used to map the argument strings to other 99 // Declare our own parsers which are used to map the argument strings to other
98 // types. 100 // types.
@@ -205,10 +207,11 @@ const clap = @import("clap");
205const std = @import("std"); 207const std = @import("std");
206 208
207pub fn main() !void { 209pub fn main() !void {
208 const params = comptime [_]clap.Param(clap.Help){ 210 const params = comptime clap.parseParamsComptime(
209 clap.parseParam("-h, --help Display this help and exit. ") catch unreachable, 211 \\-h, --help Display this help and exit.
210 clap.parseParam("-v, --version Output version information and exit.") catch unreachable, 212 \\-v, --version Output version information and exit.
211 }; 213 \\
214 );
212 215
213 var res = try clap.parse(clap.Help, &params, clap.parsers.default, .{}); 216 var res = try clap.parse(clap.Help, &params, clap.parsers.default, .{});
214 defer res.deinit(); 217 defer res.deinit();
@@ -238,11 +241,12 @@ const clap = @import("clap");
238const std = @import("std"); 241const std = @import("std");
239 242
240pub fn main() !void { 243pub fn main() !void {
241 const params = comptime [_]clap.Param(clap.Help){ 244 const params = comptime clap.parseParamsComptime(
242 clap.parseParam("-h, --help Display this help and exit.") catch unreachable, 245 \\-h, --help Display this help and exit.
243 clap.parseParam("-v, --version Output version information and exit.") catch unreachable, 246 \\-v, --version Output version information and exit.
244 clap.parseParam(" --value <str> An option parameter, which takes a value.") catch unreachable, 247 \\ --value <str> An option parameter, which takes a value.
245 }; 248 \\
249 );
246 250
247 var res = try clap.parse(clap.Help, &params, clap.parsers.default, .{}); 251 var res = try clap.parse(clap.Help, &params, clap.parsers.default, .{});
248 defer res.deinit(); 252 defer res.deinit();
diff --git a/clap.zig b/clap.zig
index d55ccc2..0a08634 100644
--- a/clap.zig
+++ b/clap.zig
@@ -78,9 +78,76 @@ pub fn Param(comptime Id: type) type {
78 }; 78 };
79} 79}
80 80
81/// Takes a string and parses it into many Param(Help). Returned is a newly allocated slice
82/// containing all the parsed params. The caller is responsible for freeing the slice.
83pub fn parseParams(allocator: mem.Allocator, str: []const u8) ![]Param(Help) {
84 var list = std.ArrayList(Param(Help)).init(allocator);
85 errdefer list.deinit();
86
87 try parseParamsIntoArrayList(&list, str);
88 return list.toOwnedSlice();
89}
90
91/// Takes a string and parses it into many Param(Help) at comptime. Returned is an array of
92/// exactly the number of params that was parsed from `str`. A parse error becomes a compiler
93/// error.
94pub fn parseParamsComptime(comptime str: []const u8) [countParams(str)]Param(Help) {
95 var res: [countParams(str)]Param(Help) = undefined;
96 _ = parseParamsIntoSlice(&res, str) catch unreachable;
97 return res;
98}
99
100fn countParams(str: []const u8) usize {
101 // See parseParam for reasoning. I would like to remove it from parseParam, but people depend
102 // on that function to still work conveniently at comptime, so leaving it for now.
103 @setEvalBranchQuota(std.math.maxInt(u32));
104
105 var res: usize = 0;
106 var it = mem.split(u8, str, "\n");
107 while (it.next()) |line| {
108 const trimmed = mem.trimLeft(u8, line, " \t");
109 if (mem.startsWith(u8, trimmed, "-") or
110 mem.startsWith(u8, trimmed, "<"))
111 {
112 res += 1;
113 }
114 }
115
116 return res;
117}
118
119/// Takes a string and parses it into many Param(Help), which are written to `slice`. A subslice
120/// is returned, containing all the parameters parsed. This function will fail if the input slice
121/// is to small.
122pub fn parseParamsIntoSlice(slice: []Param(Help), str: []const u8) ![]Param(Help) {
123 var null_alloc = heap.FixedBufferAllocator.init("");
124 var list = std.ArrayList(Param(Help)){
125 .allocator = null_alloc.allocator(),
126 .items = slice[0..0],
127 .capacity = slice.len,
128 };
129
130 try parseParamsIntoArrayList(&list, str);
131 return list.items;
132}
133
134/// Takes a string and parses it into many Param(Help), which are appended onto `list`.
135pub fn parseParamsIntoArrayList(list: *std.ArrayList(Param(Help)), str: []const u8) !void {
136 var i: usize = 0;
137 while (i != str.len) {
138 var end: usize = undefined;
139 try list.append(try parseParamEx(str[i..], &end));
140 i += end;
141 }
142}
143
144pub fn parseParam(str: []const u8) !Param(Help) {
145 var end: usize = undefined;
146 return parseParamEx(str, &end);
147}
148
81/// Takes a string and parses it to a Param(Help). 149/// Takes a string and parses it to a Param(Help).
82/// This is the reverse of 'help' but for at single parameter only. 150pub fn parseParamEx(str: []const u8, end: *usize) !Param(Help) {
83pub fn parseParam(line: []const u8) !Param(Help) {
84 // This function become a lot less ergonomic to use once you hit the eval branch quota. To 151 // This function become a lot less ergonomic to use once you hit the eval branch quota. To
85 // avoid this we pick a sane default. Sadly, the only sane default is the biggest possible 152 // avoid this we pick a sane default. Sadly, the only sane default is the biggest possible
86 // value. If we pick something a lot smaller and a user hits the quota after that, they have 153 // value. If we pick something a lot smaller and a user hits the quota after that, they have
@@ -120,157 +187,177 @@ pub fn parseParam(line: []const u8) !Param(Help) {
120 rest_of_description, 187 rest_of_description,
121 rest_of_description_new_line, 188 rest_of_description_new_line,
122 } = .start; 189 } = .start;
123 for (line) |c, i| switch (state) { 190 for (str) |c, i| {
124 .start => switch (c) { 191 errdefer end.* = i;
125 ' ', '\t', '\n' => {}, 192
126 '-' => state = .start_of_short_name, 193 switch (state) {
127 '<' => state = .first_char_of_value, 194 .start => switch (c) {
128 else => return error.InvalidParameter, 195 ' ', '\t', '\n' => {},
129 }, 196 '-' => state = .start_of_short_name,
197 '<' => state = .first_char_of_value,
198 else => return error.InvalidParameter,
199 },
130 200
131 .start_of_short_name => switch (c) { 201 .start_of_short_name => switch (c) {
132 '-' => state = .first_char_of_long_name, 202 '-' => state = .first_char_of_long_name,
133 'a'...'z', 'A'...'Z', '0'...'9' => { 203 'a'...'z', 'A'...'Z', '0'...'9' => {
134 res.names.short = c; 204 res.names.short = c;
135 state = .end_of_short_name; 205 state = .end_of_short_name;
206 },
207 else => return error.InvalidParameter,
208 },
209 .end_of_short_name => switch (c) {
210 ' ', '\t' => state = .before_long_name_or_value_or_description,
211 '\n' => state = .before_description_new_line,
212 ',' => state = .before_long_name,
213 else => return error.InvalidParameter,
136 }, 214 },
137 else => return error.InvalidParameter,
138 },
139 .end_of_short_name => switch (c) {
140 ' ', '\t' => state = .before_long_name_or_value_or_description,
141 '\n' => state = .before_description_new_line,
142 ',' => state = .before_long_name,
143 else => return error.InvalidParameter,
144 },
145 215
146 .before_long_name => switch (c) { 216 .before_long_name => switch (c) {
147 ' ', '\t' => {}, 217 ' ', '\t' => {},
148 '-' => state = .start_of_long_name, 218 '-' => state = .start_of_long_name,
149 else => return error.InvalidParameter, 219 else => return error.InvalidParameter,
150 },
151 .start_of_long_name => switch (c) {
152 '-' => state = .first_char_of_long_name,
153 else => return error.InvalidParameter,
154 },
155 .first_char_of_long_name => switch (c) {
156 'a'...'z', 'A'...'Z', '0'...'9' => {
157 start = i;
158 state = .rest_of_long_name;
159 }, 220 },
160 else => return error.InvalidParameter, 221 .start_of_long_name => switch (c) {
161 }, 222 '-' => state = .first_char_of_long_name,
162 .rest_of_long_name => switch (c) { 223 else => return error.InvalidParameter,
163 'a'...'z', 'A'...'Z', '0'...'9' => {},
164 ' ', '\t' => {
165 res.names.long = line[start..i];
166 state = .before_value_or_description;
167 }, 224 },
168 '\n' => { 225 .first_char_of_long_name => switch (c) {
169 res.names.long = line[start..i]; 226 'a'...'z', 'A'...'Z', '0'...'9' => {
170 state = .before_description_new_line; 227 start = i;
228 state = .rest_of_long_name;
229 },
230 else => return error.InvalidParameter,
231 },
232 .rest_of_long_name => switch (c) {
233 'a'...'z', 'A'...'Z', '0'...'9' => {},
234 ' ', '\t' => {
235 res.names.long = str[start..i];
236 state = .before_value_or_description;
237 },
238 '\n' => {
239 res.names.long = str[start..i];
240 state = .before_description_new_line;
241 },
242 else => return error.InvalidParameter,
171 }, 243 },
172 else => return error.InvalidParameter,
173 },
174 244
175 .before_long_name_or_value_or_description => switch (c) { 245 .before_long_name_or_value_or_description => switch (c) {
176 ' ', '\t' => {}, 246 ' ', '\t' => {},
177 ',' => state = .before_long_name, 247 ',' => state = .before_long_name,
178 '<' => state = .first_char_of_value, 248 '<' => state = .first_char_of_value,
179 else => { 249 else => {
180 start = i; 250 start = i;
181 state = .rest_of_description; 251 state = .rest_of_description;
252 },
182 }, 253 },
183 },
184 254
185 .before_value_or_description => switch (c) { 255 .before_value_or_description => switch (c) {
186 ' ', '\t' => {}, 256 ' ', '\t' => {},
187 '<' => state = .first_char_of_value, 257 '<' => state = .first_char_of_value,
188 else => { 258 else => {
189 start = i; 259 start = i;
190 state = .rest_of_description; 260 state = .rest_of_description;
261 },
191 }, 262 },
192 }, 263 .first_char_of_value => switch (c) {
193 .first_char_of_value => switch (c) { 264 '>' => return error.InvalidParameter,
194 '>' => return error.InvalidParameter, 265 else => {
195 else => { 266 start = i;
196 start = i; 267 state = .rest_of_value;
197 state = .rest_of_value; 268 },
198 }, 269 },
199 }, 270 .rest_of_value => switch (c) {
200 .rest_of_value => switch (c) { 271 '>' => {
201 '>' => { 272 res.takes_value = .one;
202 res.takes_value = .one; 273 res.id.val = str[start..i];
203 res.id.val = line[start..i]; 274 state = .end_of_one_value;
204 state = .end_of_one_value; 275 },
276 else => {},
205 }, 277 },
206 else => {}, 278 .end_of_one_value => switch (c) {
207 }, 279 '.' => state = .second_dot_of_multi_value,
208 .end_of_one_value => switch (c) { 280 ' ', '\t' => state = .before_description,
209 '.' => state = .second_dot_of_multi_value, 281 '\n' => state = .before_description_new_line,
210 ' ', '\t' => state = .before_description, 282 else => {
211 '\n' => state = .before_description_new_line, 283 start = i;
212 else => { 284 state = .rest_of_description;
213 start = i; 285 },
214 state = .rest_of_description;
215 }, 286 },
216 }, 287 .second_dot_of_multi_value => switch (c) {
217 .second_dot_of_multi_value => switch (c) { 288 '.' => state = .third_dot_of_multi_value,
218 '.' => state = .third_dot_of_multi_value, 289 else => return error.InvalidParameter,
219 else => return error.InvalidParameter, 290 },
220 }, 291 .third_dot_of_multi_value => switch (c) {
221 .third_dot_of_multi_value => switch (c) { 292 '.' => {
222 '.' => { 293 res.takes_value = .many;
223 res.takes_value = .many; 294 state = .before_description;
224 state = .before_description; 295 },
296 else => return error.InvalidParameter,
225 }, 297 },
226 else => return error.InvalidParameter,
227 },
228 298
229 .before_description => switch (c) { 299 .before_description => switch (c) {
230 ' ', '\t' => {}, 300 ' ', '\t' => {},
231 '\n' => state = .before_description_new_line, 301 '\n' => state = .before_description_new_line,
232 else => { 302 else => {
233 start = i; 303 start = i;
234 state = .rest_of_description; 304 state = .rest_of_description;
305 },
235 }, 306 },
236 }, 307 .before_description_new_line => switch (c) {
237 .before_description_new_line => switch (c) { 308 ' ', '\t', '\n' => {},
238 ' ', '\t', '\n' => {}, 309 '-', '<' => {
239 '-' => break, 310 end.* = i;
240 else => { 311 break;
241 start = i; 312 },
242 state = .rest_of_description; 313 else => {
314 start = i;
315 state = .rest_of_description;
316 },
243 }, 317 },
244 }, 318 .rest_of_description => switch (c) {
245 .rest_of_description => switch (c) { 319 '\n' => state = .rest_of_description_new_line,
246 '\n' => state = .rest_of_description_new_line, 320 else => {},
247 else => {},
248 },
249 .rest_of_description_new_line => switch (c) {
250 ' ', '\t', '\n' => {},
251 '-' => {
252 res.id.desc = mem.trimRight(u8, line[start..i], " \t\n\r");
253 break;
254 }, 321 },
255 else => state = .rest_of_description, 322 .rest_of_description_new_line => switch (c) {
256 }, 323 ' ', '\t', '\n' => {},
257 } else switch (state) { 324 '-', '<' => {
258 .rest_of_description, .rest_of_description_new_line => { 325 res.id.desc = mem.trimRight(u8, str[start..i], " \t\n\r");
259 res.id.desc = mem.trimRight(u8, line[start..], " \t\n\r"); 326 end.* = i;
260 }, 327 break;
261 .rest_of_long_name => res.names.long = line[start..], 328 },
262 .end_of_short_name, 329 else => state = .rest_of_description,
263 .end_of_one_value, 330 },
264 .before_value_or_description, 331 }
265 .before_description, 332 } else {
266 .before_description_new_line, 333 end.* = str.len;
267 => {}, 334 switch (state) {
268 else => return error.InvalidParameter, 335 .rest_of_description, .rest_of_description_new_line => {
336 res.id.desc = mem.trimRight(u8, str[start..], " \t\n\r");
337 },
338 .rest_of_long_name => res.names.long = str[start..],
339 .end_of_short_name,
340 .end_of_one_value,
341 .before_value_or_description,
342 .before_description,
343 .before_description_new_line,
344 => {},
345 else => return error.InvalidParameter,
346 }
269 } 347 }
270 348
271 return res; 349 return res;
272} 350}
273 351
352fn testParseParams(str: []const u8, expected_params: []const Param(Help)) !void {
353 const actual_params = try parseParams(testing.allocator, str);
354 defer testing.allocator.free(actual_params);
355
356 try testing.expectEqual(expected_params.len, actual_params.len);
357 for (expected_params) |_, i|
358 try expectParam(expected_params[i], actual_params[i]);
359}
360
274fn expectParam(expect: Param(Help), actual: Param(Help)) !void { 361fn expectParam(expect: Param(Help), actual: Param(Help)) !void {
275 try testing.expectEqualStrings(expect.id.desc, actual.id.desc); 362 try testing.expectEqualStrings(expect.id.desc, actual.id.desc);
276 try testing.expectEqualStrings(expect.id.val, actual.id.val); 363 try testing.expectEqualStrings(expect.id.val, actual.id.val);
@@ -283,157 +370,112 @@ fn expectParam(expect: Param(Help), actual: Param(Help)) !void {
283 } 370 }
284} 371}
285 372
286test "parseParam" { 373test "parseParams" {
287 try expectParam(.{ 374 try testParseParams(
288 .id = .{}, 375 \\-s
289 .names = .{ .short = 's' }, 376 \\--str
290 }, try parseParam("-s")); 377 \\-s, --str
291 378 \\--str <str>
292 try expectParam(.{ 379 \\-s, --str <str>
293 .id = .{}, 380 \\-s, --long <val> Help text
294 .names = .{ .long = "str" }, 381 \\-s, --long <val>... Help text
295 }, try parseParam("--str")); 382 \\--long <val> Help text
296 383 \\-s <val> Help text
297 try expectParam(.{ 384 \\-s, --long Help text
298 .id = .{}, 385 \\-s Help text
299 .names = .{ .short = 's', .long = "str" }, 386 \\--long Help text
300 }, try parseParam("-s, --str")); 387 \\--long <A | B> Help text
301 388 \\<A> Help text
302 try expectParam(.{ 389 \\<A>... Help text
303 .id = .{ .val = "str" },
304 .names = .{ .long = "str" },
305 .takes_value = .one,
306 }, try parseParam("--str <str>"));
307
308 try expectParam(.{
309 .id = .{ .val = "str" },
310 .names = .{ .short = 's', .long = "str" },
311 .takes_value = .one,
312 }, try parseParam("-s, --str <str>"));
313
314 try expectParam(.{
315 .id = .{ .desc = "Help text", .val = "val" },
316 .names = .{ .short = 's', .long = "long" },
317 .takes_value = .one,
318 }, try parseParam("-s, --long <val> Help text"));
319
320 try expectParam(.{
321 .id = .{ .desc = "Help text", .val = "val" },
322 .names = .{ .short = 's', .long = "long" },
323 .takes_value = .many,
324 }, try parseParam("-s, --long <val>... Help text"));
325
326 try expectParam(.{
327 .id = .{ .desc = "Help text", .val = "val" },
328 .names = .{ .long = "long" },
329 .takes_value = .one,
330 }, try parseParam("--long <val> Help text"));
331
332 try expectParam(.{
333 .id = .{ .desc = "Help text", .val = "val" },
334 .names = .{ .short = 's' },
335 .takes_value = .one,
336 }, try parseParam("-s <val> Help text"));
337
338 try expectParam(.{
339 .id = .{ .desc = "Help text" },
340 .names = .{ .short = 's', .long = "long" },
341 }, try parseParam("-s, --long Help text"));
342
343 try expectParam(.{
344 .id = .{ .desc = "Help text" },
345 .names = .{ .short = 's' },
346 }, try parseParam("-s Help text"));
347
348 try expectParam(.{
349 .id = .{ .desc = "Help text" },
350 .names = .{ .long = "long" },
351 }, try parseParam("--long Help text"));
352
353 try expectParam(.{
354 .id = .{ .desc = "Help text", .val = "A | B" },
355 .names = .{ .long = "long" },
356 .takes_value = .one,
357 }, try parseParam("--long <A | B> Help text"));
358
359 try expectParam(.{
360 .id = .{ .desc = "Help text", .val = "A" },
361 .names = .{},
362 .takes_value = .one,
363 }, try parseParam("<A> Help text"));
364
365 try expectParam(.{
366 .id = .{ .desc = "Help text", .val = "A" },
367 .names = .{},
368 .takes_value = .many,
369 }, try parseParam("<A>... Help text"));
370
371 try expectParam(.{
372 .id = .{
373 .desc =
374 \\This is
375 \\ help spanning multiple
376 \\ lines
377 ,
378 },
379 .names = .{ .long = "aa" },
380 .takes_value = .none,
381 }, try parseParam(
382 \\--aa This is 390 \\--aa This is
383 \\ help spanning multiple 391 \\ help spanning multiple
384 \\ lines 392 \\ lines
385 \\ 393 \\
386 ));
387
388 try expectParam(.{
389 .id = .{ .desc = "This msg should end and the newline cause of new param" },
390 .names = .{ .long = "aa" },
391 .takes_value = .none,
392 }, try parseParam(
393 \\--aa This msg should end and the newline cause of new param 394 \\--aa This msg should end and the newline cause of new param
394 \\--bb This should not end up being parsed 395 \\--bb This should be a new param
395 \\
396 ));
397
398 try expectParam(.{
399 .id = .{},
400 .names = .{ .short = 'a' },
401 .takes_value = .none,
402 }, try parseParam(
403 \\-a
404 \\--bb
405 \\
406 ));
407
408 try expectParam(.{
409 .id = .{},
410 .names = .{ .long = "aa" },
411 .takes_value = .none,
412 }, try parseParam(
413 \\--aa
414 \\--bb
415 \\ 396 \\
416 )); 397 , &.{
417 398 .{ .names = .{ .short = 's' } },
418 try expectParam(.{ 399 .{ .names = .{ .long = "str" } },
419 .id = .{ .val = "q" }, 400 .{ .names = .{ .short = 's', .long = "str" } },
420 .names = .{ .short = 'a' }, 401 .{
421 .takes_value = .one, 402 .id = .{ .val = "str" },
422 }, try parseParam( 403 .names = .{ .long = "str" },
423 \\-a <q> 404 .takes_value = .one,
424 \\--bb 405 },
425 \\ 406 .{
426 )); 407 .id = .{ .val = "str" },
427 408 .names = .{ .short = 's', .long = "str" },
428 try expectParam(.{ 409 .takes_value = .one,
429 .id = .{ .val = "q" }, 410 },
430 .names = .{ .short = 'a' }, 411 .{
431 .takes_value = .many, 412 .id = .{ .desc = "Help text", .val = "val" },
432 }, try parseParam( 413 .names = .{ .short = 's', .long = "long" },
433 \\-a <q>... 414 .takes_value = .one,
434 \\--bb 415 },
435 \\ 416 .{
436 )); 417 .id = .{ .desc = "Help text", .val = "val" },
418 .names = .{ .short = 's', .long = "long" },
419 .takes_value = .many,
420 },
421 .{
422 .id = .{ .desc = "Help text", .val = "val" },
423 .names = .{ .long = "long" },
424 .takes_value = .one,
425 },
426 .{
427 .id = .{ .desc = "Help text", .val = "val" },
428 .names = .{ .short = 's' },
429 .takes_value = .one,
430 },
431 .{
432 .id = .{ .desc = "Help text" },
433 .names = .{ .short = 's', .long = "long" },
434 },
435 .{
436 .id = .{ .desc = "Help text" },
437 .names = .{ .short = 's' },
438 },
439 .{
440 .id = .{ .desc = "Help text" },
441 .names = .{ .long = "long" },
442 },
443 .{
444 .id = .{ .desc = "Help text", .val = "A | B" },
445 .names = .{ .long = "long" },
446 .takes_value = .one,
447 },
448 .{
449 .id = .{ .desc = "Help text", .val = "A" },
450 .takes_value = .one,
451 },
452 .{
453 .id = .{ .desc = "Help text", .val = "A" },
454 .names = .{},
455 .takes_value = .many,
456 },
457 .{
458 .id = .{
459 .desc =
460 \\This is
461 \\ help spanning multiple
462 \\ lines
463 ,
464 },
465 .names = .{ .long = "aa" },
466 .takes_value = .none,
467 },
468 .{
469 .id = .{ .desc = "This msg should end and the newline cause of new param" },
470 .names = .{ .long = "aa" },
471 .takes_value = .none,
472 },
473 .{
474 .id = .{ .desc = "This should be a new param" },
475 .names = .{ .long = "bb" },
476 .takes_value = .none,
477 },
478 });
437 479
438 try testing.expectError(error.InvalidParameter, parseParam("--long, Help")); 480 try testing.expectError(error.InvalidParameter, parseParam("--long, Help"));
439 try testing.expectError(error.InvalidParameter, parseParam("-s, Help")); 481 try testing.expectError(error.InvalidParameter, parseParam("-s, Help"));
@@ -821,33 +863,35 @@ fn Arguments(
821} 863}
822 864
823test "str and u64" { 865test "str and u64" {
824 const params = comptime &.{ 866 const params = comptime parseParamsComptime(
825 parseParam("--str <str>") catch unreachable, 867 \\--str <str>
826 parseParam("--num <u64>") catch unreachable, 868 \\--num <u64>
827 }; 869 \\
870 );
828 871
829 var iter = args.SliceIterator{ 872 var iter = args.SliceIterator{
830 .args = &.{ "--num", "10", "--str", "cooley_rec_inp_ptr" }, 873 .args = &.{ "--num", "10", "--str", "cooley_rec_inp_ptr" },
831 }; 874 };
832 var res = try parseEx(Help, params, parsers.default, &iter, .{ 875 var res = try parseEx(Help, &params, parsers.default, &iter, .{
833 .allocator = testing.allocator, 876 .allocator = testing.allocator,
834 }); 877 });
835 defer res.deinit(); 878 defer res.deinit();
836} 879}
837 880
838test "" { 881test "" {
839 const params = comptime &.{ 882 const params = comptime parseParamsComptime(
840 parseParam("-a, --aa") catch unreachable, 883 \\-a, --aa
841 parseParam("-b, --bb") catch unreachable, 884 \\-b, --bb
842 parseParam("-c, --cc <str>") catch unreachable, 885 \\-c, --cc <str>
843 parseParam("-d, --dd <usize>...") catch unreachable, 886 \\-d, --dd <usize>...
844 parseParam("<str>") catch unreachable, 887 \\<str>
845 }; 888 \\
889 );
846 890
847 var iter = args.SliceIterator{ 891 var iter = args.SliceIterator{
848 .args = &.{ "-a", "-c", "0", "something", "-d", "1", "--dd", "2" }, 892 .args = &.{ "-a", "-c", "0", "something", "-d", "1", "--dd", "2" },
849 }; 893 };
850 var res = try parseEx(Help, params, parsers.default, &iter, .{ 894 var res = try parseEx(Help, &params, parsers.default, &iter, .{
851 .allocator = testing.allocator, 895 .allocator = testing.allocator,
852 }); 896 });
853 defer res.deinit(); 897 defer res.deinit();
@@ -888,10 +932,11 @@ fn testErr(
888} 932}
889 933
890test "errors" { 934test "errors" {
891 const params = comptime [_]Param(Help){ 935 const params = comptime parseParamsComptime(
892 parseParam("-a, --aa") catch unreachable, 936 \\-a, --aa
893 parseParam("-c, --cc <str>") catch unreachable, 937 \\-c, --cc <str>
894 }; 938 \\
939 );
895 940
896 try testErr(&params, &.{"q"}, "Invalid argument 'q'\n"); 941 try testErr(&params, &.{"q"}, "Invalid argument 'q'\n");
897 try testErr(&params, &.{"-q"}, "Invalid argument '-q'\n"); 942 try testErr(&params, &.{"-q"}, "Invalid argument '-q'\n");
@@ -957,7 +1002,7 @@ pub fn help(stream: anytype, comptime Id: type, params: []const Param(Id)) !void
957 try stream.writeByteNTimes(' ', max_spacing); 1002 try stream.writeByteNTimes(' ', max_spacing);
958 } 1003 }
959 try stream.writeAll("\t"); 1004 try stream.writeAll("\t");
960 try stream.writeAll(line); 1005 try stream.writeAll(mem.trimLeft(u8, line, " \t"));
961 try stream.writeAll("\n"); 1006 try stream.writeAll("\n");
962 } 1007 }
963 } 1008 }
@@ -998,25 +1043,22 @@ test "clap.help" {
998 var buf: [1024]u8 = undefined; 1043 var buf: [1024]u8 = undefined;
999 var slice_stream = io.fixedBufferStream(&buf); 1044 var slice_stream = io.fixedBufferStream(&buf);
1000 1045
1001 @setEvalBranchQuota(10000); 1046 const params = comptime parseParamsComptime(
1002 try help( 1047 \\-a Short flag.
1003 slice_stream.writer(), 1048 \\-b <V1> Short option.
1004 Help, 1049 \\--aa Long flag.
1005 comptime &.{ 1050 \\--bb <V2> Long option.
1006 parseParam("-a Short flag.") catch unreachable, 1051 \\-c, --cc Both flag.
1007 parseParam("-b <V1> Short option.") catch unreachable, 1052 \\--complicate Flag with a complicated and
1008 parseParam("--aa Long flag.") catch unreachable, 1053 \\ very long description that
1009 parseParam("--bb <V2> Long option.") catch unreachable, 1054 \\ spans multiple lines.
1010 parseParam("-c, --cc Both flag.") catch unreachable, 1055 \\-d, --dd <V3> Both option.
1011 parseParam("--complicate Flag with a complicated and\nvery long description that\nspans multiple lines.") catch unreachable, 1056 \\-d, --dd <V3>... Both repeated option.
1012 parseParam("-d, --dd <V3> Both option.") catch unreachable, 1057 \\<P> Positional. This should not appear in the help message.
1013 parseParam("-d, --dd <V3>... Both repeated option.") catch unreachable, 1058 \\
1014 parseParam(
1015 "<P> Positional. This should not appear in the help message.",
1016 ) catch unreachable,
1017 },
1018 ); 1059 );
1019 1060
1061 try help(slice_stream.writer(), Help, &params);
1020 const expected = "" ++ 1062 const expected = "" ++
1021 "\t-a \tShort flag.\n" ++ 1063 "\t-a \tShort flag.\n" ++
1022 "\t-b <V1> \tShort option.\n" ++ 1064 "\t-b <V1> \tShort option.\n" ++
@@ -1105,38 +1147,44 @@ fn testUsage(expected: []const u8, params: []const Param(Help)) !void {
1105 1147
1106test "usage" { 1148test "usage" {
1107 @setEvalBranchQuota(100000); 1149 @setEvalBranchQuota(100000);
1108 try testUsage("[-ab]", &.{ 1150 try testUsage("[-ab]", &comptime parseParamsComptime(
1109 try parseParam("-a"), 1151 \\-a
1110 try parseParam("-b"), 1152 \\-b
1111 }); 1153 \\
1112 try testUsage("[-a <value>] [-b <v>]", &.{ 1154 ));
1113 try parseParam("-a <value>"), 1155 try testUsage("[-a <value>] [-b <v>]", &comptime parseParamsComptime(
1114 try parseParam("-b <v>"), 1156 \\-a <value>
1115 }); 1157 \\-b <v>
1116 try testUsage("[--a] [--b]", &.{ 1158 \\
1117 try parseParam("--a"), 1159 ));
1118 try parseParam("--b"), 1160 try testUsage("[--a] [--b]", &comptime parseParamsComptime(
1119 }); 1161 \\--a
1120 try testUsage("[--a <value>] [--b <v>]", &.{ 1162 \\--b
1121 try parseParam("--a <value>"), 1163 \\
1122 try parseParam("--b <v>"), 1164 ));
1123 }); 1165 try testUsage("[--a <value>] [--b <v>]", &comptime parseParamsComptime(
1124 try testUsage("<file>", &.{ 1166 \\--a <value>
1125 try parseParam("<file>"), 1167 \\--b <v>
1126 }); 1168 \\
1169 ));
1170 try testUsage("<file>", &comptime parseParamsComptime(
1171 \\<file>
1172 \\
1173 ));
1127 try testUsage( 1174 try testUsage(
1128 "[-ab] [-c <value>] [-d <v>] [--e] [--f] [--g <value>] [--h <v>] [-i <v>...] <file>", 1175 "[-ab] [-c <value>] [-d <v>] [--e] [--f] [--g <value>] [--h <v>] [-i <v>...] <file>",
1129 &.{ 1176 &comptime parseParamsComptime(
1130 try parseParam("-a"), 1177 \\-a
1131 try parseParam("-b"), 1178 \\-b
1132 try parseParam("-c <value>"), 1179 \\-c <value>
1133 try parseParam("-d <v>"), 1180 \\-d <v>
1134 try parseParam("--e"), 1181 \\--e
1135 try parseParam("--f"), 1182 \\--f
1136 try parseParam("--g <value>"), 1183 \\--g <value>
1137 try parseParam("--h <v>"), 1184 \\--h <v>
1138 try parseParam("-i <v>..."), 1185 \\-i <v>...
1139 try parseParam("<file>"), 1186 \\<file>
1140 }, 1187 \\
1188 ),
1141 ); 1189 );
1142} 1190}
diff --git a/example/README.md.template b/example/README.md.template
index 9fbc1cc..c19c1cd 100644
--- a/example/README.md.template
+++ b/example/README.md.template
@@ -35,8 +35,8 @@ The fields in `args` are typed. The type is based on the name of the value the p
35Since `--number` takes a `usize` the field `res.args.number` has the type `usize`. 35Since `--number` takes a `usize` the field `res.args.number` has the type `usize`.
36 36
37Note that this is only the case because `clap.parsers.default` has a field called `usize` which 37Note that this is only the case because `clap.parsers.default` has a field called `usize` which
38contains a parser that returns `usize`. You can pass in something other than `clap.parsers.default` 38contains a parser that returns `usize`. You can pass in something other than
39if you want some other mapping. 39`clap.parsers.default` if you want some other mapping.
40 40
41```zig 41```zig
42{s} 42{s}
diff --git a/example/help.zig b/example/help.zig
index f3edb58..64d1709 100644
--- a/example/help.zig
+++ b/example/help.zig
@@ -2,10 +2,11 @@ const clap = @import("clap");
2const std = @import("std"); 2const std = @import("std");
3 3
4pub fn main() !void { 4pub fn main() !void {
5 const params = comptime [_]clap.Param(clap.Help){ 5 const params = comptime clap.parseParamsComptime(
6 clap.parseParam("-h, --help Display this help and exit. ") catch unreachable, 6 \\-h, --help Display this help and exit.
7 clap.parseParam("-v, --version Output version information and exit.") catch unreachable, 7 \\-v, --version Output version information and exit.
8 }; 8 \\
9 );
9 10
10 var res = try clap.parse(clap.Help, &params, clap.parsers.default, .{}); 11 var res = try clap.parse(clap.Help, &params, clap.parsers.default, .{});
11 defer res.deinit(); 12 defer res.deinit();
diff --git a/example/simple-ex.zig b/example/simple-ex.zig
index 6cb4c3f..d0d214d 100644
--- a/example/simple-ex.zig
+++ b/example/simple-ex.zig
@@ -7,13 +7,14 @@ const process = std.process;
7 7
8pub fn main() !void { 8pub fn main() !void {
9 // First we specify what parameters our program can take. 9 // First we specify what parameters our program can take.
10 // We can use `parseParam` to parse a string to a `Param(Help)` 10 // We can use `parseParamsComptime` to parse a string into an array of `Param(Help)`
11 const params = comptime [_]clap.Param(clap.Help){ 11 const params = comptime clap.parseParamsComptime(
12 clap.parseParam("-h, --help Display this help and exit.") catch unreachable, 12 \\-h, --help Display this help and exit.
13 clap.parseParam("-n, --number <INT> An option parameter, which takes a value.") catch unreachable, 13 \\-n, --number <INT> An option parameter, which takes a value.
14 clap.parseParam("-s, --string <STR>... An option parameter which can be specified multiple times.") catch unreachable, 14 \\-s, --string <STR>... An option parameter which can be specified multiple times.
15 clap.parseParam("<FILE>...") catch unreachable, 15 \\<FILE>...
16 }; 16 \\
17 );
17 18
18 // Declare our own parsers which are used to map the argument strings to other 19 // Declare our own parsers which are used to map the argument strings to other
19 // types. 20 // types.
diff --git a/example/simple.zig b/example/simple.zig
index 11e975e..1ac7de5 100644
--- a/example/simple.zig
+++ b/example/simple.zig
@@ -6,13 +6,14 @@ const io = std.io;
6 6
7pub fn main() !void { 7pub fn main() !void {
8 // First we specify what parameters our program can take. 8 // First we specify what parameters our program can take.
9 // We can use `parseParam` to parse a string to a `Param(Help)` 9 // We can use `parseParamsComptime` to parse a string into an array of `Param(Help)`
10 const params = comptime [_]clap.Param(clap.Help){ 10 const params = comptime clap.parseParamsComptime(
11 clap.parseParam("-h, --help Display this help and exit.") catch unreachable, 11 \\-h, --help Display this help and exit.
12 clap.parseParam("-n, --number <usize> An option parameter, which takes a value.") catch unreachable, 12 \\-n, --number <usize> An option parameter, which takes a value.
13 clap.parseParam("-s, --string <str>... An option parameter which can be specified multiple times.") catch unreachable, 13 \\-s, --string <str>... An option parameter which can be specified multiple times.
14 clap.parseParam("<str>...") catch unreachable, 14 \\<str>...
15 }; 15 \\
16 );
16 17
17 // Initalize our diagnostics, which can be used for reporting useful errors. 18 // Initalize our diagnostics, which can be used for reporting useful errors.
18 // This is optional. You can also pass `.{}` to `clap.parse` if you don't 19 // This is optional. You can also pass `.{}` to `clap.parse` if you don't
diff --git a/example/usage.zig b/example/usage.zig
index 20d4736..f57b07c 100644
--- a/example/usage.zig
+++ b/example/usage.zig
@@ -2,11 +2,12 @@ const clap = @import("clap");
2const std = @import("std"); 2const std = @import("std");
3 3
4pub fn main() !void { 4pub fn main() !void {
5 const params = comptime [_]clap.Param(clap.Help){ 5 const params = comptime clap.parseParamsComptime(
6 clap.parseParam("-h, --help Display this help and exit.") catch unreachable, 6 \\-h, --help Display this help and exit.
7 clap.parseParam("-v, --version Output version information and exit.") catch unreachable, 7 \\-v, --version Output version information and exit.
8 clap.parseParam(" --value <str> An option parameter, which takes a value.") catch unreachable, 8 \\ --value <str> An option parameter, which takes a value.
9 }; 9 \\
10 );
10 11
11 var res = try clap.parse(clap.Help, &params, clap.parsers.default, .{}); 12 var res = try clap.parse(clap.Help, &params, clap.parsers.default, .{});
12 defer res.deinit(); 13 defer res.deinit();