parser: support unexposed values

Having thought about this more, it seems likely that complex converters
could benefit from being able to parse their arguments on the fly
without having to structure them into a rigid type. This is sort of a
get out of jail free card for custom converters as they can dump into
the user context type or whatever they want directly.
This commit is contained in:
torque 2023-04-03 01:36:45 -07:00
parent b01c10409d
commit 6ffc1c1a4c
Signed by: torque
SSH Key Fingerprint: SHA256:nCrXefBNo6EbjNSQhv0nXmEg/VuNq3sMF5b8zETw3Tk
3 changed files with 31 additions and 18 deletions

View File

@ -25,7 +25,6 @@ fn BuilderGenerics(comptime UserContext: type) type {
multi: bool = false,
pub fn arg_gen(comptime self: @This()) ParameterGenerics {
if (self.OutputType == void) @compileError("argument must have OutputType specified");
if (self.value_count == .flag) @compileError("argument may not be a flag");
if (self.value_count == .count) @compileError("argument may not be a count");
@ -39,8 +38,8 @@ fn BuilderGenerics(comptime UserContext: type) type {
}
pub fn opt_gen(comptime self: @This()) ParameterGenerics {
if (self.OutputType == void) @compileError("option must have OutputType specified");
if (self.value_count == .flag) @compileError("option may not be a flag");
if (self.value_count == .count) @compileError("option may not be a count");
return ParameterGenerics{
.UserContext = UserContext,
@ -246,6 +245,7 @@ pub fn CommandBuilder(comptime UserContext: type) type {
var env_var_fields: []const StructField = &[_]StructField{};
paramloop: for (spec, 0..) |param, idx| {
const PType = @TypeOf(param);
// these three blocks are to check for redundantly defined tags and
// environment variables. This only works within a command. It
// doesn't support compile time checks for conflict into
@ -278,12 +278,13 @@ pub fn CommandBuilder(comptime UserContext: type) type {
.alignment = 0,
}};
if (!PType.has_output) continue :paramloop;
while (flag_skip > 0) {
flag_skip -= 1;
continue :paramloop;
}
const PType = @TypeOf(param);
if (PType.is_flag) {
var peek = idx + 1;
var bias_seen: [ncmeta.enum_length(FlagBias)]bool = [_]bool{false} ** ncmeta.enum_length(FlagBias);

View File

@ -29,16 +29,20 @@ pub const FlagBias = enum {
pub const ParameterGenerics = struct {
UserContext: type = void,
/// If void, do not expose this parameter in the aggregate converted parameter
/// object. The converter for this parameter shall not return a value. This may be
/// useful for implementing complex conversion that produces output through its
/// side effects or by modifying the user context.
OutputType: type = void,
param_type: ParameterType,
value_count: ValueCount,
/// allow this named parameter to be passed multiple times.
/// values will be appended when it is encountered. If false, only the
/// final encountered instance will be used.
multi: bool,
// since we now use multi in place of greedy values for simplicity, we may want to
// convert this an enum or add an additional flag to distinguish between the
// many-to-many and the many-to-one cases.
multi: bool,
pub fn fixed_value_count(comptime OutputType: type, comptime value_count: ValueCount) ValueCount {
return comptime if (value_count == .fixed)
@ -54,14 +58,14 @@ pub const ParameterGenerics = struct {
value_count;
}
pub fn clone_without_multi(comptime self: @This()) @This() {
return .{ .UserContext = self.UserContext, .OutputType = self.OutputType, .param_type = self.param_type, .value_count = self.value_count, .multi = false };
}
pub fn has_context(comptime self: @This()) bool {
return comptime self.UserContext != void;
}
pub fn has_output(comptime self: @This()) bool {
return self.OutputType != void;
}
pub fn is_flag(comptime self: @This()) bool {
return comptime switch (self.value_count) {
.flag, .count => true,
@ -71,7 +75,7 @@ pub const ParameterGenerics = struct {
pub fn ConvertedType(comptime self: @This()) type {
// is this the correct way to collapse this?
return comptime if (self.multi and self.value_count != .count)
return comptime if (self.multi and self.value_count != .count and self.OutputType != void)
std.ArrayList(self.ReturnValue())
else
self.ReturnValue();
@ -184,6 +188,7 @@ fn OptionType(comptime generics: ParameterGenerics) type {
pub const is_flag: bool = generics.is_flag();
pub const value_count: ValueCount = generics.value_count;
pub const multi: bool = generics.multi;
pub const has_output: bool = generics.has_output();
name: []const u8,
short_tag: ?[]const u8,

View File

@ -425,11 +425,17 @@ pub fn Parser(comptime command: anytype, comptime callback: anytype) type {
if (@field(self.intermediate, param.name)) |intermediate| {
var buffer = std.ArrayList(u8).init(self.allocator);
const writer = buffer.writer();
if (comptime @TypeOf(param).has_output) {
@field(self.output, param.name) = try param.converter(context, intermediate, writer);
} else {
try param.converter(context, intermediate, writer);
}
} else {
if (comptime param.required) {
return ParseError.RequiredParameterMissing;
} else if (comptime param.default) |def| {
} else if (comptime @TypeOf(param).has_output) {
if (comptime param.default) |def| {
// this has to be explicitly set because even though we set it as
// the field default, it gets clobbered because self.output is
// initialized as undefined.
@ -440,6 +446,7 @@ pub fn Parser(comptime command: anytype, comptime callback: anytype) type {
}
}
}
}
fn print_help(self: *@This(), name: []const u8) noreturn {
defer std.process.exit(0);