summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Jimmi Holst Christensen2020-03-05 23:24:36 +0100
committerGravatar Jimmi Holst Christensen2020-03-05 23:24:36 +0100
commitcc056cf4232721b62b76428d1ada447eecd98421 (patch)
tree56e859d0f91b372c50d6b35818877a4476ded5a1
parentAdd clap.parse as the simplest way of using the lib (diff)
downloadzig-clap-cc056cf4232721b62b76428d1ada447eecd98421.tar.gz
zig-clap-cc056cf4232721b62b76428d1ada447eecd98421.tar.xz
zig-clap-cc056cf4232721b62b76428d1ada447eecd98421.zip
Add clap.usage
-rw-r--r--README.md43
-rw-r--r--build.zig2
-rw-r--r--clap.zig193
-rw-r--r--example/README.md.template23
-rw-r--r--example/usage.zig20
5 files changed, 260 insertions, 21 deletions
diff --git a/README.md b/README.md
index 0555a0f..47c028d 100644
--- a/README.md
+++ b/README.md
@@ -232,3 +232,46 @@ The `helpEx` is the generic version of `help`. It can print a help message for a
232 232
233The `helpFull` is even more generic, allowing the functions that get the help and value strings 233The `helpFull` is even more generic, allowing the functions that get the help and value strings
234to return errors and take a context as a parameter. 234to return errors and take a context as a parameter.
235
236### `usage`
237
238The `usage`, `usageEx` and `usageFull` are functions for printing a simple list of all parameters the
239program can take.
240
241```zig
242const std = @import("std");
243const clap = @import("clap");
244
245pub fn main() !void {
246 const stderr_file = try std.io.getStdErr();
247 var stderr_out_stream = stderr_file.outStream();
248 const stderr = &stderr_out_stream.stream;
249
250 // clap.usage is a function that can print a simple usage message, given a
251 // slice of Param(Help). There is also a usageEx, which can print a
252 // usage message for any Param, but it is more verbose to call.
253 try clap.usage(
254 stderr,
255 comptime [_]clap.Param(clap.Help){
256 clap.parseParam("-h, --help Display this help and exit. ") catch unreachable,
257 clap.parseParam("-v, --version Output version information and exit.") catch unreachable,
258 clap.parseParam(" --value <N> Output version information and exit.") catch unreachable,
259 },
260 );
261}
262
263```
264
265```
266[-hv] [--value <N>]
267```
268
269The `usage` functions are the simplest to call. It only takes an `OutStream` and a slice of
270`Param(Help)`.
271
272The `usageEx` is the generic version of `usage`. It can print a usage message for any
273`Param` give that the caller provides functions for getting the usage and value strings.
274
275The `usageFull` is even more generic, allowing the functions that get the usage and value strings
276to return errors and take a context as a parameter.
277
diff --git a/build.zig b/build.zig
index 1a6fe09..e65df7f 100644
--- a/build.zig
+++ b/build.zig
@@ -32,6 +32,7 @@ pub fn build(b: *Builder) void {
32 "comptime-clap", 32 "comptime-clap",
33 "streaming-clap", 33 "streaming-clap",
34 "help", 34 "help",
35 "usage",
35 }) |example_name| { 36 }) |example_name| {
36 const example = b.addExecutable(example_name, "example/" ++ example_name ++ ".zig"); 37 const example = b.addExecutable(example_name, "example/" ++ example_name ++ ".zig");
37 example.addPackagePath("clap", "clap.zig"); 38 example.addPackagePath("clap", "clap.zig");
@@ -67,6 +68,7 @@ fn readMeStep(b: *Builder) *std.build.Step {
67 @embedFile("example/comptime-clap.zig"), 68 @embedFile("example/comptime-clap.zig"),
68 @embedFile("example/streaming-clap.zig"), 69 @embedFile("example/streaming-clap.zig"),
69 @embedFile("example/help.zig"), 70 @embedFile("example/help.zig"),
71 @embedFile("example/usage.zig"),
70 ); 72 );
71 } 73 }
72 }.make); 74 }.make);
diff --git a/clap.zig b/clap.zig
index e0d20e3..40e76f4 100644
--- a/clap.zig
+++ b/clap.zig
@@ -265,25 +265,25 @@ pub fn parse(
265} 265}
266 266
267/// Will print a help message in the following format: 267/// Will print a help message in the following format:
268/// -s, --long <value_text> help_text 268/// -s, --long <valueText> helpText
269/// -s, help_text 269/// -s, helpText
270/// -s <value_text> help_text 270/// -s <valueText> helpText
271/// --long help_text 271/// --long helpText
272/// --long <value_text> help_text 272/// --long <valueText> helpText
273pub fn helpFull( 273pub fn helpFull(
274 stream: var, 274 stream: var,
275 comptime Id: type, 275 comptime Id: type,
276 params: []const Param(Id), 276 params: []const Param(Id),
277 comptime Error: type, 277 comptime Error: type,
278 context: var, 278 context: var,
279 help_text: fn (@typeOf(context), Param(Id)) Error![]const u8, 279 helpText: fn (@typeOf(context), Param(Id)) Error![]const u8,
280 value_text: fn (@typeOf(context), Param(Id)) Error![]const u8, 280 valueText: fn (@typeOf(context), Param(Id)) Error![]const u8,
281) !void { 281) !void {
282 const max_spacing = blk: { 282 const max_spacing = blk: {
283 var res: usize = 0; 283 var res: usize = 0;
284 for (params) |param| { 284 for (params) |param| {
285 var counting_stream = io.CountingOutStream(io.NullOutStream.Error).init(io.null_out_stream); 285 var counting_stream = io.CountingOutStream(io.NullOutStream.Error).init(io.null_out_stream);
286 try printParam(&counting_stream.stream, Id, param, Error, context, value_text); 286 try printParam(&counting_stream.stream, Id, param, Error, context, valueText);
287 if (res < counting_stream.bytes_written) 287 if (res < counting_stream.bytes_written)
288 res = counting_stream.bytes_written; 288 res = counting_stream.bytes_written;
289 } 289 }
@@ -297,9 +297,9 @@ pub fn helpFull(
297 297
298 var counting_stream = io.CountingOutStream(@typeOf(stream.*).Error).init(stream); 298 var counting_stream = io.CountingOutStream(@typeOf(stream.*).Error).init(stream);
299 try stream.print("\t"); 299 try stream.print("\t");
300 try printParam(&counting_stream.stream, Id, param, Error, context, value_text); 300 try printParam(&counting_stream.stream, Id, param, Error, context, valueText);
301 try stream.writeByteNTimes(' ', max_spacing - counting_stream.bytes_written); 301 try stream.writeByteNTimes(' ', max_spacing - counting_stream.bytes_written);
302 try stream.print("\t{}\n", try help_text(context, param)); 302 try stream.print("\t{}\n", try helpText(context, param));
303 } 303 }
304} 304}
305 305
@@ -309,7 +309,7 @@ fn printParam(
309 param: Param(Id), 309 param: Param(Id),
310 comptime Error: type, 310 comptime Error: type,
311 context: var, 311 context: var,
312 value_text: fn (@typeOf(context), Param(Id)) Error![]const u8, 312 valueText: fn (@typeOf(context), Param(Id)) Error![]const u8,
313) @typeOf(stream.*).Error!void { 313) @typeOf(stream.*).Error!void {
314 if (param.names.short) |s| { 314 if (param.names.short) |s| {
315 try stream.print("-{c}", s); 315 try stream.print("-{c}", s);
@@ -326,28 +326,28 @@ fn printParam(
326 try stream.print("--{}", l); 326 try stream.print("--{}", l);
327 } 327 }
328 if (param.takes_value) 328 if (param.takes_value)
329 try stream.print(" <{}>", value_text(context, param)); 329 try stream.print(" <{}>", valueText(context, param));
330} 330}
331 331
332/// A wrapper around helpFull for simple help_text and value_text functions that 332/// A wrapper around helpFull for simple helpText and valueText functions that
333/// cant return an error or take a context. 333/// cant return an error or take a context.
334pub fn helpEx( 334pub fn helpEx(
335 stream: var, 335 stream: var,
336 comptime Id: type, 336 comptime Id: type,
337 params: []const Param(Id), 337 params: []const Param(Id),
338 help_text: fn (Param(Id)) []const u8, 338 helpText: fn (Param(Id)) []const u8,
339 value_text: fn (Param(Id)) []const u8, 339 valueText: fn (Param(Id)) []const u8,
340) !void { 340) !void {
341 const Context = struct { 341 const Context = struct {
342 help_text: fn (Param(Id)) []const u8, 342 helpText: fn (Param(Id)) []const u8,
343 value_text: fn (Param(Id)) []const u8, 343 valueText: fn (Param(Id)) []const u8,
344 344
345 pub fn help(c: @This(), p: Param(Id)) error{}![]const u8 { 345 pub fn help(c: @This(), p: Param(Id)) error{}![]const u8 {
346 return c.help_text(p); 346 return c.helpText(p);
347 } 347 }
348 348
349 pub fn value(c: @This(), p: Param(Id)) error{}![]const u8 { 349 pub fn value(c: @This(), p: Param(Id)) error{}![]const u8 {
350 return c.value_text(p); 350 return c.valueText(p);
351 } 351 }
352 }; 352 };
353 353
@@ -357,8 +357,8 @@ pub fn helpEx(
357 params, 357 params,
358 error{}, 358 error{},
359 Context{ 359 Context{
360 .help_text = help_text, 360 .helpText = helpText,
361 .value_text = value_text, 361 .valueText = valueText,
362 }, 362 },
363 Context.help, 363 Context.help,
364 Context.value, 364 Context.value,
@@ -429,3 +429,154 @@ test "clap.help" {
429 testing.expect(false); 429 testing.expect(false);
430 } 430 }
431} 431}
432
433/// Will print a usage message in the following format:
434/// [-abc] [--longa] [-d <valueText>] [--longb <valueText>] <valueText>
435///
436/// First all none value taking parameters, which have a short name are
437/// printed, then non positional parameters and finally the positinal.
438pub fn usageFull(
439 stream: var,
440 comptime Id: type,
441 params: []const Param(Id),
442 comptime Error: type,
443 context: var,
444 valueText: fn (@typeOf(context), Param(Id)) Error![]const u8,
445) !void {
446 var cs = io.CountingOutStream(@typeOf(stream.*).Error).init(stream);
447 for (params) |param| {
448 const name = param.names.short orelse continue;
449 if (param.takes_value)
450 continue;
451
452 if (cs.bytes_written == 0)
453 try stream.write("[-");
454 try cs.stream.write([_]u8{name});
455 }
456 if (cs.bytes_written != 0)
457 try cs.stream.write("]");
458
459 var positional: ?Param(Id) = null;
460 for (params) |param| {
461 if (!param.takes_value and param.names.short != null)
462 continue;
463
464 const prefix = if (param.names.short) |_| "-" else "--";
465 const name = if (param.names.short) |*s| (*const [1]u8)(s)[0..] else param.names.long orelse {
466 positional = param;
467 continue;
468 };
469 if (cs.bytes_written != 0)
470 try cs.stream.write(" ");
471
472 try cs.stream.print("[{}{}", prefix, name);
473 if (param.takes_value)
474 try cs.stream.print(" <{}>", try valueText(context, param));
475
476 try cs.stream.write("]");
477 }
478
479 if (positional) |p| {
480 if (cs.bytes_written != 0)
481 try cs.stream.write(" ");
482 try cs.stream.print("<{}>", try valueText(context, p));
483 }
484}
485
486/// A wrapper around usageFull for a simple valueText functions that
487/// cant return an error or take a context.
488pub fn usageEx(
489 stream: var,
490 comptime Id: type,
491 params: []const Param(Id),
492 valueText: fn (Param(Id)) []const u8,
493) !void {
494 const Context = struct {
495 valueText: fn (Param(Id)) []const u8,
496
497 pub fn value(c: @This(), p: Param(Id)) error{}![]const u8 {
498 return c.valueText(p);
499 }
500 };
501
502 return usageFull(
503 stream,
504 Id,
505 params,
506 error{},
507 Context{ .valueText = valueText },
508 Context.value,
509 );
510}
511
512/// A wrapper around usageEx that takes a Param(Help).
513pub fn usage(stream: var, params: []const Param(Help)) !void {
514 try usageEx(stream, Help, params, getValueSimple);
515}
516
517fn testUsage(expected: []const u8, params: []const Param(Help)) !void {
518 var buf: [1024]u8 = undefined;
519 var slice_stream = io.SliceOutStream.init(buf[0..]);
520 try usage(&slice_stream.stream, params);
521
522 const actual = slice_stream.getWritten();
523 if (!mem.eql(u8, actual, expected)) {
524 debug.warn("\n============ Expected ============\n");
525 debug.warn("{}\n", expected);
526 debug.warn("============= Actual =============\n");
527 debug.warn("{}\n", actual);
528
529 var buffer: [1024 * 2]u8 = undefined;
530 var fba = std.heap.FixedBufferAllocator.init(&buffer);
531
532 debug.warn("============ Expected (escaped) ============\n");
533 debug.warn("{x}\n", expected);
534 debug.warn("============ Actual (escaped) ============\n");
535 debug.warn("{x}\n", actual);
536 testing.expect(false);
537 }
538}
539
540test "usage" {
541 @setEvalBranchQuota(100000);
542 try testUsage("[-ab]", comptime [_]Param(Help){
543 parseParam("-a") catch unreachable,
544 parseParam("-b") catch unreachable,
545 });
546 try testUsage("[-a <value>] [-b <v>]", comptime [_]Param(Help){
547 parseParam("-a <value>") catch unreachable,
548 parseParam("-b <v>") catch unreachable,
549 });
550 try testUsage("[--a] [--b]", comptime [_]Param(Help){
551 parseParam("--a") catch unreachable,
552 parseParam("--b") catch unreachable,
553 });
554 try testUsage("[--a <value>] [--b <v>]", comptime [_]Param(Help){
555 parseParam("--a <value>") catch unreachable,
556 parseParam("--b <v>") catch unreachable,
557 });
558 try testUsage("<file>", comptime [_]Param(Help){
559 Param(Help){
560 .id = Help{
561 .value = "file",
562 },
563 .takes_value = true,
564 },
565 });
566 try testUsage("[-ab] [-c <value>] [-d <v>] [--e] [--f] [--g <value>] [--h <v>] <file>", comptime [_]Param(Help){
567 parseParam("-a") catch unreachable,
568 parseParam("-b") catch unreachable,
569 parseParam("-c <value>") catch unreachable,
570 parseParam("-d <v>") catch unreachable,
571 parseParam("--e") catch unreachable,
572 parseParam("--f") catch unreachable,
573 parseParam("--g <value>") catch unreachable,
574 parseParam("--h <v>") catch unreachable,
575 Param(Help){
576 .id = Help{
577 .value = "file",
578 },
579 .takes_value = true,
580 },
581 });
582}
diff --git a/example/README.md.template b/example/README.md.template
index 1fd90b0..d1d45e5 100644
--- a/example/README.md.template
+++ b/example/README.md.template
@@ -87,3 +87,26 @@ The `helpEx` is the generic version of `help`. It can print a help message for a
87 87
88The `helpFull` is even more generic, allowing the functions that get the help and value strings 88The `helpFull` is even more generic, allowing the functions that get the help and value strings
89to return errors and take a context as a parameter. 89to return errors and take a context as a parameter.
90
91### `usage`
92
93The `usage`, `usageEx` and `usageFull` are functions for printing a simple list of all parameters the
94program can take.
95
96```zig
97{}
98```
99
100```
101[-hv] [--value <N>]
102```
103
104The `usage` functions are the simplest to call. It only takes an `OutStream` and a slice of
105`Param(Help)`.
106
107The `usageEx` is the generic version of `usage`. It can print a usage message for any
108`Param` give that the caller provides functions for getting the usage and value strings.
109
110The `usageFull` is even more generic, allowing the functions that get the usage and value strings
111to return errors and take a context as a parameter.
112
diff --git a/example/usage.zig b/example/usage.zig
new file mode 100644
index 0000000..734d741
--- /dev/null
+++ b/example/usage.zig
@@ -0,0 +1,20 @@
1const std = @import("std");
2const clap = @import("clap");
3
4pub fn main() !void {
5 const stderr_file = try std.io.getStdErr();
6 var stderr_out_stream = stderr_file.outStream();
7 const stderr = &stderr_out_stream.stream;
8
9 // clap.usage is a function that can print a simple usage message, given a
10 // slice of Param(Help). There is also a usageEx, which can print a
11 // usage message for any Param, but it is more verbose to call.
12 try clap.usage(
13 stderr,
14 comptime [_]clap.Param(clap.Help){
15 clap.parseParam("-h, --help Display this help and exit. ") catch unreachable,
16 clap.parseParam("-v, --version Output version information and exit.") catch unreachable,
17 clap.parseParam(" --value <N> Output version information and exit.") catch unreachable,
18 },
19 );
20}