all: start organizing into components and add user context support
The user can provide a context type and corresponding value that will get passed into any executed callbacks. This allows for complex behavior through side effects and provides a mechanism by which the user can pass an allocator into argument handlers, etc. There was also a lot of restructuring in this including a bit more automagical behavior, like making parameters that wrap optional types default to being optional. The start of automatic handler picking (user overridable, of course) is in place as well. Needing to specify the userdata context type makes things a bit more verbose, and there's some other jank I'm interested in trying to remove. I have some ideas, but I don't know how far I can go in my abuse of the compiler. However, this seems like it will be usable once I get around to writing the help text generation.
This commit is contained in:
parent
e4b11a16ce
commit
b1bac01257
@ -1,60 +1,42 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const noclip = @import("noclip");
|
const noclip = @import("noclip");
|
||||||
|
|
||||||
const subData: noclip.CommandData = .{ .name = "subcommand", .help = "this a sub command\n" };
|
const context: []const u8 = "hello friend";
|
||||||
|
const ContextType = @TypeOf(context);
|
||||||
|
|
||||||
const subFlag = noclip.StringOption{
|
const helpFlag = noclip.HelpFlag(.{ .UserContext = ContextType });
|
||||||
.name = "meta",
|
|
||||||
.short = "-m",
|
|
||||||
.handler = noclip.passthrough,
|
|
||||||
};
|
|
||||||
const subArg = noclip.StringArg{
|
|
||||||
.name = "sub",
|
|
||||||
.handler = noclip.passthrough,
|
|
||||||
};
|
|
||||||
const subSpec = .{ subFlag, subArg };
|
|
||||||
const subCommand = noclip.Command(subData, subSpec, void, subCallback);
|
|
||||||
|
|
||||||
const cdata: noclip.CommandData = .{
|
const subData: noclip.CommandData = .{ .name = "subcommand", .help = "this a sub command" };
|
||||||
.name = "main",
|
const subFlag: noclip.StringOption(ContextType) = .{ .name = "meta", .short = "-m" };
|
||||||
.help = "main CLI entry point\n",
|
const subArg: noclip.StringArg(ContextType) = .{ .name = "sub" };
|
||||||
};
|
const subSpec = .{ helpFlag, subFlag, subArg };
|
||||||
const flagCheck = noclip.FlagOption{
|
const subCommand: noclip.CommandParser(subData, subSpec, ContextType, subCallback) = .{};
|
||||||
|
|
||||||
|
fn wrecker(zontext: ContextType, input: []const u8) ![]const u8 {
|
||||||
|
std.debug.print("ctx: {s}\n", .{zontext});
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cdata: noclip.CommandData = .{ .name = "main", .help = "main CLI entry point" };
|
||||||
|
const flagCheck: noclip.FlagOption(ContextType) = .{
|
||||||
.name = "flag",
|
.name = "flag",
|
||||||
.default = .{ .value = false },
|
|
||||||
.truthy = .{ .short = "-f", .long = "--flag" },
|
.truthy = .{ .short = "-f", .long = "--flag" },
|
||||||
.falsy = .{ .long = "--no-flag" },
|
.falsy = .{ .long = "--no-flag" },
|
||||||
};
|
};
|
||||||
const inputOption = noclip.StringOption{
|
const inputOption: noclip.StringOption(ContextType) = .{
|
||||||
.name = "input",
|
.name = "input",
|
||||||
.short = "-i",
|
.short = "-i",
|
||||||
.long = "--input",
|
.long = "--input",
|
||||||
|
.handler = wrecker,
|
||||||
.envVar = "OPTS_INPUT",
|
.envVar = "OPTS_INPUT",
|
||||||
.handler = noclip.passthrough,
|
|
||||||
};
|
|
||||||
const outputOption = noclip.StringOption{
|
|
||||||
.name = "output",
|
|
||||||
.long = "--output",
|
|
||||||
.default = .{ .value = "waoh" },
|
|
||||||
.handler = noclip.passthrough,
|
|
||||||
};
|
|
||||||
const numberOption = noclip.ValuedOption(i32){
|
|
||||||
.name = "number",
|
|
||||||
.short = "-n",
|
|
||||||
.long = "--number",
|
|
||||||
.handler = noclip.intHandler(i32),
|
|
||||||
};
|
|
||||||
const argCheck = noclip.StringArg{
|
|
||||||
.name = "argument",
|
|
||||||
.handler = noclip.passthrough,
|
|
||||||
};
|
|
||||||
const argAgain = noclip.StringArg{
|
|
||||||
.name = "another",
|
|
||||||
.handler = noclip.passthrough,
|
|
||||||
};
|
};
|
||||||
|
const outputOption: noclip.StringOption(ContextType) = .{ .name = "output", .long = "--output", .default = "waoh" };
|
||||||
|
const numberOption: noclip.ValuedOption(.{ .Output = i32, .UserContext = ContextType }) = .{ .name = "number", .short = "-n", .long = "--number" };
|
||||||
|
const argCheck: noclip.StringArg(ContextType) = .{ .name = "argument" };
|
||||||
|
const argAgain: noclip.StringArg(ContextType) = .{ .name = "another", .default = "nope" };
|
||||||
|
|
||||||
const mainSpec = .{
|
const mainSpec = .{
|
||||||
noclip.defaultHelpFlag,
|
helpFlag,
|
||||||
flagCheck,
|
flagCheck,
|
||||||
inputOption,
|
inputOption,
|
||||||
outputOption,
|
outputOption,
|
||||||
@ -64,14 +46,14 @@ const mainSpec = .{
|
|||||||
subCommand,
|
subCommand,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn subCallback(_: void, result: noclip.CommandResult(subSpec)) !void {
|
pub fn subCallback(_: ContextType, result: noclip.CommandResult(subSpec, ContextType)) !void {
|
||||||
std.debug.print("subcommand {}!!!\n", .{result});
|
std.debug.print("subcommand {any}!!!\n", .{result});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn mainCommand(_: void, result: noclip.CommandResult(mainSpec)) !void {
|
pub fn mainCommand(_: ContextType, result: noclip.CommandResult(mainSpec, ContextType)) !void {
|
||||||
std.debug.print(
|
std.debug.print(
|
||||||
\\arguments: {{
|
\\arguments: {{
|
||||||
\\ .flag = {}
|
\\ .flag = {any}
|
||||||
\\ .input = {s}
|
\\ .input = {s}
|
||||||
\\ .output = {s}
|
\\ .output = {s}
|
||||||
\\ .number = {d}
|
\\ .number = {d}
|
||||||
@ -92,7 +74,7 @@ pub fn mainCommand(_: void, result: noclip.CommandResult(mainSpec)) !void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn main() !void {
|
pub fn main() !void {
|
||||||
const command = noclip.Command(cdata, mainSpec, void, mainCommand);
|
var command: noclip.CommandParser(cdata, mainSpec, ContextType, mainCommand) = .{};
|
||||||
|
|
||||||
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
|
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
|
||||||
defer arena.deinit();
|
defer arena.deinit();
|
||||||
@ -101,5 +83,5 @@ pub fn main() !void {
|
|||||||
var argit = try std.process.argsWithAllocator(allocator);
|
var argit = try std.process.argsWithAllocator(allocator);
|
||||||
_ = argit.next();
|
_ = argit.next();
|
||||||
|
|
||||||
try command.execute(allocator, std.process.ArgIterator, &argit, {});
|
try command.execute(allocator, std.process.ArgIterator, &argit, context);
|
||||||
}
|
}
|
||||||
|
38
source/handlers.zig
Normal file
38
source/handlers.zig
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const builtin = std.builtin;
|
||||||
|
|
||||||
|
const noclip = @import("./noclip.zig");
|
||||||
|
|
||||||
|
pub fn stringHandler(comptime UserContext: type) HandlerType(.{ .UserContext = UserContext, .Output = []const u8 }) {
|
||||||
|
return struct {
|
||||||
|
pub fn handler(_: UserContext, buf: []const u8) ![]const u8 {
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
}.handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn intHandler(comptime UserContext: type, comptime IntType: type) HandlerType(.{ .UserContext = UserContext, .Output = IntType }) {
|
||||||
|
return struct {
|
||||||
|
pub fn handler(_: UserContext, buf: []const u8) std.fmt.ParseIntError!IntType {
|
||||||
|
return try std.fmt.parseInt(IntType, buf, 0);
|
||||||
|
}
|
||||||
|
}.handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn HandlerType(comptime args: noclip.ParameterArgs) type {
|
||||||
|
return *const fn (args.UserContext, []const u8) anyerror!args.Output;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getDefaultHandler(comptime args: noclip.ParameterArgs) ?HandlerType(args) {
|
||||||
|
switch (@typeInfo(args.Output)) {
|
||||||
|
.Optional => |info| return getDefaultHandler(.{ .Output = info.child, .UserContext = args.user }),
|
||||||
|
.Int => return intHandler(args.UserContext, args.Output),
|
||||||
|
.Pointer => |info| {
|
||||||
|
if (info.size == .Slice and info.child == u8) {
|
||||||
|
return stringHandler(args.UserContext);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
else => return null,
|
||||||
|
}
|
||||||
|
}
|
109
source/meta.zig
Normal file
109
source/meta.zig
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const StructField = std.builtin.Type.StructField;
|
||||||
|
|
||||||
|
/// Given a type and a struct literal of defaults to add, this function creates
|
||||||
|
/// a simulacrum type with additional defaults set on its fields.
|
||||||
|
///
|
||||||
|
/// This function cannot remove default values from fields, but it can add some
|
||||||
|
/// to fields that don't have them, and it can overwrite existing defaults
|
||||||
|
pub fn UpdateDefaults(comptime input: type, comptime defaults: anytype) type {
|
||||||
|
comptime {
|
||||||
|
const inputInfo = @typeInfo(input);
|
||||||
|
const fieldcount = switch (inputInfo) {
|
||||||
|
.Struct => |spec| {
|
||||||
|
if (spec.decls.len > 0) {
|
||||||
|
@compileError("UpdateDefaults only works on structs " ++
|
||||||
|
"without decls due to limitations in @Type.");
|
||||||
|
}
|
||||||
|
break spec.fields.len;
|
||||||
|
},
|
||||||
|
else => @compileError("can only add default value to struct type"),
|
||||||
|
};
|
||||||
|
|
||||||
|
var fields: [fieldcount]StructField = undefined;
|
||||||
|
for (inputInfo.Struct.fields) |field, idx| {
|
||||||
|
fields[idx] = .{
|
||||||
|
.name = field.name,
|
||||||
|
.field_type = field.field_type,
|
||||||
|
// the cast ostensibly does type checking for us. It also makes
|
||||||
|
// setting null defaults work, and it converts comptime_int to
|
||||||
|
// the appropriate type, which is nice for ergonomics. Not sure
|
||||||
|
// if it introduces weird edge cases. Probably it's fine?
|
||||||
|
.default_value = if (@hasField(@TypeOf(defaults), field.name))
|
||||||
|
@ptrCast(?*const anyopaque, &@as(field.field_type, @field(defaults, field.name)))
|
||||||
|
else
|
||||||
|
field.default_value,
|
||||||
|
.is_comptime = field.is_comptime,
|
||||||
|
.alignment = field.alignment,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return @Type(.{ .Struct = .{
|
||||||
|
.layout = inputInfo.Struct.layout,
|
||||||
|
.backing_integer = inputInfo.Struct.backing_integer,
|
||||||
|
.fields = &fields,
|
||||||
|
.decls = inputInfo.Struct.decls,
|
||||||
|
.is_tuple = inputInfo.Struct.is_tuple,
|
||||||
|
} });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test "add basic default" {
|
||||||
|
const Base = struct { a: u8 };
|
||||||
|
const Defaulted = UpdateDefaults(Base, .{ .a = 4 });
|
||||||
|
|
||||||
|
const value = Defaulted{};
|
||||||
|
try std.testing.expectEqual(@as(u8, 4), value.a);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "overwrite basic default" {
|
||||||
|
const Base = struct { a: u8 = 0 };
|
||||||
|
const Defaulted = UpdateDefaults(Base, .{ .a = 1 });
|
||||||
|
|
||||||
|
const value = Defaulted{};
|
||||||
|
try std.testing.expectEqual(@as(u8, 1), value.a);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "add string default" {
|
||||||
|
const Base = struct { a: []const u8 };
|
||||||
|
const Defaulted = UpdateDefaults(Base, .{ .a = "hello" });
|
||||||
|
|
||||||
|
const value = Defaulted{};
|
||||||
|
try std.testing.expectEqual(@as([]const u8, "hello"), value.a);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "add null default" {
|
||||||
|
const Base = struct { a: ?u8 };
|
||||||
|
const Defaulted = UpdateDefaults(Base, .{ .a = null });
|
||||||
|
|
||||||
|
const value = Defaulted{};
|
||||||
|
try std.testing.expectEqual(@as(?u8, null), value.a);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "add enum default" {
|
||||||
|
const Options = enum { good, bad };
|
||||||
|
const Base = struct { a: Options };
|
||||||
|
const Defaulted = UpdateDefaults(Base, .{ .a = .good });
|
||||||
|
|
||||||
|
const value = Defaulted{};
|
||||||
|
try std.testing.expectEqual(Options.good, value.a);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "preserve existing default" {
|
||||||
|
const Base = struct { a: ?u8 = 2, b: u8 };
|
||||||
|
const Defaulted = UpdateDefaults(Base, .{ .b = 3 });
|
||||||
|
|
||||||
|
const value = Defaulted{};
|
||||||
|
try std.testing.expectEqual(@as(?u8, 2), value.a);
|
||||||
|
try std.testing.expectEqual(@as(?u8, 3), value.b);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "add multiple defaults" {
|
||||||
|
const Base = struct { a: u8, b: i8, c: ?u8 };
|
||||||
|
const Defaulted = UpdateDefaults(Base, .{ .a = 3, .c = 2 });
|
||||||
|
|
||||||
|
const value = Defaulted{ .b = -1 };
|
||||||
|
try std.testing.expectEqual(@as(u8, 3), value.a);
|
||||||
|
try std.testing.expectEqual(@as(i8, -1), value.b);
|
||||||
|
try std.testing.expectEqual(@as(?u8, 2), value.c);
|
||||||
|
}
|
@ -14,6 +14,8 @@
|
|||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const StructField = std.builtin.Type.StructField;
|
const StructField = std.builtin.Type.StructField;
|
||||||
|
pub const meta = @import("./meta.zig");
|
||||||
|
pub const handlers = @import("./handlers.zig");
|
||||||
|
|
||||||
const Brand = enum {
|
const Brand = enum {
|
||||||
Option,
|
Option,
|
||||||
@ -22,34 +24,6 @@ const Brand = enum {
|
|||||||
Command,
|
Command,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn noopHandleGen(comptime ResultType: type) *const fn (buf: []const u8) anyerror!ResultType {
|
|
||||||
return struct {
|
|
||||||
pub fn handler(input: []const u8) anyerror!ResultType {
|
|
||||||
return input;
|
|
||||||
}
|
|
||||||
}.handler;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const noopHandler = noopHandleGen([]const u8);
|
|
||||||
pub const passthrough = noopHandler;
|
|
||||||
const noOptHandler = noopHandleGen(?[]const u8);
|
|
||||||
|
|
||||||
pub fn intHandler(comptime intType: type) *const fn (buf: []const u8) std.fmt.ParseIntError!intType {
|
|
||||||
return struct {
|
|
||||||
pub fn handler(buf: []const u8) std.fmt.ParseIntError!intType {
|
|
||||||
return try std.fmt.parseInt(intType, buf, 0);
|
|
||||||
}
|
|
||||||
}.handler;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn intRadixHandler(comptime intType: type, radix: u8) *const fn (buf: []const u8) std.fmt.ParseIntError!intType {
|
|
||||||
return struct {
|
|
||||||
pub fn handler(buf: []const u8) std.fmt.ParseIntError!intType {
|
|
||||||
return try std.fmt.parseInt(intType, buf, radix);
|
|
||||||
}
|
|
||||||
}.handler;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const OptionError = error{
|
pub const OptionError = error{
|
||||||
BadShortOption,
|
BadShortOption,
|
||||||
BadLongOption,
|
BadLongOption,
|
||||||
@ -59,103 +33,112 @@ pub const OptionError = error{
|
|||||||
ExtraArguments,
|
ExtraArguments,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const ArgCountCategory = enum {
|
pub const ArgCount = union(enum) {
|
||||||
None,
|
// TODO: how is this different than .Some = 0?
|
||||||
Some,
|
|
||||||
Many,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const ArgCount = union(ArgCountCategory) {
|
|
||||||
None: void,
|
None: void,
|
||||||
Some: u32,
|
Some: u32,
|
||||||
|
// TODO: how is this meaningfully different than .Some = 2 ** 32 - 1? (it
|
||||||
|
// is unlikely anyone would specify 4 billion arguments on the command line,
|
||||||
|
// or that the command line would tolerate such a thing particularly well)
|
||||||
Many: void,
|
Many: void,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn ValuedOption(comptime resultType: type) type {
|
pub const ParameterArgs = struct {
|
||||||
return struct {
|
Output: type,
|
||||||
pub fn brand(_: @This()) Brand {
|
UserContext: type,
|
||||||
return .Option;
|
};
|
||||||
}
|
|
||||||
|
pub fn ValuedOption(comptime args: ParameterArgs) type {
|
||||||
|
// We use a combination of the resultType and default value to decide if an
|
||||||
|
// option must be provided to the command line. The default is specified
|
||||||
|
// when the type is constructed, so we cannot definitively decide it here.
|
||||||
|
// It can be checked (along with the handler function) when constructing
|
||||||
|
// the CommandResult type and thus be reasonably compile-time checked.
|
||||||
|
|
||||||
|
comptime var result = struct {
|
||||||
|
pub const brand: Brand = .Option;
|
||||||
|
pub const mayBeOptional: bool = switch (@typeInfo(args.Output)) {
|
||||||
|
.Optional => true,
|
||||||
|
else => false,
|
||||||
|
};
|
||||||
|
pub const ResultType: type = args.Output;
|
||||||
|
pub const ContextType: type = args.UserContext;
|
||||||
|
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
// this fake optional is a workaround for a bug in the stage1 compiler
|
// Should this be unconditionally made an optional type? Adding an extra
|
||||||
// (it doesn't handle nested optionals in struct fields correctly) and
|
// layer of optional here doesn't seem to give us any advantage that I
|
||||||
// should be replaced with proper optionals as soon as stage2 is
|
// can think of. An argument is optional if either mayBeOptional is true
|
||||||
// functional.
|
// or default is not null.
|
||||||
default: union(enum) { none: void, value: resultType } = .none,
|
default: (if (mayBeOptional) args.Output else ?args.Output) = null,
|
||||||
// this is a combination conversion/validation callback.
|
// this is optional so that null can be provided as a default if there's
|
||||||
// Should we try to pass a user context? Zig's bound functions
|
// not a sane default handler that can be selected (or generated). The
|
||||||
// don't seem to coerce nicely to this type, probably because
|
// handler can never actually be null, so we'll check for that when
|
||||||
// they're no longer just a pointer. Any nontrivial type may need an
|
// creating CommandResult and cause a compileError there if the handler
|
||||||
// allocator context passed.
|
// is null. That will allow us to force unwrap these safely in the
|
||||||
handler: *const fn (input: []const u8) anyerror!resultType,
|
// parsing funcion.
|
||||||
|
handler: ?handlers.HandlerType(args) = handlers.getDefaultHandler(args),
|
||||||
short: ?*const [2]u8 = null,
|
short: ?*const [2]u8 = null,
|
||||||
long: ?[]const u8 = null,
|
long: ?[]const u8 = null,
|
||||||
help: ?[]const u8 = null,
|
help: ?[]const u8 = null,
|
||||||
|
|
||||||
envVar: ?[]const u8 = null,
|
envVar: ?[]const u8 = null,
|
||||||
hideResult: bool = false,
|
hideResult: bool = false,
|
||||||
eager: bool = false,
|
|
||||||
|
|
||||||
|
// TODO: for ArgCount.Some > 1 semantics: automatically wrap args.Output
|
||||||
|
// in an array? Eliminates the need for an allocator, but precludes
|
||||||
|
// memory management techniques that may be better.
|
||||||
args: ArgCount = .{ .Some = 1 },
|
args: ArgCount = .{ .Some = 1 },
|
||||||
|
|
||||||
pub fn ResultType(comptime _: @This()) type {
|
fn required(self: @This()) bool {
|
||||||
return resultType;
|
return !@TypeOf(self).mayBeOptional and self.default == null;
|
||||||
}
|
|
||||||
|
|
||||||
pub fn required(self: @This()) bool {
|
|
||||||
return self.default == .none;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const StringOption = ValuedOption([]const u8);
|
pub fn StringOption(comptime UserContext: type) type {
|
||||||
|
return ValuedOption(.{ .Output = []const u8, .UserContext = UserContext });
|
||||||
|
}
|
||||||
|
|
||||||
// this could be ValuedOption(bool) except it allows truthy/falsy flag variants
|
// this could be ValuedOption(bool) except it allows truthy/falsy flag variants
|
||||||
// and it doesn't want to parse a value. It could be lowered into a pair of
|
// and it doesn't want to parse a value. with some contortions, it could be
|
||||||
// ValuedOption(bool) though, if consuming a value became optional.
|
// lowered into a pair of ValuedOption(bool), if we allowed multiple different
|
||||||
|
// arguments to specify the same output field name.
|
||||||
|
|
||||||
const ShortLong = struct {
|
const ShortLong = struct {
|
||||||
short: ?*const [2]u8 = null,
|
short: ?*const [2]u8 = null,
|
||||||
long: ?[]const u8 = null,
|
long: ?[]const u8 = null,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const FlagOption = struct {
|
// Flags don't have a conversion callback,
|
||||||
pub fn brand(_: @This()) Brand {
|
pub fn FlagOption(comptime UserContext: type) type {
|
||||||
return .Flag;
|
return struct {
|
||||||
}
|
pub const brand: Brand = .Flag;
|
||||||
|
// TODO: it may in some cases be useful to distinguish if the flag has been
|
||||||
|
// entirely unspecified, but I can't think of any right now.
|
||||||
|
pub const ResultType: type = bool;
|
||||||
|
pub const ContextType: type = UserContext;
|
||||||
|
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
default: union(enum) { none: void, value: bool } = .{ .value = false },
|
default: bool = false,
|
||||||
truthy: ShortLong = .{},
|
truthy: ShortLong = .{},
|
||||||
falsy: ShortLong = .{},
|
falsy: ShortLong = .{},
|
||||||
help: ?[]const u8 = null,
|
help: ?[]const u8 = null,
|
||||||
// should envVar be split into truthy/falsy the way the args are? otherwise
|
|
||||||
// we probably need to peek the value of the environmental variable to see
|
|
||||||
// if it is truthy or falsy. Honestly, looking at the value is probably
|
|
||||||
// required to avoid violating the principle of least astonishment because
|
|
||||||
// otherwise you can get `MY_VAR=false` causing `true` to be emitted, which
|
|
||||||
// looks and feels bad. But then we need to establish a truthiness baseline.
|
|
||||||
// case insensitive true/false is easy. What about yes/no? 0/1 (or nonzero).
|
|
||||||
// How about empty strings? I'd base on how it reads, and `MY_VAR= prog`
|
|
||||||
// reads falsy to me.
|
|
||||||
envVar: ?[]const u8 = null,
|
envVar: ?[]const u8 = null,
|
||||||
hideResult: bool = false,
|
hideResult: bool = false,
|
||||||
eager: ?*const fn (cmd: CommandData) anyerror!void = null,
|
eager: ?*const fn (UserContext, CommandData) anyerror!void = null,
|
||||||
|
|
||||||
pub fn ResultType(comptime _: @This()) type {
|
|
||||||
return bool;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn required(self: @This()) bool {
|
|
||||||
return self.default == .none;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
pub fn produceHelp(cmd: CommandData) !void {
|
pub fn produceHelp(comptime UserContext: type) *const fn (UserContext, CommandData) anyerror!void {
|
||||||
std.debug.print("{s}", .{cmd.help});
|
return struct {
|
||||||
|
pub fn handler(_: UserContext, data: CommandData) !void {
|
||||||
|
std.debug.print("{s}\n", .{data.help});
|
||||||
std.process.exit(0);
|
std.process.exit(0);
|
||||||
}
|
}
|
||||||
|
}.handler;
|
||||||
|
}
|
||||||
|
|
||||||
// I haven't really figured out a way not to special case the help flag.
|
// I haven't really figured out a way not to special case the help flag.
|
||||||
// Everything else assumes that it can be handled in a vacuum without worrying
|
// Everything else assumes that it can be handled in a vacuum without worrying
|
||||||
@ -169,49 +152,59 @@ const HelpFlagArgs = struct {
|
|||||||
short: ?*const [2]u8 = "-h",
|
short: ?*const [2]u8 = "-h",
|
||||||
long: ?[]const u8 = "--help",
|
long: ?[]const u8 = "--help",
|
||||||
help: []const u8 = "print this help message",
|
help: []const u8 = "print this help message",
|
||||||
|
UserContext: type,
|
||||||
};
|
};
|
||||||
|
|
||||||
// this doesn't work in situ,
|
// this doesn't work in situ,
|
||||||
pub fn HelpFlag(comptime args: HelpFlagArgs) FlagOption {
|
pub fn HelpFlag(comptime args: HelpFlagArgs) FlagOption(args.UserContext) {
|
||||||
return FlagOption{
|
return FlagOption(args.UserContext){
|
||||||
.name = args.name,
|
.name = args.name,
|
||||||
.truthy = .{ .short = args.short, .long = args.long },
|
.truthy = .{ .short = args.short, .long = args.long },
|
||||||
.help = args.help,
|
.help = args.help,
|
||||||
.hideResult = true,
|
.hideResult = true,
|
||||||
.eager = produceHelp,
|
.eager = produceHelp(args.UserContext),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// but this does, which is kind of silly.
|
// but this does, which is kind of silly.
|
||||||
pub const defaultHelpFlag = HelpFlag(.{});
|
pub const defaultHelpFlag = HelpFlag(.{});
|
||||||
|
|
||||||
pub fn Argument(comptime resultType: type) type {
|
pub fn Argument(comptime args: ParameterArgs) type {
|
||||||
|
// NOTE: optional arguments are kind of weird, since they're identified by
|
||||||
|
// the order they're specified on the command line rather than by a named
|
||||||
|
// flag. As long as the order is not violated, it's perfectly safe to omit
|
||||||
|
// them if the provided specification supplies a default value.
|
||||||
|
|
||||||
return struct {
|
return struct {
|
||||||
pub fn brand(_: @This()) Brand {
|
pub const brand: Brand = .Argument;
|
||||||
return .Argument;
|
pub const mayBeOptional: bool = switch (@typeInfo(args.Output)) {
|
||||||
}
|
.Optional => true,
|
||||||
|
else => false,
|
||||||
|
};
|
||||||
|
pub const ResultType: type = args.Output;
|
||||||
|
pub const ContextType: type = args.UserContext;
|
||||||
|
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
default: union(enum) { none: void, value: resultType } = .none,
|
default: (if (mayBeOptional) args.Output else ?args.Output) = null,
|
||||||
handler: *const fn (input: []const u8) anyerror!resultType,
|
handler: ?handlers.HandlerType(args) = handlers.getDefaultHandler(args),
|
||||||
help: ?[]const u8 = null,
|
help: ?[]const u8 = null,
|
||||||
hideResult: bool = false,
|
hideResult: bool = false,
|
||||||
// allow loading arguments from environmental variables? I don't think
|
// allow loading arguments from environmental variables? I don't think
|
||||||
// it's possible to come up with sane semantics for this.
|
// it's possible to come up with sane semantics for this.
|
||||||
|
|
||||||
pub fn ResultType(comptime _: @This()) type {
|
fn required(self: @This()) bool {
|
||||||
return resultType;
|
return !@TypeOf(self).mayBeOptional and self.default == null;
|
||||||
}
|
|
||||||
|
|
||||||
pub fn required(self: @This()) bool {
|
|
||||||
return self.default == .none;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const StringArg = Argument([]const u8);
|
pub fn StringArg(comptime UserContext: type) type {
|
||||||
|
return Argument(.{ .Output = []const u8, .UserContext = UserContext });
|
||||||
|
}
|
||||||
|
|
||||||
pub const CommandData = struct {
|
pub const CommandData = struct {
|
||||||
|
pub const brand: Brand = .Command;
|
||||||
|
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
help: []const u8 = "",
|
help: []const u8 = "",
|
||||||
// cheesy way to allow deferred initialization of the subcommands
|
// cheesy way to allow deferred initialization of the subcommands
|
||||||
@ -219,50 +212,40 @@ pub const CommandData = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/// spec is a tuple of ValuedOption, FlagOption, and Argument
|
/// spec is a tuple of ValuedOption, FlagOption, and Argument
|
||||||
pub fn Command(
|
pub fn CommandParser(
|
||||||
comptime commandData: CommandData,
|
comptime commandData: CommandData,
|
||||||
comptime spec: anytype,
|
comptime spec: anytype,
|
||||||
comptime UdType: type,
|
comptime UserContext: type,
|
||||||
comptime callback: *const fn (userdata: UdType, res: CommandResult(spec)) anyerror!void,
|
comptime callback: *const fn (UserContext, CommandResult(spec, UserContext)) anyerror!void,
|
||||||
) type {
|
) type {
|
||||||
comptime var argCount = 0;
|
comptime var argCount = 0;
|
||||||
comptime var requiredOptions = 0;
|
|
||||||
comptime for (spec) |param| {
|
comptime for (spec) |param| {
|
||||||
switch (param.brand()) {
|
switch (@TypeOf(param).brand) {
|
||||||
.Argument => argCount += 1,
|
.Argument => argCount += 1,
|
||||||
.Option, .Flag => if (param.required()) {
|
.Option, .Flag, .Command => continue,
|
||||||
requiredOptions += 1;
|
|
||||||
},
|
|
||||||
.Command => continue,
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const ResultType = CommandResult(spec);
|
const ResultType = CommandResult(spec, UserContext);
|
||||||
const RequiredType = RequiredTracker(spec);
|
const RequiredType = RequiredTracker(spec);
|
||||||
|
|
||||||
const ParseState = enum { Mixed, ForcedArgs };
|
const ParseState = enum { Mixed, ForcedArgs };
|
||||||
|
|
||||||
return struct {
|
return struct {
|
||||||
pub fn brand() Brand {
|
pub const brand: Brand = .Command;
|
||||||
return .Command;
|
pub const ContextType = UserContext;
|
||||||
}
|
// this should be copied at compile time
|
||||||
// copy happens at comptime
|
var data: CommandData = commandData;
|
||||||
pub var data: CommandData = commandData;
|
|
||||||
|
|
||||||
/// parse command line arguments from an iterator
|
/// parse command line arguments from an iterator
|
||||||
pub fn execute(alloc: std.mem.Allocator, comptime argit_type: type, argit: *argit_type, userdata: UdType) !void {
|
pub fn execute(self: @This(), alloc: std.mem.Allocator, comptime argit_type: type, argit: *argit_type, context: UserContext) !void {
|
||||||
|
try self.attachSubcommands(alloc);
|
||||||
// we could precompute some tuples that would simplify some of the later logic:
|
|
||||||
// tuple of eager Options/Flags
|
|
||||||
// tuple of non-eager Options/Flags
|
|
||||||
// tuple of Arguments
|
|
||||||
// tuple of Commands
|
|
||||||
|
|
||||||
var result: ResultType = createCommandresult();
|
var result: ResultType = createCommandresult();
|
||||||
var required: RequiredType = .{};
|
var required: RequiredType = .{};
|
||||||
var parseState: ParseState = .Mixed;
|
var parseState: ParseState = .Mixed;
|
||||||
|
|
||||||
try extractEnvVars(alloc, &result, &required);
|
try extractEnvVars(alloc, &result, &required, context);
|
||||||
|
|
||||||
var seenArgs: u32 = 0;
|
var seenArgs: u32 = 0;
|
||||||
argloop: while (argit.next()) |arg| {
|
argloop: while (argit.next()) |arg| {
|
||||||
@ -287,10 +270,10 @@ pub fn Command(
|
|||||||
if (arg[1] == '-') {
|
if (arg[1] == '-') {
|
||||||
// we have a long flag or option
|
// we have a long flag or option
|
||||||
specloop: inline for (spec) |param| {
|
specloop: inline for (spec) |param| {
|
||||||
switch (comptime param.brand()) {
|
switch (@TypeOf(param).brand) {
|
||||||
.Option => {
|
.Option => {
|
||||||
// have to force lower the handler to runtime
|
// have to force lower the handler to runtime
|
||||||
var handler = param.handler;
|
// var handler = param.handler.?;
|
||||||
if (param.long) |flag| {
|
if (param.long) |flag| {
|
||||||
if (std.mem.eql(u8, flag, arg)) {
|
if (std.mem.eql(u8, flag, arg)) {
|
||||||
if (comptime param.required()) {
|
if (comptime param.required()) {
|
||||||
@ -299,7 +282,7 @@ pub fn Command(
|
|||||||
|
|
||||||
const val = argit.next() orelse return OptionError.MissingArgument;
|
const val = argit.next() orelse return OptionError.MissingArgument;
|
||||||
if (param.hideResult == false) {
|
if (param.hideResult == false) {
|
||||||
@field(result, param.name) = try handler(val);
|
@field(result, param.name) = try param.handler.?(context, val);
|
||||||
}
|
}
|
||||||
continue :argloop;
|
continue :argloop;
|
||||||
}
|
}
|
||||||
@ -310,11 +293,7 @@ pub fn Command(
|
|||||||
if (variant[0]) |flag| {
|
if (variant[0]) |flag| {
|
||||||
if (std.mem.eql(u8, flag, arg)) {
|
if (std.mem.eql(u8, flag, arg)) {
|
||||||
if (param.eager) |handler| {
|
if (param.eager) |handler| {
|
||||||
try handler(data);
|
try handler(context, data);
|
||||||
}
|
|
||||||
|
|
||||||
if (comptime param.required()) {
|
|
||||||
@field(required, param.name) = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (param.hideResult == false) {
|
if (param.hideResult == false) {
|
||||||
@ -335,9 +314,9 @@ pub fn Command(
|
|||||||
// we have a short flag, which may be multiple fused flags
|
// we have a short flag, which may be multiple fused flags
|
||||||
shortloop: for (arg[1..]) |shorty, idx| {
|
shortloop: for (arg[1..]) |shorty, idx| {
|
||||||
specloop: inline for (spec) |param| {
|
specloop: inline for (spec) |param| {
|
||||||
switch (comptime param.brand()) {
|
switch (@TypeOf(param).brand) {
|
||||||
.Option => {
|
.Option => {
|
||||||
var handler = param.handler;
|
// var handler = param.handler.?;
|
||||||
if (param.short) |flag| {
|
if (param.short) |flag| {
|
||||||
if (flag[1] == shorty) {
|
if (flag[1] == shorty) {
|
||||||
if (comptime param.required()) {
|
if (comptime param.required()) {
|
||||||
@ -350,7 +329,7 @@ pub fn Command(
|
|||||||
argit.next() orelse return OptionError.MissingArgument;
|
argit.next() orelse return OptionError.MissingArgument;
|
||||||
|
|
||||||
if (param.hideResult == false) {
|
if (param.hideResult == false) {
|
||||||
@field(result, param.name) = try handler(val);
|
@field(result, param.name) = try param.handler.?(context, val);
|
||||||
}
|
}
|
||||||
continue :argloop;
|
continue :argloop;
|
||||||
}
|
}
|
||||||
@ -361,11 +340,7 @@ pub fn Command(
|
|||||||
if (variant[0]) |flag| {
|
if (variant[0]) |flag| {
|
||||||
if (flag[1] == shorty) {
|
if (flag[1] == shorty) {
|
||||||
if (param.eager) |handler| {
|
if (param.eager) |handler| {
|
||||||
try handler(data);
|
try handler(context, data);
|
||||||
}
|
|
||||||
|
|
||||||
if (comptime param.required()) {
|
|
||||||
@field(required, param.name) = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (param.hideResult == false) {
|
if (param.hideResult == false) {
|
||||||
@ -388,30 +363,33 @@ pub fn Command(
|
|||||||
defer seenArgs += 1;
|
defer seenArgs += 1;
|
||||||
comptime var idx = 0;
|
comptime var idx = 0;
|
||||||
inline for (spec) |param| {
|
inline for (spec) |param| {
|
||||||
switch (comptime param.brand()) {
|
switch (@TypeOf(param).brand) {
|
||||||
|
.Command => {
|
||||||
|
if (std.mem.eql(u8, @TypeOf(param).data.name, arg)) {
|
||||||
|
// we're calling a subcommand
|
||||||
|
try checkErrors(seenArgs, required);
|
||||||
|
try callback(context, result);
|
||||||
|
return param.execute(alloc, argit_type, argit, context);
|
||||||
|
}
|
||||||
|
},
|
||||||
.Argument => {
|
.Argument => {
|
||||||
if (seenArgs == idx) {
|
if (seenArgs == idx) {
|
||||||
var handler = param.handler;
|
if (comptime param.required()) {
|
||||||
@field(result, param.name) = try handler(arg);
|
@field(required, param.name) = true;
|
||||||
|
}
|
||||||
|
// var handler = param.handler;
|
||||||
|
@field(result, param.name) = try param.handler.?(context, arg);
|
||||||
continue :argloop;
|
continue :argloop;
|
||||||
}
|
}
|
||||||
idx += 1;
|
idx += 1;
|
||||||
},
|
},
|
||||||
.Command => {
|
|
||||||
if (seenArgs == argCount and std.mem.eql(u8, param.data.name, arg)) {
|
|
||||||
// we're calling a subcommand
|
|
||||||
try checkErrors(seenArgs, required);
|
|
||||||
try callback(userdata, result);
|
|
||||||
return param.execute(alloc, argit_type, argit, userdata);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
else => continue,
|
else => continue,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try checkErrors(seenArgs, required);
|
try checkErrors(seenArgs, required);
|
||||||
try callback(userdata, result);
|
try callback(context, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fn checkErrors(seenArgs: u32, required: RequiredType) OptionError!void {
|
inline fn checkErrors(seenArgs: u32, required: RequiredType) OptionError!void {
|
||||||
@ -421,6 +399,8 @@ pub fn Command(
|
|||||||
return OptionError.ExtraArguments;
|
return OptionError.ExtraArguments;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
describeError(required);
|
||||||
|
|
||||||
inline for (@typeInfo(@TypeOf(required)).Struct.fields) |field| {
|
inline for (@typeInfo(@TypeOf(required)).Struct.fields) |field| {
|
||||||
if (@field(required, field.name) == false) {
|
if (@field(required, field.name) == false) {
|
||||||
return OptionError.MissingOption;
|
return OptionError.MissingOption;
|
||||||
@ -428,15 +408,23 @@ pub fn Command(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn attachSubcommands(alloc: std.mem.Allocator) !void {
|
pub fn describeError(required: RequiredType) void {
|
||||||
|
inline for (@typeInfo(@TypeOf(required)).Struct.fields) |field| {
|
||||||
|
if (@field(required, field.name) == false) {
|
||||||
|
std.debug.print("missing {s}\n", .{field.name});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn attachSubcommands(_: @This(), alloc: std.mem.Allocator) !void {
|
||||||
if (data.subcommands == null) {
|
if (data.subcommands == null) {
|
||||||
data.subcommands = std.ArrayList(*CommandData).init(alloc);
|
data.subcommands = std.ArrayList(*CommandData).init(alloc);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline for (spec) |param| {
|
inline for (spec) |param| {
|
||||||
switch (comptime param.brand()) {
|
switch (@TypeOf(param).brand) {
|
||||||
.Command => {
|
.Command => {
|
||||||
try data.subcommands.append(¶m);
|
try data.subcommands.?.append(&@TypeOf(param).data);
|
||||||
},
|
},
|
||||||
else => continue,
|
else => continue,
|
||||||
}
|
}
|
||||||
@ -468,31 +456,32 @@ pub fn Command(
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extractEnvVars(alloc: std.mem.Allocator, result: *ResultType, required: *RequiredType) !void {
|
fn extractEnvVars(
|
||||||
|
alloc: std.mem.Allocator,
|
||||||
|
result: *ResultType,
|
||||||
|
required: *RequiredType,
|
||||||
|
context: UserContext,
|
||||||
|
) !void {
|
||||||
var env: std.process.EnvMap = try std.process.getEnvMap(alloc);
|
var env: std.process.EnvMap = try std.process.getEnvMap(alloc);
|
||||||
defer env.deinit();
|
defer env.deinit();
|
||||||
|
|
||||||
inline for (spec) |param| {
|
inline for (spec) |param| {
|
||||||
switch (comptime param.brand()) {
|
const ParamType = @TypeOf(param);
|
||||||
|
switch (ParamType.brand) {
|
||||||
.Option => {
|
.Option => {
|
||||||
if (param.envVar) |want| {
|
if (param.envVar) |want| {
|
||||||
if (env.get(want)) |value| {
|
if (env.get(want)) |value| {
|
||||||
if (comptime param.required()) {
|
if (param.required()) {
|
||||||
@field(required, param.name) = true;
|
@field(required, param.name) = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var handler = param.handler;
|
@field(result, param.name) = try param.handler.?(context, value);
|
||||||
@field(result, param.name) = try handler(value);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
.Flag => {
|
.Flag => {
|
||||||
if (param.envVar) |want| {
|
if (param.envVar) |want| {
|
||||||
if (env.get(want)) |value| {
|
if (env.get(want)) |value| {
|
||||||
if (comptime param.required()) {
|
|
||||||
@field(required, param.name) = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@field(result, param.name) = try scryTruthiness(alloc, value);
|
@field(result, param.name) = try scryTruthiness(alloc, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -505,13 +494,10 @@ pub fn Command(
|
|||||||
inline fn createCommandresult() ResultType {
|
inline fn createCommandresult() ResultType {
|
||||||
var result: ResultType = undefined;
|
var result: ResultType = undefined;
|
||||||
inline for (spec) |param| {
|
inline for (spec) |param| {
|
||||||
switch (comptime param.brand()) {
|
switch (@TypeOf(param).brand) {
|
||||||
.Command => continue,
|
.Command => continue,
|
||||||
else => if (param.hideResult == false) {
|
else => if (param.hideResult == false) {
|
||||||
@field(result, param.name) = switch (param.default) {
|
@field(result, param.name) = param.default orelse continue;
|
||||||
.none => continue,
|
|
||||||
.value => |val| val,
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -520,16 +506,31 @@ pub fn Command(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn CommandResult(comptime spec: anytype) type {
|
pub fn CommandResult(comptime spec: anytype, comptime UserContext: type) type {
|
||||||
comptime {
|
comptime {
|
||||||
// not sure how to do this without iterating twice, so let's iterate
|
// not sure how to do this without iterating twice, so let's iterate
|
||||||
// twice
|
// twice
|
||||||
var outsize = 0;
|
var outsize = 0;
|
||||||
for (spec) |param| {
|
for (spec) |param| {
|
||||||
switch (param.brand()) {
|
const ParamType = @TypeOf(param);
|
||||||
|
if (ParamType.ContextType != UserContext) {
|
||||||
|
@compileError("param \"" ++ param.name ++ "\" has wrong context type (wanted: " ++ @typeName(UserContext) ++ ", got: " ++ @typeName(ParamType.ContextType) ++ ")");
|
||||||
|
}
|
||||||
|
switch (ParamType.brand) {
|
||||||
|
.Argument, .Option => {
|
||||||
|
if (param.handler == null) {
|
||||||
|
@compileError("param \"" ++ param.name ++ "\" does not have a handler");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (ParamType.brand) {
|
||||||
.Command => continue,
|
.Command => continue,
|
||||||
else => if (param.hideResult == false) {
|
else => {
|
||||||
|
if (param.hideResult == false) {
|
||||||
outsize += 1;
|
outsize += 1;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -538,22 +539,20 @@ pub fn CommandResult(comptime spec: anytype) type {
|
|||||||
|
|
||||||
var idx = 0;
|
var idx = 0;
|
||||||
for (spec) |param| {
|
for (spec) |param| {
|
||||||
switch (param.brand()) {
|
const ParamType = @TypeOf(param);
|
||||||
|
switch (ParamType.brand) {
|
||||||
.Command => continue,
|
.Command => continue,
|
||||||
else => if (param.hideResult == true) continue,
|
else => if (param.hideResult == true) continue,
|
||||||
}
|
}
|
||||||
|
|
||||||
const fieldType = param.ResultType();
|
const FieldType = ParamType.ResultType;
|
||||||
|
|
||||||
fields[idx] = .{
|
fields[idx] = .{
|
||||||
.name = param.name,
|
.name = param.name,
|
||||||
.field_type = fieldType,
|
.field_type = FieldType,
|
||||||
.default_value = switch (param.default) {
|
.default_value = @ptrCast(?*const anyopaque, ¶m.default),
|
||||||
.none => null,
|
|
||||||
.value => |val| @ptrCast(?*const anyopaque, &val),
|
|
||||||
},
|
|
||||||
.is_comptime = false,
|
.is_comptime = false,
|
||||||
.alignment = @alignOf(fieldType),
|
.alignment = @alignOf(FieldType),
|
||||||
};
|
};
|
||||||
|
|
||||||
idx += 1;
|
idx += 1;
|
||||||
@ -574,10 +573,16 @@ fn RequiredTracker(comptime spec: anytype) type {
|
|||||||
// twice
|
// twice
|
||||||
var outsize = 0;
|
var outsize = 0;
|
||||||
for (spec) |param| {
|
for (spec) |param| {
|
||||||
switch (param.brand()) {
|
const ParamType = @TypeOf(param);
|
||||||
.Argument, .Command => continue,
|
switch (ParamType.brand) {
|
||||||
else => {
|
// flags are always optional, and commands don't map into the
|
||||||
if (param.required()) outsize += 1;
|
// output type.
|
||||||
|
.Flag, .Command => continue,
|
||||||
|
.Argument, .Option => if (param.required()) {
|
||||||
|
// if mayBeOptional is false, then the argument/option is
|
||||||
|
// required. Otherwise, we have to check if a default has
|
||||||
|
// been provided.
|
||||||
|
outsize += 1;
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -586,9 +591,10 @@ fn RequiredTracker(comptime spec: anytype) type {
|
|||||||
|
|
||||||
var idx = 0;
|
var idx = 0;
|
||||||
for (spec) |param| {
|
for (spec) |param| {
|
||||||
switch (param.brand()) {
|
const ParamType = @TypeOf(param);
|
||||||
.Argument, .Command => continue,
|
switch (ParamType.brand) {
|
||||||
else => if (param.required()) {
|
.Flag, .Command => continue,
|
||||||
|
.Argument, .Option => if (param.required()) {
|
||||||
fields[idx] = .{
|
fields[idx] = .{
|
||||||
.name = param.name,
|
.name = param.name,
|
||||||
.field_type = bool,
|
.field_type = bool,
|
||||||
@ -610,3 +616,7 @@ fn RequiredTracker(comptime spec: anytype) type {
|
|||||||
} });
|
} });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test {
|
||||||
|
_ = meta;
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user