Skip to content

Commit 0738c31

Browse files
Copilotjiacai2050
andauthored
simargs: print help to stderr on parse error (#74)
* Initial plan * simargs: print help to stderr on parse error Co-authored-by: jiacai2050 <3848910+jiacai2050@users.noreply.github.com> Agent-Logs-Url: https://github.com/jiacai2050/zigcli/sessions/3c3cd09b-5dcb-4efc-815d-94bc70dbb852 --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: jiacai2050 <3848910+jiacai2050@users.noreply.github.com>
1 parent 49beee1 commit 0738c31

1 file changed

Lines changed: 42 additions & 2 deletions

File tree

src/mod/simargs.zig

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ pub const ParseOptions = struct {
2828
argument_prompt: ?[]const u8 = null,
2929
version_string: ?[]const u8 = null,
3030
print_help_and_exit: bool = true,
31+
/// When true, print the help text to stderr before returning any parse error.
32+
print_help_on_error: bool = true,
3133
};
3234

3335
/// Parses arguments according to the given structure.
@@ -784,14 +786,22 @@ fn OptionParser(
784786
options.version_string,
785787
options.argument_prompt,
786788
);
787-
const parsed_options = try Self.parseCommand(
789+
const parsed_options = Self.parseCommand(
788790
Options,
789791
arguments_to_parse,
790792
&argument_index,
791793
message_helper,
792794
null,
793795
options,
794-
);
796+
) catch |err| {
797+
if (!is_test and options.print_help_on_error) {
798+
var stderr_buffer: [4096]u8 = undefined;
799+
var stderr_writer = std.fs.File.stderr().writer(&stderr_buffer);
800+
message_helper.printHelp(Options, null, &stderr_writer.interface) catch {};
801+
stderr_writer.interface.flush() catch {};
802+
}
803+
return err;
804+
};
795805
var result = ParseResult(Options, options.version_string, options.argument_prompt){
796806
.program_name = input_arguments[0],
797807
.allocator = self.allocator,
@@ -1182,6 +1192,36 @@ test "parse/print_help_and_exit false" {
11821192
try std.testing.expectEqual(result.positional_arguments.len, 0);
11831193
}
11841194

1195+
test "parse/print_help_on_error" {
1196+
// Verify that parse errors are propagated correctly regardless of print_help_on_error.
1197+
// The actual stderr printing is guarded by !is_test, so only the error return is tested here.
1198+
const gpa = std.testing.allocator;
1199+
var input_arguments = [_][:0]u8{
1200+
try gpa.dupeZ(u8, "awesome-cli"),
1201+
try gpa.dupeZ(u8, "--help"),
1202+
};
1203+
defer for (input_arguments) |argument| {
1204+
gpa.free(argument);
1205+
};
1206+
1207+
// With print_help_on_error: true (default), errors are still propagated.
1208+
{
1209+
var parser = OptionParser(TestArguments).init(gpa);
1210+
try std.testing.expectError(
1211+
error.MissingRequiredOption,
1212+
parser.parse(&input_arguments, .{ .print_help_on_error = true }),
1213+
);
1214+
}
1215+
// With print_help_on_error: false, errors are also propagated (no other change in test mode).
1216+
{
1217+
var parser = OptionParser(TestArguments).init(gpa);
1218+
try std.testing.expectError(
1219+
error.MissingRequiredOption,
1220+
parser.parse(&input_arguments, .{ .print_help_on_error = false }),
1221+
);
1222+
}
1223+
}
1224+
11851225
test "parse/sub commands" {
11861226
const gpa = std.testing.allocator;
11871227
var input_arguments = [_][:0]u8{

0 commit comments

Comments
 (0)