The converters are hooked up, including some weird magic under the hood to automatically handle multi-values as well as the many-to-one case of the structure. The many-to-many case of arrays/slices is not currently handled, but it should be straightforward to do. Hooking up subcommands should be pretty straightforward if I've designed this correctly, so that will be next. The final major piece of the puzzle is help text generation, which in theory can be largely carried over from the old implementation. The error handling is very poorly done right now as well, and I need to figure out a strategy. My plan for converters is to pass in a writable buffer that the parser owns that they can write messages to when they fail, to provide useful context. I will need to figure out how this works for recursive converters (e.g. the struct converter) since the failure happens inside-out, but the error message will read much better if it's composed outside-in. There are many ways to tackle this. The code will also need to be restructured to not be a monolithic 1000 line file.
129 lines
4.6 KiB
Zig
129 lines
4.6 KiB
Zig
const std = @import("std");
|
|
|
|
const ParameterGenerics = @import("./doodle.zig").ParameterGenerics;
|
|
const CommandError = @import("./doodle.zig").Errors;
|
|
const ValueCount = @import("./doodle.zig").ValueCount;
|
|
const ParseError = @import("./doodle.zig").ParseError;
|
|
const ncmeta = @import("./meta.zig");
|
|
|
|
pub fn ConverterSignature(comptime gen: ParameterGenerics) type {
|
|
return *const fn (*gen.UserContext, gen.IntermediateType()) ParseError!gen.ConvertedType();
|
|
}
|
|
|
|
pub fn default_converter(comptime gen: ParameterGenerics) ?ConverterSignature(gen) {
|
|
return if (comptime gen.multi)
|
|
multi_converter(gen)
|
|
else switch (@typeInfo(gen.OutputType)) {
|
|
.Bool => flag_converter(gen),
|
|
.Int => int_converter(gen),
|
|
.Pointer => |info| if (info.size == .Slice and info.child == u8)
|
|
string_converter(gen)
|
|
else
|
|
null,
|
|
.Enum => choice_converter(gen),
|
|
// TODO: how to handle structs with field defaults? maybe this should only work
|
|
// for tuples, which I don't think can have defaults.
|
|
.Struct => |info| if (gen.value_count == .fixed and gen.value_count.fixed == info.fields.len)
|
|
struct_converter(gen)
|
|
else
|
|
null,
|
|
else => null,
|
|
};
|
|
}
|
|
|
|
fn multi_converter(comptime gen: ParameterGenerics) ?ConverterSignature(gen) {
|
|
const converter = default_converter(
|
|
ncmeta.copy_struct(ParameterGenerics, gen, .{ .multi = false }),
|
|
) orelse
|
|
@compileError("no default converter");
|
|
const Intermediate = gen.IntermediateType();
|
|
|
|
return struct {
|
|
pub fn handler(context: *gen.UserContext, input: Intermediate) ParseError!std.ArrayList(gen.OutputType) {
|
|
var output = std.ArrayList(gen.OutputType).initCapacity(input.allocator, input.items.len) catch
|
|
return ParseError.ConversionFailed;
|
|
|
|
for (input.items) |item| {
|
|
output.appendAssumeCapacity(try converter(context, item));
|
|
}
|
|
|
|
return output;
|
|
}
|
|
}.handler;
|
|
}
|
|
|
|
fn flag_converter(comptime gen: ParameterGenerics) ConverterSignature(gen) {
|
|
return struct {
|
|
pub fn handler(_: *gen.UserContext, input: []const u8) ParseError!bool {
|
|
// treat an empty string as falsy
|
|
if (input.len == 0) return false;
|
|
|
|
if (input.len <= 5) {
|
|
var lowerBuf: [5]u8 = undefined;
|
|
const comp = std.ascii.lowerString(&lowerBuf, input);
|
|
|
|
inline for ([_][]const u8{ "false", "no", "0" }) |candidate| {
|
|
if (std.mem.eql(u8, comp, candidate)) return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}.handler;
|
|
}
|
|
|
|
fn string_converter(comptime gen: ParameterGenerics) ConverterSignature(gen) {
|
|
return struct {
|
|
pub fn handler(_: *gen.UserContext, value: []const u8) ParseError![]const u8 {
|
|
return value;
|
|
}
|
|
}.handler;
|
|
}
|
|
|
|
fn int_converter(comptime gen: ParameterGenerics) ConverterSignature(gen) {
|
|
const IntType = gen.OutputType;
|
|
|
|
return struct {
|
|
pub fn handler(_: *gen.UserContext, value: []const u8) ParseError!IntType {
|
|
return std.fmt.parseInt(IntType, value, 0) catch return ParseError.ConversionFailed;
|
|
}
|
|
}.handler;
|
|
}
|
|
|
|
fn struct_converter(comptime gen: ParameterGenerics) ConverterSignature(gen) {
|
|
const StructType = gen.OutputType;
|
|
const type_info = @typeInfo(StructType).Struct;
|
|
const Intermediate = gen.IntermediateType();
|
|
|
|
return struct {
|
|
pub fn handler(context: *gen.UserContext, value: Intermediate) ParseError!StructType {
|
|
if (value.items.len != type_info.fields.len) return ParseError.ConversionFailed;
|
|
|
|
var result: StructType = undefined;
|
|
inline for (comptime type_info.fields, 0..) |field, idx| {
|
|
const converter = comptime default_converter(
|
|
ncmeta.copy_struct(ParameterGenerics, gen, .{
|
|
.OutputType = field.type,
|
|
.value_count = .{ .fixed = 1 },
|
|
}),
|
|
) orelse
|
|
@compileError("cannot get converter for field" ++ field.name);
|
|
|
|
@field(result, field.name) = try converter(context, value.items[idx]);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
}.handler;
|
|
}
|
|
|
|
fn choice_converter(comptime gen: ParameterGenerics) ConverterSignature(gen) {
|
|
const EnumType = gen.OutputType;
|
|
|
|
return struct {
|
|
pub fn handler(_: *gen.UserContext, value: []const u8) ParseError!EnumType {
|
|
return std.meta.stringToEnum(gen.ConvertedType(), value) orelse ParseError.ConversionFailed;
|
|
}
|
|
}.handler;
|
|
}
|