diff --git a/src/Config.zig b/src/Config.zig index 7d476be8..fdef5a40 100644 --- a/src/Config.zig +++ b/src/Config.zig @@ -201,7 +201,7 @@ const Commands = cli.Builder(.{ .shared_options = CommonOptions, }, .{ .name = "version", .options = .{} }, - .{ .name = "help", .options = .{} }, + .{ .name = "help", .positional = .{ .name = "subcommand", .type = ?[]const u8 }, .options = .{} }, }); pub const RunMode = Commands.Enum; @@ -623,11 +623,7 @@ pub fn printUsageAndExit(self: *const Config, success: bool) void { ; // MAX_HELP_LEN| - const usage = - \\usage: {0s} command [options] [URL] - \\ - \\Command can be either 'fetch', 'serve', 'mcp' or 'help' - \\ + const fetch_options = \\fetch command \\Fetches the specified URL \\Example: {0s} fetch --dump html https://lightpanda.io/ @@ -686,11 +682,12 @@ pub fn printUsageAndExit(self: *const Config, success: bool) void { \\ Defaults to no cookie loading. \\ \\--cookie-jar Path to a JSON file to save cookies to on exit (write-only). - \\ Available for fetch and mcp commands. \\ Defaults to no cookie saving. \\ - ++ common_options ++ - \\ + ++ common_options; + + // MAX_HELP_LEN| + const serve_options = \\serve command \\Starts a websocket CDP server \\Example: {0s} serve --host 127.0.0.1 --port 9222 @@ -718,20 +715,37 @@ pub fn printUsageAndExit(self: *const Config, success: bool) void { \\--cookie Path to a JSON file to load cookies from (read-only). \\ Defaults to no cookie loading. \\ - ++ common_options ++ - \\ + ++ common_options; + + // MAX_HELP_LEN| + const mcp_options = \\mcp command \\Starts an MCP (Model Context Protocol) server over stdio \\Example: {0s} mcp \\ + \\Options: \\--cookie Path to a JSON file to load cookies from (read-only). \\ Defaults to no cookie loading. \\ \\--cookie-jar Path to a JSON file to save cookies to on exit (write-only). - \\ Available for fetch and mcp commands. \\ Defaults to no cookie saving. \\ - ++ common_options ++ + ++ common_options; + + // MAX_HELP_LEN| + const usage = + \\usage: {0s} command [options] [URL] + \\ + \\Command can be either 'fetch', 'serve', 'mcp' or 'help' + \\ + ++ fetch_options ++ + \\ + \\ + ++ serve_options ++ + \\ + \\ + ++ mcp_options ++ + \\ \\ \\version command \\Displays the version of {0s} @@ -740,6 +754,26 @@ pub fn printUsageAndExit(self: *const Config, success: bool) void { \\Displays this message \\ ; + + // When called with a subcommand argument, + // print only the relevant subcommand section instead of the full help. + switch (self.mode) { + .help => |h| if (h.subcommand) |sub| { + if (std.mem.eql(u8, sub, "fetch")) { + std.debug.print(fetch_options ++ "\n", .{self.exec_name}); + } else if (std.mem.eql(u8, sub, "serve")) { + std.debug.print(serve_options ++ "\n", .{self.exec_name}); + } else if (std.mem.eql(u8, sub, "mcp")) { + std.debug.print(mcp_options ++ "\n", .{self.exec_name}); + } else { + std.debug.print(usage, .{self.exec_name}); + } + if (success) return std.process.cleanExit(); + std.process.exit(1); + }, + else => {}, + } + std.debug.print(usage, .{self.exec_name}); if (success) { return std.process.cleanExit(); diff --git a/src/cli.zig b/src/cli.zig index bf5c35aa..141bc12f 100644 --- a/src/cli.zig +++ b/src/cli.zig @@ -268,7 +268,17 @@ pub fn Builder(comptime commands: anytype) type { inline for (commands) |command| { // Match a command. if (std.mem.eql(u8, cmd_str, command.name)) { - return .{ exec_name, try parseCommand(allocator, command, &args) }; + const cmd_parsed = parseCommand(allocator, command, &args) catch |err| { + if (err == error.HelpRequested) { + // help requested, return help + var h = @FieldType(Union, "help"){}; + if (@hasField(@FieldType(Union, "help"), "subcommand")) { + h.subcommand = command.name; + } + return .{ exec_name, @unionInit(Union, "help", h) }; + } else return err; + }; + return .{ exec_name, cmd_parsed }; } } @@ -291,7 +301,17 @@ pub fn Builder(comptime commands: anytype) type { inline for (commands) |command| { if (std.mem.eql(u8, @tagName(command_enum), command.name)) { - return .{ exec_name, try parseCommand(allocator, command, &args) }; + const cmd_parsed = parseCommand(allocator, command, &args) catch |err| { + if (err == error.HelpRequested) { + // help requested, return help + var h = @FieldType(Union, "help"){}; + if (@hasField(@FieldType(Union, "help"), "subcommand")) { + h.subcommand = command.name; + } + return .{ exec_name, @unionInit(Union, "help", h) }; + } else return err; + }; + return .{ exec_name, cmd_parsed }; } } @@ -587,6 +607,11 @@ pub fn Builder(comptime commands: anytype) type { } } + // Subcommand help: `lightpanda fetch help` or `lightpanda fetch --help` + if (std.mem.eql(u8, option_name, "help") or std.mem.eql(u8, option_name, "--help")) { + return error.HelpRequested; + } + // Encountered an option we don't know of. if (std.mem.startsWith(u8, option_name, "--")) { log.fatal(.app, "unknown argument", .{ .mode = command.name, .arg = option_name });