Add support for wrapping help output

If `wrap_len` is provided, wrap lines over `wrap_len` characters,
retaining indentation.

Fix test runner - tests were not running with `zig build test`.
This commit is contained in:
Bob Farrell 2024-03-19 00:09:35 +00:00 committed by Felix Queißner
parent 062303e29f
commit 01d72b9a01
2 changed files with 78 additions and 4 deletions

View file

@ -1013,8 +1013,35 @@ pub fn printHelp(comptime Generic: type, name: []const u8, writer: anytype) !voi
if (!foundShorthand)
try writer.print(" ", .{});
}
const fmtString = std.fmt.comptimePrint("--{{s: <{}}} {{s}}\n", .{maxOptionLength});
try writer.print(fmtString, .{ field.name, @field(Generic.meta.option_docs, field.name) });
if (@hasDecl(Generic, "wrap_len")) {
var it = std.mem.split(u8, @field(Generic.meta.option_docs, field.name), " ");
const threshold = Generic.wrap_len;
var line_len: usize = 0;
var newline = false;
var first = true;
while (it.next()) |word| {
if (first) {
const fmtString = std.fmt.comptimePrint("--{{s: <{}}} {{s}}", .{maxOptionLength});
try writer.print(fmtString, .{ field.name, word });
first = false;
} else if (newline) {
const fmtString = std.fmt.comptimePrint("\n{{s: <{}}} {{s}}", .{maxOptionLength + 10});
try writer.print(fmtString, .{ " ", word });
newline = false;
} else {
try writer.print(" {s}", .{word});
}
line_len += word.len;
if (line_len >= threshold) {
newline = true;
line_len = 0;
}
}
try writer.writeByte('\n');
} else {
const fmtString = std.fmt.comptimePrint("--{{s: <{}}} {{s}}\n", .{maxOptionLength});
try writer.print(fmtString, .{ field.name, @field(Generic.meta.option_docs, field.name) });
}
}
}
}
@ -1095,3 +1122,48 @@ test "help with no usage summary" {
try std.testing.expectEqualStrings(expected, test_buffer.items);
}
test "help with wrapping" {
const Options = struct {
boolflag: bool = false,
stringflag: []const u8 = "hello",
pub const shorthands = .{
.b = "boolflag",
};
pub const wrap_len = 10;
pub const meta = .{
.full_text = "testing tool",
.option_docs = .{
.boolflag = "a boolean flag with a pretty long description about booleans",
.stringflag = "a string flag with another long description about strings",
},
};
};
var test_buffer = std.ArrayList(u8).init(std.testing.allocator);
defer test_buffer.deinit();
try printHelp(Options, "test", test_buffer.writer());
const expected =
\\Usage: test
\\
\\testing tool
\\
\\Options:
\\ -b, --boolflag a boolean flag
\\ with a pretty
\\ long description
\\ about booleans
\\ --stringflag a string flag
\\ with another
\\ long description
\\ about strings
\\
;
try std.testing.expectEqualStrings(expected, test_buffer.items);
}

View file

@ -8,12 +8,14 @@ pub fn build(b: *std.Build) void {
.root_source_file = .{ .path = "args.zig" },
});
const test_runner = b.addTest(.{
const main_tests = b.addTest(.{
.root_source_file = .{ .path = "args.zig" },
.optimize = optimize,
.target = target,
});
const run_main_tests = b.addRunArtifact(main_tests);
// Standard demo
const demo_exe = b.addExecutable(.{
@ -57,7 +59,7 @@ pub fn build(b: *std.Build) void {
});
const test_step = b.step("test", "Runs the test suite.");
test_step.dependOn(&test_runner.step);
test_step.dependOn(&run_main_tests.step);
test_step.dependOn(&run_demo.step);
test_step.dependOn(&run_demo_verb_1.step);
test_step.dependOn(&run_demo_verb_2.step);