diff --git a/src/Config.zig b/src/Config.zig index 264bcfa88..e6b16d37e 100644 --- a/src/Config.zig +++ b/src/Config.zig @@ -316,9 +316,7 @@ fn dumpValidator(_: Allocator, args: *std.process.Args.Iterator, target: *?DumpF if (peek_args.next()) |next_arg| { const mode = std.meta.stringToEnum(DumpFormat, next_arg) orelse { // Anything else is the positional url, unless it is a misspelt format. - // No format has a `.`, `/` or `:`, so `markdown.com` is a url. - const url_like = std.mem.indexOfAny(u8, next_arg, ".:/") != null; - if (!url_like and string.closest(next_arg, tagNames(DumpFormat)) != null) { + if (!cli.isUrlLike(next_arg) and string.closest(next_arg, tagNames(DumpFormat)) != null) { return cli.invalidChoice("--dump", "", next_arg, tagNames(DumpFormat)); } target.* = .html; @@ -1381,6 +1379,24 @@ test "Config: parseArgs --dump tells a url from a misspelt format" { } } +test "Config: parseArgs tells a url from a misspelt command" { + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + + { + const argv = [_][*:0]const u8{ "lightpanda", "version.io" }; + const proc_args: std.process.Args = .{ .vector = &argv }; + const config = try parseArgs(arena.allocator(), proc_args); + try std.testing.expectEqualStrings("version.io", config.mode.fetch.url.items[0]); + } + { + log.expectLog(&.{.app}); + const argv = [_][*:0]const u8{ "lightpanda", "versoin" }; + const proc_args: std.process.Args = .{ .vector = &argv }; + try std.testing.expectError(error.UnknownCommand, parseArgs(arena.allocator(), proc_args)); + } +} + test "Config: validateUserAgent" { try validateUserAgent("Lightpanda/1.0"); try std.testing.expectError(error.Reserved, validateUserAgent("mozilla/1.0")); diff --git a/src/cli.zig b/src/cli.zig index bcf0afe9f..881644427 100644 --- a/src/cli.zig +++ b/src/cli.zig @@ -200,6 +200,12 @@ pub fn tagNames(comptime E: type) []const []const u8 { }; } +/// No command or choice has a `.`, `/` or `:`, so `markdown.com` is a url +/// however close it is to `markdown`. +pub fn isUrlLike(arg: []const u8) bool { + return std.mem.indexOfAny(u8, arg, ".:/") != null; +} + /// `prefix` was stripped from `value` before matching, like `--log-filter`'s sign. pub fn invalidChoice(arg: []const u8, prefix: []const u8, value: []const u8, choices: []const []const u8) error{InvalidArgument} { var value_buf: [128]u8 = undefined; @@ -456,7 +462,7 @@ pub fn Builder(comptime commands: anytype) type { } // A bare word close to a command name is a typo, not a fetch url. - if (std.mem.startsWith(u8, cmd_str, "--") == false and string.closest(cmd_str, command_names) != null) { + if (std.mem.startsWith(u8, cmd_str, "--") == false and !isUrlLike(cmd_str) and string.closest(cmd_str, command_names) != null) { return unknownCommand(cmd_str); }