diff --git a/source/command.zig b/source/command.zig index b085e6f..b42020e 100644 --- a/source/command.zig +++ b/source/command.zig @@ -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); diff --git a/source/parameters.zig b/source/parameters.zig index f5efb5e..d0f1b42 100644 --- a/source/parameters.zig +++ b/source/parameters.zig @@ -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, diff --git a/source/parser.zig b/source/parser.zig index 7dec9b3..7b3e582 100644 --- a/source/parser.zig +++ b/source/parser.zig @@ -425,18 +425,25 @@ 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(); - @field(self.output, param.name) = try param.converter(context, intermediate, 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| { - // 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. - @field(self.output, param.name) = def; - } else { - @field(self.output, param.name) = null; - return; + } 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. + @field(self.output, param.name) = def; + } else { + @field(self.output, param.name) = null; + return; + } } } }