I think I still prefer snake_case stylistically, but this style fits in a lot better with other zig code and is the officially endorsed style, so it makes sense to use it. Since I don't have good test coverage, some conversions may have been missed, but the demo still builds, which is a decent amount of coverage. This was pretty easy to do with a find and replace, so it should be reasonably thorough.
148 lines
5.4 KiB
Zig
148 lines
5.4 KiB
Zig
const std = @import("std");
|
|
|
|
const ConversionError = @import("./errors.zig").ConversionError;
|
|
const ncmeta = @import("./meta.zig");
|
|
const parameters = @import("./parameters.zig");
|
|
|
|
const ValueCount = parameters.ValueCount;
|
|
const ParameterGenerics = parameters.ParameterGenerics;
|
|
|
|
const ErrorWriter = std.ArrayList(u8).Writer;
|
|
|
|
pub fn ConverterSignature(comptime gen: ParameterGenerics) type {
|
|
return *const fn (
|
|
context: gen.UserContext,
|
|
input: gen.IntermediateType(),
|
|
failure: ErrorWriter,
|
|
) ConversionError!gen.ConvertedType();
|
|
}
|
|
|
|
pub fn DefaultConverter(comptime gen: ParameterGenerics) ?ConverterSignature(gen) {
|
|
return if (comptime gen.multi)
|
|
MultiConverter(gen)
|
|
else switch (@typeInfo(gen.OutputType)) {
|
|
.Bool => FlagConverter(gen),
|
|
.Int => IntConverter(gen),
|
|
.Pointer => |info| if (info.size == .Slice and info.child == u8)
|
|
StringConverter(gen)
|
|
else
|
|
null,
|
|
.Enum => |info| if (info.is_exhaustive) ChoiceConverter(gen) else null,
|
|
// 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)
|
|
StructConverter(gen)
|
|
else
|
|
null,
|
|
else => null,
|
|
};
|
|
}
|
|
|
|
fn MultiConverter(comptime gen: ParameterGenerics) ?ConverterSignature(gen) {
|
|
const converter = DefaultConverter(
|
|
ncmeta.copyStruct(ParameterGenerics, gen, .{ .multi = false }),
|
|
) orelse
|
|
@compileError("no default converter");
|
|
const Intermediate = gen.IntermediateType();
|
|
|
|
return struct {
|
|
pub fn handler(context: gen.UserContext, input: Intermediate, failure: ErrorWriter) ConversionError!std.ArrayList(gen.OutputType) {
|
|
var output = std.ArrayList(gen.OutputType).initCapacity(input.allocator, input.items.len) catch
|
|
return ConversionError.ConversionFailed;
|
|
|
|
for (input.items) |item| {
|
|
output.appendAssumeCapacity(try converter(context, item, failure));
|
|
}
|
|
|
|
return output;
|
|
}
|
|
}.handler;
|
|
}
|
|
|
|
fn FlagConverter(comptime gen: ParameterGenerics) ConverterSignature(gen) {
|
|
return struct {
|
|
pub fn handler(_: gen.UserContext, input: []const u8, _: ErrorWriter) ConversionError!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 StringConverter(comptime gen: ParameterGenerics) ConverterSignature(gen) {
|
|
return struct {
|
|
pub fn handler(_: gen.UserContext, input: []const u8, _: ErrorWriter) ConversionError![]const u8 {
|
|
return input;
|
|
}
|
|
}.handler;
|
|
}
|
|
|
|
fn IntConverter(comptime gen: ParameterGenerics) ConverterSignature(gen) {
|
|
const IntType = gen.OutputType;
|
|
|
|
return struct {
|
|
pub fn handler(_: gen.UserContext, input: []const u8, failure: ErrorWriter) ConversionError!IntType {
|
|
return std.fmt.parseInt(IntType, input, 0) catch {
|
|
try failure.print("cannot interpret \"{s}\" as an integer", .{input});
|
|
return ConversionError.ConversionFailed;
|
|
};
|
|
}
|
|
}.handler;
|
|
}
|
|
|
|
fn StructConverter(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, input: Intermediate, failure: ErrorWriter) ConversionError!StructType {
|
|
if (input.items.len != type_info.fields.len) {
|
|
try failure.print(
|
|
"Wrong number of fields provided. Got {d}, needed {d}",
|
|
.{ input.items.len, type_info.fields.len },
|
|
);
|
|
return ConversionError.ConversionFailed;
|
|
}
|
|
|
|
var result: StructType = undefined;
|
|
inline for (comptime type_info.fields, 0..) |field, idx| {
|
|
const Converter = comptime DefaultConverter(
|
|
ncmeta.copyStruct(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, input.items[idx], failure);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
}.handler;
|
|
}
|
|
|
|
fn ChoiceConverter(comptime gen: ParameterGenerics) ConverterSignature(gen) {
|
|
const EnumType = gen.OutputType;
|
|
|
|
return struct {
|
|
pub fn handler(_: gen.UserContext, input: []const u8, failure: ErrorWriter) ConversionError!EnumType {
|
|
return std.meta.stringToEnum(gen.ConvertedType(), input) orelse {
|
|
try failure.print("\"{s}\" is not a valid choice", .{input});
|
|
return ConversionError.ConversionFailed;
|
|
};
|
|
}
|
|
}.handler;
|
|
}
|