NOCLIP/source/converters.zig
torque e31e41d975
all: start scratching the refactoring itch
This is basically a full rewrite but with a much more solid concept of
what the public API looks like, which has informed some of the
lower-level decisions. This is not at feature parity with the main
branch yet, but it does handle some things better. The main
functionality missing is the help text generation and subcommands.
There's still some design to think about on the subcommand side of
things.
2023-03-30 00:29:46 -07:00

122 lines
4.0 KiB
Zig

const std = @import("std");
const ParameterGenerics = @import("./doodle.zig").ParameterGenerics;
const CommandError = @import("./doodle.zig").Errors;
pub const ConversionError = error{
BadValue,
};
pub fn ConverterSignature(comptime gen: ParameterGenerics) type {
return if (gen.no_context())
*const fn ([]const u8) ConversionError!gen.ResultType()
else
*const fn (gen.ContextType, []const u8) ConversionError!gen.ResultType();
}
pub fn default_converter(comptime gen: ParameterGenerics) ?ConverterSignature(gen) {
return switch (@typeInfo(gen.ResultType())) {
.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),
else => null,
};
}
fn flag_converter(comptime gen: ParameterGenerics) ConverterSignature(gen) {
return if (gen.no_context())
struct {
pub fn handler(input: []const u8) 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
else
struct {
pub fn handler(_: gen.ContextType, input: []const u8) 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 string_converter(comptime gen: ParameterGenerics) ConverterSignature(gen) {
return if (gen.no_context())
struct {
pub fn handler(value: []const u8) ConversionError![]const u8 {
return value;
}
}.handler
else
struct {
pub fn handler(_: gen.ContextType, value: []const u8) ConversionError![]const u8 {
return value;
}
}.handler;
}
fn int_converter(comptime gen: ParameterGenerics) ConverterSignature(gen) {
const IntType = gen.ResultType();
std.debug.assert(switch (@typeInfo(IntType)) {
.Int => true,
else => false,
});
return if (gen.no_context())
struct {
pub fn handler(value: []const u8) ConversionError!IntType {
return std.fmt.parseInt(IntType, value, 0) catch return ConversionError.BadValue;
}
}.handler
else
struct {
pub fn handler(_: gen.ContextType, value: []const u8) ConversionError!IntType {
return std.fmt.parseInt(IntType, value, 0) catch return ConversionError.BadValue;
}
}.handler;
}
fn choice_converter(comptime gen: ParameterGenerics) ConverterSignature(gen) {
const EnumType = gen.ResultType();
return if (gen.no_context())
struct {
pub fn handler(value: []const u8) ConversionError!EnumType {
return std.meta.stringToEnum(gen.ResultType(), value) orelse ConversionError.BadValue;
}
}.handler
else
struct {
pub fn handler(_: gen.ContextType, value: []const u8) ConversionError!EnumType {
return std.meta.stringToEnum(gen.ResultType(), value) orelse ConversionError.BadValue;
}
}.handler;
}