Add clap.usage

This commit is contained in:
Jimmi Holst Christensen 2020-03-05 23:24:36 +01:00
parent a305e818bd
commit cc056cf423
5 changed files with 260 additions and 21 deletions

View File

@ -232,3 +232,46 @@ The `helpEx` is the generic version of `help`. It can print a help message for a
The `helpFull` is even more generic, allowing the functions that get the help and value strings
to return errors and take a context as a parameter.
### `usage`
The `usage`, `usageEx` and `usageFull` are functions for printing a simple list of all parameters the
program can take.
```zig
const std = @import("std");
const clap = @import("clap");
pub fn main() !void {
const stderr_file = try std.io.getStdErr();
var stderr_out_stream = stderr_file.outStream();
const stderr = &stderr_out_stream.stream;
// clap.usage is a function that can print a simple usage message, given a
// slice of Param(Help). There is also a usageEx, which can print a
// usage message for any Param, but it is more verbose to call.
try clap.usage(
stderr,
comptime [_]clap.Param(clap.Help){
clap.parseParam("-h, --help Display this help and exit. ") catch unreachable,
clap.parseParam("-v, --version Output version information and exit.") catch unreachable,
clap.parseParam(" --value <N> Output version information and exit.") catch unreachable,
},
);
}
```
```
[-hv] [--value <N>]
```
The `usage` functions are the simplest to call. It only takes an `OutStream` and a slice of
`Param(Help)`.
The `usageEx` is the generic version of `usage`. It can print a usage message for any
`Param` give that the caller provides functions for getting the usage and value strings.
The `usageFull` is even more generic, allowing the functions that get the usage and value strings
to return errors and take a context as a parameter.

View File

@ -32,6 +32,7 @@ pub fn build(b: *Builder) void {
"comptime-clap",
"streaming-clap",
"help",
"usage",
}) |example_name| {
const example = b.addExecutable(example_name, "example/" ++ example_name ++ ".zig");
example.addPackagePath("clap", "clap.zig");
@ -67,6 +68,7 @@ fn readMeStep(b: *Builder) *std.build.Step {
@embedFile("example/comptime-clap.zig"),
@embedFile("example/streaming-clap.zig"),
@embedFile("example/help.zig"),
@embedFile("example/usage.zig"),
);
}
}.make);

193
clap.zig
View File

@ -265,25 +265,25 @@ pub fn parse(
}
/// Will print a help message in the following format:
/// -s, --long <value_text> help_text
/// -s, help_text
/// -s <value_text> help_text
/// --long help_text
/// --long <value_text> help_text
/// -s, --long <valueText> helpText
/// -s, helpText
/// -s <valueText> helpText
/// --long helpText
/// --long <valueText> helpText
pub fn helpFull(
stream: var,
comptime Id: type,
params: []const Param(Id),
comptime Error: type,
context: var,
help_text: fn (@typeOf(context), Param(Id)) Error![]const u8,
value_text: fn (@typeOf(context), Param(Id)) Error![]const u8,
helpText: fn (@typeOf(context), Param(Id)) Error![]const u8,
valueText: fn (@typeOf(context), Param(Id)) Error![]const u8,
) !void {
const max_spacing = blk: {
var res: usize = 0;
for (params) |param| {
var counting_stream = io.CountingOutStream(io.NullOutStream.Error).init(io.null_out_stream);
try printParam(&counting_stream.stream, Id, param, Error, context, value_text);
try printParam(&counting_stream.stream, Id, param, Error, context, valueText);
if (res < counting_stream.bytes_written)
res = counting_stream.bytes_written;
}
@ -297,9 +297,9 @@ pub fn helpFull(
var counting_stream = io.CountingOutStream(@typeOf(stream.*).Error).init(stream);
try stream.print("\t");
try printParam(&counting_stream.stream, Id, param, Error, context, value_text);
try printParam(&counting_stream.stream, Id, param, Error, context, valueText);
try stream.writeByteNTimes(' ', max_spacing - counting_stream.bytes_written);
try stream.print("\t{}\n", try help_text(context, param));
try stream.print("\t{}\n", try helpText(context, param));
}
}
@ -309,7 +309,7 @@ fn printParam(
param: Param(Id),
comptime Error: type,
context: var,
value_text: fn (@typeOf(context), Param(Id)) Error![]const u8,
valueText: fn (@typeOf(context), Param(Id)) Error![]const u8,
) @typeOf(stream.*).Error!void {
if (param.names.short) |s| {
try stream.print("-{c}", s);
@ -326,28 +326,28 @@ fn printParam(
try stream.print("--{}", l);
}
if (param.takes_value)
try stream.print(" <{}>", value_text(context, param));
try stream.print(" <{}>", valueText(context, param));
}
/// A wrapper around helpFull for simple help_text and value_text functions that
/// A wrapper around helpFull for simple helpText and valueText functions that
/// cant return an error or take a context.
pub fn helpEx(
stream: var,
comptime Id: type,
params: []const Param(Id),
help_text: fn (Param(Id)) []const u8,
value_text: fn (Param(Id)) []const u8,
helpText: fn (Param(Id)) []const u8,
valueText: fn (Param(Id)) []const u8,
) !void {
const Context = struct {
help_text: fn (Param(Id)) []const u8,
value_text: fn (Param(Id)) []const u8,
helpText: fn (Param(Id)) []const u8,
valueText: fn (Param(Id)) []const u8,
pub fn help(c: @This(), p: Param(Id)) error{}![]const u8 {
return c.help_text(p);
return c.helpText(p);
}
pub fn value(c: @This(), p: Param(Id)) error{}![]const u8 {
return c.value_text(p);
return c.valueText(p);
}
};
@ -357,8 +357,8 @@ pub fn helpEx(
params,
error{},
Context{
.help_text = help_text,
.value_text = value_text,
.helpText = helpText,
.valueText = valueText,
},
Context.help,
Context.value,
@ -429,3 +429,154 @@ test "clap.help" {
testing.expect(false);
}
}
/// Will print a usage message in the following format:
/// [-abc] [--longa] [-d <valueText>] [--longb <valueText>] <valueText>
///
/// First all none value taking parameters, which have a short name are
/// printed, then non positional parameters and finally the positinal.
pub fn usageFull(
stream: var,
comptime Id: type,
params: []const Param(Id),
comptime Error: type,
context: var,
valueText: fn (@typeOf(context), Param(Id)) Error![]const u8,
) !void {
var cs = io.CountingOutStream(@typeOf(stream.*).Error).init(stream);
for (params) |param| {
const name = param.names.short orelse continue;
if (param.takes_value)
continue;
if (cs.bytes_written == 0)
try stream.write("[-");
try cs.stream.write([_]u8{name});
}
if (cs.bytes_written != 0)
try cs.stream.write("]");
var positional: ?Param(Id) = null;
for (params) |param| {
if (!param.takes_value and param.names.short != null)
continue;
const prefix = if (param.names.short) |_| "-" else "--";
const name = if (param.names.short) |*s| (*const [1]u8)(s)[0..] else param.names.long orelse {
positional = param;
continue;
};
if (cs.bytes_written != 0)
try cs.stream.write(" ");
try cs.stream.print("[{}{}", prefix, name);
if (param.takes_value)
try cs.stream.print(" <{}>", try valueText(context, param));
try cs.stream.write("]");
}
if (positional) |p| {
if (cs.bytes_written != 0)
try cs.stream.write(" ");
try cs.stream.print("<{}>", try valueText(context, p));
}
}
/// A wrapper around usageFull for a simple valueText functions that
/// cant return an error or take a context.
pub fn usageEx(
stream: var,
comptime Id: type,
params: []const Param(Id),
valueText: fn (Param(Id)) []const u8,
) !void {
const Context = struct {
valueText: fn (Param(Id)) []const u8,
pub fn value(c: @This(), p: Param(Id)) error{}![]const u8 {
return c.valueText(p);
}
};
return usageFull(
stream,
Id,
params,
error{},
Context{ .valueText = valueText },
Context.value,
);
}
/// A wrapper around usageEx that takes a Param(Help).
pub fn usage(stream: var, params: []const Param(Help)) !void {
try usageEx(stream, Help, params, getValueSimple);
}
fn testUsage(expected: []const u8, params: []const Param(Help)) !void {
var buf: [1024]u8 = undefined;
var slice_stream = io.SliceOutStream.init(buf[0..]);
try usage(&slice_stream.stream, params);
const actual = slice_stream.getWritten();
if (!mem.eql(u8, actual, expected)) {
debug.warn("\n============ Expected ============\n");
debug.warn("{}\n", expected);
debug.warn("============= Actual =============\n");
debug.warn("{}\n", actual);
var buffer: [1024 * 2]u8 = undefined;
var fba = std.heap.FixedBufferAllocator.init(&buffer);
debug.warn("============ Expected (escaped) ============\n");
debug.warn("{x}\n", expected);
debug.warn("============ Actual (escaped) ============\n");
debug.warn("{x}\n", actual);
testing.expect(false);
}
}
test "usage" {
@setEvalBranchQuota(100000);
try testUsage("[-ab]", comptime [_]Param(Help){
parseParam("-a") catch unreachable,
parseParam("-b") catch unreachable,
});
try testUsage("[-a <value>] [-b <v>]", comptime [_]Param(Help){
parseParam("-a <value>") catch unreachable,
parseParam("-b <v>") catch unreachable,
});
try testUsage("[--a] [--b]", comptime [_]Param(Help){
parseParam("--a") catch unreachable,
parseParam("--b") catch unreachable,
});
try testUsage("[--a <value>] [--b <v>]", comptime [_]Param(Help){
parseParam("--a <value>") catch unreachable,
parseParam("--b <v>") catch unreachable,
});
try testUsage("<file>", comptime [_]Param(Help){
Param(Help){
.id = Help{
.value = "file",
},
.takes_value = true,
},
});
try testUsage("[-ab] [-c <value>] [-d <v>] [--e] [--f] [--g <value>] [--h <v>] <file>", comptime [_]Param(Help){
parseParam("-a") catch unreachable,
parseParam("-b") catch unreachable,
parseParam("-c <value>") catch unreachable,
parseParam("-d <v>") catch unreachable,
parseParam("--e") catch unreachable,
parseParam("--f") catch unreachable,
parseParam("--g <value>") catch unreachable,
parseParam("--h <v>") catch unreachable,
Param(Help){
.id = Help{
.value = "file",
},
.takes_value = true,
},
});
}

View File

@ -87,3 +87,26 @@ The `helpEx` is the generic version of `help`. It can print a help message for a
The `helpFull` is even more generic, allowing the functions that get the help and value strings
to return errors and take a context as a parameter.
### `usage`
The `usage`, `usageEx` and `usageFull` are functions for printing a simple list of all parameters the
program can take.
```zig
{}
```
```
[-hv] [--value <N>]
```
The `usage` functions are the simplest to call. It only takes an `OutStream` and a slice of
`Param(Help)`.
The `usageEx` is the generic version of `usage`. It can print a usage message for any
`Param` give that the caller provides functions for getting the usage and value strings.
The `usageFull` is even more generic, allowing the functions that get the usage and value strings
to return errors and take a context as a parameter.

20
example/usage.zig Normal file
View File

@ -0,0 +1,20 @@
const std = @import("std");
const clap = @import("clap");
pub fn main() !void {
const stderr_file = try std.io.getStdErr();
var stderr_out_stream = stderr_file.outStream();
const stderr = &stderr_out_stream.stream;
// clap.usage is a function that can print a simple usage message, given a
// slice of Param(Help). There is also a usageEx, which can print a
// usage message for any Param, but it is more verbose to call.
try clap.usage(
stderr,
comptime [_]clap.Param(clap.Help){
clap.parseParam("-h, --help Display this help and exit. ") catch unreachable,
clap.parseParam("-v, --version Output version information and exit.") catch unreachable,
clap.parseParam(" --value <N> Output version information and exit.") catch unreachable,
},
);
}