From cc056cf4232721b62b76428d1ada447eecd98421 Mon Sep 17 00:00:00 2001 From: Jimmi Holst Christensen Date: Thu, 5 Mar 2020 23:24:36 +0100 Subject: Add clap.usage --- README.md | 43 ++++++++++ build.zig | 2 + clap.zig | 193 ++++++++++++++++++++++++++++++++++++++++----- example/README.md.template | 23 ++++++ example/usage.zig | 20 +++++ 5 files changed, 260 insertions(+), 21 deletions(-) create mode 100644 example/usage.zig 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 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 Output version information and exit.") catch unreachable, + }, + ); +} + +``` + +``` +[-hv] [--value ] +``` + +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. + 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 { "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); 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( } /// Will print a help message in the following format: -/// -s, --long help_text -/// -s, help_text -/// -s help_text -/// --long help_text -/// --long help_text +/// -s, --long helpText +/// -s, helpText +/// -s helpText +/// --long helpText +/// --long 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 ] [--longb ] +/// +/// 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 ] [-b ]", comptime [_]Param(Help){ + parseParam("-a ") catch unreachable, + parseParam("-b ") catch unreachable, + }); + try testUsage("[--a] [--b]", comptime [_]Param(Help){ + parseParam("--a") catch unreachable, + parseParam("--b") catch unreachable, + }); + try testUsage("[--a ] [--b ]", comptime [_]Param(Help){ + parseParam("--a ") catch unreachable, + parseParam("--b ") catch unreachable, + }); + try testUsage("", comptime [_]Param(Help){ + Param(Help){ + .id = Help{ + .value = "file", + }, + .takes_value = true, + }, + }); + try testUsage("[-ab] [-c ] [-d ] [--e] [--f] [--g ] [--h ] ", comptime [_]Param(Help){ + parseParam("-a") catch unreachable, + parseParam("-b") catch unreachable, + parseParam("-c ") catch unreachable, + parseParam("-d ") catch unreachable, + parseParam("--e") catch unreachable, + parseParam("--f") catch unreachable, + parseParam("--g ") catch unreachable, + parseParam("--h ") catch unreachable, + Param(Help){ + .id = Help{ + .value = "file", + }, + .takes_value = true, + }, + }); +} 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 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 ] +``` + +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. + 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 @@ +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 Output version information and exit.") catch unreachable, + }, + ); +} -- cgit v1.2.3 From ca4b720d1b99f217559fe52d2be2146514d406a3 Mon Sep 17 00:00:00 2001 From: Jimmi Holst Christensen Date: Thu, 5 Mar 2020 23:28:11 +0100 Subject: Better readme for usage --- README.md | 13 ++----------- example/README.md.template | 13 ++----------- 2 files changed, 4 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 47c028d..12d691d 100644 --- a/README.md +++ b/README.md @@ -235,8 +235,8 @@ 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. +The `usage`, `usageEx` and `usageFull` are functions for printing a small abbreviated version +of the help message. ```zig const std = @import("std"); @@ -266,12 +266,3 @@ pub fn main() !void { [-hv] [--value ] ``` -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. - diff --git a/example/README.md.template b/example/README.md.template index d1d45e5..5e75d5d 100644 --- a/example/README.md.template +++ b/example/README.md.template @@ -90,8 +90,8 @@ 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. +The `usage`, `usageEx` and `usageFull` are functions for printing a small abbreviated version +of the help message. ```zig {} @@ -101,12 +101,3 @@ program can take. [-hv] [--value ] ``` -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. - -- cgit v1.2.3