mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-09-14 15:03:27 -04:00
cli: suggest the closest enum value on a typo
A misspelt value for an enum-typed flag now names the nearest tag as did_you_mean. --dump's peeking validator treats a word within two edits of a format name as that mistake rather than as the url, so --dump htmx no longer becomes a second url. tagNames moves from Config into cli so both can use it.
This commit is contained in:
1 parent
88d133a676
commit
a32d30fa65
2 files changed
+24
-15
No files matched your search
+7
-7
@@ -21,6 +21,7 @@ const zenai = @import("zenai");
|
||||
const lp = @import("lightpanda");
|
||||
|
||||
const cli = @import("cli.zig");
|
||||
const string = @import("string.zig");
|
||||
const dump = @import("browser/dump.zig");
|
||||
const Mime = @import("browser/Mime.zig");
|
||||
|
||||
@@ -316,6 +317,11 @@ fn dumpValidator(_: Allocator, args: *std.process.Args.Iterator, target: *?DumpF
|
||||
var peek_args = args.*;
|
||||
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.
|
||||
if (string.closest(next_arg, tagNames(DumpFormat), 2)) |near| {
|
||||
log.fatal(.app, "invalid option choice", .{ .arg = "--dump", .value = log.red(next_arg), .did_you_mean = log.green(near) });
|
||||
return error.InvalidArgument;
|
||||
}
|
||||
target.* = .html;
|
||||
return;
|
||||
};
|
||||
@@ -1371,13 +1377,7 @@ pub fn validateUserAgent(ua: []const u8) !void {
|
||||
|
||||
/// Tag names of a Zig enum, so a command's allowed values can't drift from the
|
||||
/// enum it sets.
|
||||
pub fn tagNames(comptime E: type) []const []const u8 {
|
||||
const fields = @typeInfo(E).@"enum".fields;
|
||||
var names: [fields.len][]const u8 = undefined;
|
||||
for (fields, &names) |f, *n| n.* = f.name;
|
||||
const frozen = names;
|
||||
return &frozen;
|
||||
}
|
||||
pub const tagNames = cli.tagNames;
|
||||
|
||||
/// `<a|b|c>` ghost-text hint built from the same enum's tag names.
|
||||
pub fn tagHint(comptime E: type) []const u8 {
|
||||
|
||||
+17
-8
@@ -190,6 +190,16 @@ const string = @import("string.zig");
|
||||
/// .help => |tag| printHelp(tag),
|
||||
/// }
|
||||
/// ```
|
||||
pub fn tagNames(comptime E: type) []const []const u8 {
|
||||
return comptime blk: {
|
||||
const fields = @typeInfo(E).@"enum".fields;
|
||||
var names: [fields.len][]const u8 = undefined;
|
||||
for (fields, &names) |f, *n| n.* = f.name;
|
||||
const frozen = names;
|
||||
break :blk &frozen;
|
||||
};
|
||||
}
|
||||
|
||||
pub fn Builder(comptime commands: anytype) type {
|
||||
return struct {
|
||||
const Self = @This();
|
||||
@@ -211,13 +221,7 @@ pub fn Builder(comptime commands: anytype) type {
|
||||
break :blk @Enum(Tag, .exhaustive, &names, &std.simd.iota(Tag, len));
|
||||
};
|
||||
|
||||
const command_names: []const []const u8 = blk: {
|
||||
var names: []const []const u8 = &.{};
|
||||
for (std.meta.fieldNames(Enum)) |name| {
|
||||
names = names ++ &[_][]const u8{name};
|
||||
}
|
||||
break :blk names;
|
||||
};
|
||||
const command_names = tagNames(Enum);
|
||||
|
||||
/// Creates an array of `StructField` out of given options.
|
||||
fn optionsToStructFields(comptime options: anytype) [options.len]std.builtin.Type.StructField {
|
||||
@@ -701,7 +705,12 @@ pub fn Builder(comptime commands: anytype) type {
|
||||
|
||||
const str = args.next() orelse return error.MissingArgument;
|
||||
const v = std.meta.stringToEnum(E, str) orelse {
|
||||
log.fatal(.app, "invalid option choice", .{ .arg = kebab_cased, .value = str });
|
||||
const value = log.red(str);
|
||||
if (string.closest(str, tagNames(E), 2)) |near| {
|
||||
log.fatal(.app, "invalid option choice", .{ .arg = kebab_cased, .value = value, .did_you_mean = log.green(near) });
|
||||
} else {
|
||||
log.fatal(.app, "invalid option choice", .{ .arg = kebab_cased, .value = value });
|
||||
}
|
||||
return error.InvalidArgument;
|
||||
};
|
||||
|
||||
|
||||
Reference in new issue
Block a user