cli: read a url-like bare word as the url, not a misspelt command

`lightpanda version.io` was rejected as a misspelt `version`, 3 edits
within the length-scaled limit. Like curl, it fetches now: the command check
shares --dump's isUrlLike.
This commit is contained in:
Adrià Arrufat committed 2026-09-28 09:22:29 +02:00
1 parent 298f1b1f0c
commit 1188554515
2 files changed
+26 -4

No files matched your search

+19 -3
View File
@@ -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"));
+7 -1
View File
@@ -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);
}