summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/main.yml6
-rw-r--r--README.md36
-rw-r--r--build.zig2
-rw-r--r--clap.zig226
-rw-r--r--example/README.md.template18
-rw-r--r--example/usage.zig18
6 files changed, 270 insertions, 36 deletions
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 93d00fc..7418c8c 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -4,7 +4,7 @@ jobs:
4 test: 4 test:
5 strategy: 5 strategy:
6 matrix: 6 matrix:
7 os: [ubuntu-latest, macos-latest, windows-latest] 7 os: [ubuntu-latest, macos-latest]
8 runs-on: ${{matrix.os}} 8 runs-on: ${{matrix.os}}
9 steps: 9 steps:
10 - uses: actions/checkout@v1 10 - uses: actions/checkout@v1
@@ -12,7 +12,7 @@ jobs:
12 submodules: recursive 12 submodules: recursive
13 - uses: goto-bus-stop/setup-zig@v1.0.0 13 - uses: goto-bus-stop/setup-zig@v1.0.0
14 with: 14 with:
15 version: 0.5.0 15 version: 0.6.0
16 - run: zig build 16 - run: zig build
17 lint: 17 lint:
18 runs-on: ubuntu-latest 18 runs-on: ubuntu-latest
@@ -20,5 +20,5 @@ jobs:
20 - uses: actions/checkout@v1 20 - uses: actions/checkout@v1
21 - uses: goto-bus-stop/setup-zig@v1.0.0 21 - uses: goto-bus-stop/setup-zig@v1.0.0
22 with: 22 with:
23 version: 0.5.0 23 version: 0.6.0
24 - run: zig fmt --check . 24 - run: zig fmt --check .
diff --git a/README.md b/README.md
index 642856b..2b38281 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,10 @@
2 2
3A simple and easy to use command line argument parser library for Zig. 3A simple and easy to use command line argument parser library for Zig.
4 4
5Looking for a version that works with `zig master`? The `zig-master` branch has
6you covered. It is maintained by people who live at head (not me) and is merged
7into master on every `zig` release.
8
5## Features 9## Features
6 10
7* Short arguments `-a` 11* Short arguments `-a`
@@ -231,3 +235,35 @@ The `helpEx` is the generic version of `help`. It can print a help message for a
231 235
232The `helpFull` is even more generic, allowing the functions that get the help and value strings 236The `helpFull` is even more generic, allowing the functions that get the help and value strings
233to return errors and take a context as a parameter. 237to return errors and take a context as a parameter.
238
239### `usage`
240
241The `usage`, `usageEx` and `usageFull` are functions for printing a small abbreviated version
242of the help message.
243
244```zig
245const std = @import("std");
246const clap = @import("clap");
247
248pub fn main() !void {
249 const stderr = std.io.getStdErr().outStream();
250
251 // clap.usage is a function that can print a simple usage message, given a
252 // slice of Param(Help). There is also a usageEx, which can print a
253 // usage message for any Param, but it is more verbose to call.
254 try clap.usage(
255 stderr,
256 comptime &[_]clap.Param(clap.Help){
257 clap.parseParam("-h, --help Display this help and exit. ") catch unreachable,
258 clap.parseParam("-v, --version Output version information and exit.") catch unreachable,
259 clap.parseParam(" --value <N> Output version information and exit.") catch unreachable,
260 },
261 );
262}
263
264```
265
266```
267[-hv] [--value <N>]
268```
269
diff --git a/build.zig b/build.zig
index 6adfbf4..f468fb6 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");
@@ -66,6 +67,7 @@ fn readMeStep(b: *Builder) *std.build.Step {
66 @embedFile("example/comptime-clap.zig"), 67 @embedFile("example/comptime-clap.zig"),
67 @embedFile("example/streaming-clap.zig"), 68 @embedFile("example/streaming-clap.zig"),
68 @embedFile("example/help.zig"), 69 @embedFile("example/help.zig"),
70 @embedFile("example/usage.zig"),
69 }); 71 });
70 } 72 }
71 }.make); 73 }.make);
diff --git a/clap.zig b/clap.zig
index 6ef00a4..ff423cc 100644
--- a/clap.zig
+++ b/clap.zig
@@ -57,10 +57,14 @@ pub fn Param(comptime Id: type) type {
57/// Takes a string and parses it to a Param(Help). 57/// Takes a string and parses it to a Param(Help).
58/// This is the reverse of 'help' but for at single parameter only. 58/// This is the reverse of 'help' but for at single parameter only.
59pub fn parseParam(line: []const u8) !Param(Help) { 59pub fn parseParam(line: []const u8) !Param(Help) {
60 var z: usize = 0;
60 var res = Param(Help){ 61 var res = Param(Help){
61 .id = Help{ 62 .id = Help{
62 .msg = line[0..0], 63 // For testing, i want to be able to easily compare slices just by pointer,
63 .value = line[0..0], 64 // so I slice by a runtime value here, so that zig does not optimize this
65 // out. Maybe I should write the test better, geeh.
66 .msg = line[z..z],
67 .value = line[z..z],
64 }, 68 },
65 }; 69 };
66 70
@@ -119,6 +123,7 @@ pub fn parseParam(line: []const u8) !Param(Help) {
119} 123}
120 124
121test "parseParam" { 125test "parseParam" {
126 var z: usize = 0;
122 var text: []const u8 = "-s, --long <value> Help text"; 127 var text: []const u8 = "-s, --long <value> Help text";
123 testing.expectEqual(Param(Help){ 128 testing.expectEqual(Param(Help){
124 .id = Help{ 129 .id = Help{
@@ -162,7 +167,7 @@ test "parseParam" {
162 testing.expectEqual(Param(Help){ 167 testing.expectEqual(Param(Help){
163 .id = Help{ 168 .id = Help{
164 .msg = find(text, "Help text"), 169 .msg = find(text, "Help text"),
165 .value = text[0..0], 170 .value = text[z..z],
166 }, 171 },
167 .names = Names{ 172 .names = Names{
168 .short = 's', 173 .short = 's',
@@ -175,7 +180,7 @@ test "parseParam" {
175 testing.expectEqual(Param(Help){ 180 testing.expectEqual(Param(Help){
176 .id = Help{ 181 .id = Help{
177 .msg = find(text, "Help text"), 182 .msg = find(text, "Help text"),
178 .value = text[0..0], 183 .value = text[z..z],
179 }, 184 },
180 .names = Names{ 185 .names = Names{
181 .short = 's', 186 .short = 's',
@@ -188,7 +193,7 @@ test "parseParam" {
188 testing.expectEqual(Param(Help){ 193 testing.expectEqual(Param(Help){
189 .id = Help{ 194 .id = Help{
190 .msg = find(text, "Help text"), 195 .msg = find(text, "Help text"),
191 .value = text[0..0], 196 .value = text[z..z],
192 }, 197 },
193 .names = Names{ 198 .names = Names{
194 .short = null, 199 .short = null,
@@ -265,25 +270,25 @@ pub fn parse(
265} 270}
266 271
267/// Will print a help message in the following format: 272/// Will print a help message in the following format:
268/// -s, --long <value_text> help_text 273/// -s, --long <valueText> helpText
269/// -s, help_text 274/// -s, helpText
270/// -s <value_text> help_text 275/// -s <valueText> helpText
271/// --long help_text 276/// --long helpText
272/// --long <value_text> help_text 277/// --long <valueText> helpText
273pub fn helpFull( 278pub fn helpFull(
274 stream: var, 279 stream: var,
275 comptime Id: type, 280 comptime Id: type,
276 params: []const Param(Id), 281 params: []const Param(Id),
277 comptime Error: type, 282 comptime Error: type,
278 context: var, 283 context: var,
279 help_text: fn (@TypeOf(context), Param(Id)) Error![]const u8, 284 helpText: fn (@TypeOf(context), Param(Id)) Error![]const u8,
280 value_text: fn (@TypeOf(context), Param(Id)) Error![]const u8, 285 valueText: fn (@TypeOf(context), Param(Id)) Error![]const u8,
281) !void { 286) !void {
282 const max_spacing = blk: { 287 const max_spacing = blk: {
283 var res: usize = 0; 288 var res: usize = 0;
284 for (params) |param| { 289 for (params) |param| {
285 var counting_stream = io.countingOutStream(io.null_out_stream); 290 var counting_stream = io.countingOutStream(io.null_out_stream);
286 try printParam(&counting_stream.outStream(), Id, param, Error, context, value_text); 291 try printParam(counting_stream.outStream(), Id, param, Error, context, valueText);
287 if (res < counting_stream.bytes_written) 292 if (res < counting_stream.bytes_written)
288 res = counting_stream.bytes_written; 293 res = counting_stream.bytes_written;
289 } 294 }
@@ -297,9 +302,9 @@ pub fn helpFull(
297 302
298 var counting_stream = io.countingOutStream(stream); 303 var counting_stream = io.countingOutStream(stream);
299 try stream.print("\t", .{}); 304 try stream.print("\t", .{});
300 try printParam(&counting_stream.outStream(), Id, param, Error, context, value_text); 305 try printParam(counting_stream.outStream(), Id, param, Error, context, valueText);
301 try stream.writeByteNTimes(' ', max_spacing - counting_stream.bytes_written); 306 try stream.writeByteNTimes(' ', max_spacing - counting_stream.bytes_written);
302 try stream.print("\t{}\n", .{ try help_text(context, param) }); 307 try stream.print("\t{}\n", .{try helpText(context, param)});
303 } 308 }
304} 309}
305 310
@@ -309,10 +314,10 @@ fn printParam(
309 param: Param(Id), 314 param: Param(Id),
310 comptime Error: type, 315 comptime Error: type,
311 context: var, 316 context: var,
312 value_text: fn (@TypeOf(context), Param(Id)) Error![]const u8, 317 valueText: fn (@TypeOf(context), Param(Id)) Error![]const u8,
313) @TypeOf(stream.*).Error!void { 318) !void {
314 if (param.names.short) |s| { 319 if (param.names.short) |s| {
315 try stream.print("-{c}", .{ s }); 320 try stream.print("-{c}", .{s});
316 } else { 321 } else {
317 try stream.print(" ", .{}); 322 try stream.print(" ", .{});
318 } 323 }
@@ -323,31 +328,31 @@ fn printParam(
323 try stream.print(" ", .{}); 328 try stream.print(" ", .{});
324 } 329 }
325 330
326 try stream.print("--{}", .{ l }); 331 try stream.print("--{}", .{l});
327 } 332 }
328 if (param.takes_value) 333 if (param.takes_value)
329 try stream.print(" <{}>", .{ value_text(context, param) }); 334 try stream.print(" <{}>", .{valueText(context, param)});
330} 335}
331 336
332/// A wrapper around helpFull for simple help_text and value_text functions that 337/// A wrapper around helpFull for simple helpText and valueText functions that
333/// cant return an error or take a context. 338/// cant return an error or take a context.
334pub fn helpEx( 339pub fn helpEx(
335 stream: var, 340 stream: var,
336 comptime Id: type, 341 comptime Id: type,
337 params: []const Param(Id), 342 params: []const Param(Id),
338 help_text: fn (Param(Id)) []const u8, 343 helpText: fn (Param(Id)) []const u8,
339 value_text: fn (Param(Id)) []const u8, 344 valueText: fn (Param(Id)) []const u8,
340) !void { 345) !void {
341 const Context = struct { 346 const Context = struct {
342 help_text: fn (Param(Id)) []const u8, 347 helpText: fn (Param(Id)) []const u8,
343 value_text: fn (Param(Id)) []const u8, 348 valueText: fn (Param(Id)) []const u8,
344 349
345 pub fn help(c: @This(), p: Param(Id)) error{}![]const u8 { 350 pub fn help(c: @This(), p: Param(Id)) error{}![]const u8 {
346 return c.help_text(p); 351 return c.helpText(p);
347 } 352 }
348 353
349 pub fn value(c: @This(), p: Param(Id)) error{}![]const u8 { 354 pub fn value(c: @This(), p: Param(Id)) error{}![]const u8 {
350 return c.value_text(p); 355 return c.valueText(p);
351 } 356 }
352 }; 357 };
353 358
@@ -357,8 +362,8 @@ pub fn helpEx(
357 params, 362 params,
358 error{}, 363 error{},
359 Context{ 364 Context{
360 .help_text = help_text, 365 .helpText = helpText,
361 .value_text = value_text, 366 .valueText = valueText,
362 }, 367 },
363 Context.help, 368 Context.help,
364 Context.value, 369 Context.value,
@@ -417,17 +422,172 @@ test "clap.help" {
417 const actual = slice_stream.getWritten(); 422 const actual = slice_stream.getWritten();
418 if (!mem.eql(u8, actual, expected)) { 423 if (!mem.eql(u8, actual, expected)) {
419 debug.warn("\n============ Expected ============\n", .{}); 424 debug.warn("\n============ Expected ============\n", .{});
420 debug.warn("{}", .{ expected }); 425 debug.warn("{}", .{expected});
426 debug.warn("============= Actual =============\n", .{});
427 debug.warn("{}", .{actual});
428
429 var buffer: [1024 * 2]u8 = undefined;
430 var fba = std.heap.FixedBufferAllocator.init(&buffer);
431
432 debug.warn("============ Expected (escaped) ============\n", .{});
433 debug.warn("{x}\n", .{expected});
434 debug.warn("============ Actual (escaped) ============\n", .{});
435 debug.warn("{x}\n", .{actual});
436 testing.expect(false);
437 }
438}
439
440/// Will print a usage message in the following format:
441/// [-abc] [--longa] [-d <valueText>] [--longb <valueText>] <valueText>
442///
443/// First all none value taking parameters, which have a short name are
444/// printed, then non positional parameters and finally the positinal.
445pub fn usageFull(
446 stream: var,
447 comptime Id: type,
448 params: []const Param(Id),
449 comptime Error: type,
450 context: var,
451 valueText: fn (@TypeOf(context), Param(Id)) Error![]const u8,
452) !void {
453 var cos = io.countingOutStream(stream);
454 const cs = cos.outStream();
455 for (params) |param| {
456 const name = param.names.short orelse continue;
457 if (param.takes_value)
458 continue;
459
460 if (cos.bytes_written == 0)
461 try stream.writeAll("[-");
462 try cs.writeByte(name);
463 }
464 if (cos.bytes_written != 0)
465 try cs.writeByte(']');
466
467 var positional: ?Param(Id) = null;
468 for (params) |param| {
469 if (!param.takes_value and param.names.short != null)
470 continue;
471
472 const prefix = if (param.names.short) |_| "-" else "--";
473
474 // Seems the zig compiler is being a little wierd. I doesn't allow me to write
475 // @as(*const [1]u8, s) VVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
476 const name = if (param.names.short) |*s| @ptrCast([*]const u8, s)[0..1] else param.names.long orelse {
477 positional = param;
478 continue;
479 };
480 if (cos.bytes_written != 0)
481 try cs.writeByte(' ');
482
483 try cs.print("[{}{}", .{ prefix, name });
484 if (param.takes_value)
485 try cs.print(" <{}>", .{try valueText(context, param)});
486
487 try cs.writeByte(']');
488 }
489
490 if (positional) |p| {
491 if (cos.bytes_written != 0)
492 try cs.writeByte(' ');
493 try cs.print("<{}>", .{try valueText(context, p)});
494 }
495}
496
497/// A wrapper around usageFull for a simple valueText functions that
498/// cant return an error or take a context.
499pub fn usageEx(
500 stream: var,
501 comptime Id: type,
502 params: []const Param(Id),
503 valueText: fn (Param(Id)) []const u8,
504) !void {
505 const Context = struct {
506 valueText: fn (Param(Id)) []const u8,
507
508 pub fn value(c: @This(), p: Param(Id)) error{}![]const u8 {
509 return c.valueText(p);
510 }
511 };
512
513 return usageFull(
514 stream,
515 Id,
516 params,
517 error{},
518 Context{ .valueText = valueText },
519 Context.value,
520 );
521}
522
523/// A wrapper around usageEx that takes a Param(Help).
524pub fn usage(stream: var, params: []const Param(Help)) !void {
525 try usageEx(stream, Help, params, getValueSimple);
526}
527
528fn testUsage(expected: []const u8, params: []const Param(Help)) !void {
529 var buf: [1024]u8 = undefined;
530 var fbs = io.fixedBufferStream(&buf);
531 try usage(fbs.outStream(), params);
532
533 const actual = fbs.getWritten();
534 if (!mem.eql(u8, actual, expected)) {
535 debug.warn("\n============ Expected ============\n", .{});
536 debug.warn("{}\n", .{expected});
421 debug.warn("============= Actual =============\n", .{}); 537 debug.warn("============= Actual =============\n", .{});
422 debug.warn("{}", .{ actual }); 538 debug.warn("{}\n", .{actual});
423 539
424 var buffer: [1024 * 2]u8 = undefined; 540 var buffer: [1024 * 2]u8 = undefined;
425 var fba = std.heap.FixedBufferAllocator.init(&buffer); 541 var fba = std.heap.FixedBufferAllocator.init(&buffer);
426 542
427 debug.warn("============ Expected (escaped) ============\n", .{}); 543 debug.warn("============ Expected (escaped) ============\n", .{});
428 debug.warn("{x}\n", .{ expected }); 544 debug.warn("{x}\n", .{expected});
429 debug.warn("============ Actual (escaped) ============\n", .{}); 545 debug.warn("============ Actual (escaped) ============\n", .{});
430 debug.warn("{x}\n", .{ actual }); 546 debug.warn("{x}\n", .{actual});
431 testing.expect(false); 547 testing.expect(false);
432 } 548 }
433} 549}
550
551test "usage" {
552 @setEvalBranchQuota(100000);
553 try testUsage("[-ab]", comptime &[_]Param(Help){
554 parseParam("-a") catch unreachable,
555 parseParam("-b") catch unreachable,
556 });
557 try testUsage("[-a <value>] [-b <v>]", comptime &[_]Param(Help){
558 parseParam("-a <value>") catch unreachable,
559 parseParam("-b <v>") catch unreachable,
560 });
561 try testUsage("[--a] [--b]", comptime &[_]Param(Help){
562 parseParam("--a") catch unreachable,
563 parseParam("--b") catch unreachable,
564 });
565 try testUsage("[--a <value>] [--b <v>]", comptime &[_]Param(Help){
566 parseParam("--a <value>") catch unreachable,
567 parseParam("--b <v>") catch unreachable,
568 });
569 try testUsage("<file>", comptime &[_]Param(Help){
570 Param(Help){
571 .id = Help{
572 .value = "file",
573 },
574 .takes_value = true,
575 },
576 });
577 try testUsage("[-ab] [-c <value>] [-d <v>] [--e] [--f] [--g <value>] [--h <v>] <file>", comptime &[_]Param(Help){
578 parseParam("-a") catch unreachable,
579 parseParam("-b") catch unreachable,
580 parseParam("-c <value>") catch unreachable,
581 parseParam("-d <v>") catch unreachable,
582 parseParam("--e") catch unreachable,
583 parseParam("--f") catch unreachable,
584 parseParam("--g <value>") catch unreachable,
585 parseParam("--h <v>") catch unreachable,
586 Param(Help){
587 .id = Help{
588 .value = "file",
589 },
590 .takes_value = true,
591 },
592 });
593}
diff --git a/example/README.md.template b/example/README.md.template
index 1fd90b0..2afbe86 100644
--- a/example/README.md.template
+++ b/example/README.md.template
@@ -2,6 +2,10 @@
2 2
3A simple and easy to use command line argument parser library for Zig. 3A simple and easy to use command line argument parser library for Zig.
4 4
5Looking for a version that works with `zig master`? The `zig-master` branch has
6you covered. It is maintained by people who live at head (not me) and is merged
7into master on every `zig` release.
8
5## Features 9## Features
6 10
7* Short arguments `-a` 11* Short arguments `-a`
@@ -87,3 +91,17 @@ The `helpEx` is the generic version of `help`. It can print a help message for a
87 91
88The `helpFull` is even more generic, allowing the functions that get the help and value strings 92The `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. 93to return errors and take a context as a parameter.
94
95### `usage`
96
97The `usage`, `usageEx` and `usageFull` are functions for printing a small abbreviated version
98of the help message.
99
100```zig
101{}
102```
103
104```
105[-hv] [--value <N>]
106```
107
diff --git a/example/usage.zig b/example/usage.zig
new file mode 100644
index 0000000..25e1a34
--- /dev/null
+++ b/example/usage.zig
@@ -0,0 +1,18 @@
1const std = @import("std");
2const clap = @import("clap");
3
4pub fn main() !void {
5 const stderr = std.io.getStdErr().outStream();
6
7 // clap.usage is a function that can print a simple usage message, given a
8 // slice of Param(Help). There is also a usageEx, which can print a
9 // usage message for any Param, but it is more verbose to call.
10 try clap.usage(
11 stderr,
12 comptime &[_]clap.Param(clap.Help){
13 clap.parseParam("-h, --help Display this help and exit. ") catch unreachable,
14 clap.parseParam("-v, --version Output version information and exit.") catch unreachable,
15 clap.parseParam(" --value <N> Output version information and exit.") catch unreachable,
16 },
17 );
18}