2023-03-30 17:00:49 -07:00
|
|
|
const std = @import("std");
|
|
|
|
const StructField = std.builtin.Type.StructField;
|
|
|
|
|
2023-04-02 15:11:50 -07:00
|
|
|
const help = @import("./help.zig");
|
2023-03-30 17:00:49 -07:00
|
|
|
const ncmeta = @import("./meta.zig");
|
|
|
|
const parameters = @import("./parameters.zig");
|
|
|
|
const parser = @import("./parser.zig");
|
|
|
|
|
|
|
|
const ValueCount = parameters.ValueCount;
|
|
|
|
const ParameterGenerics = parameters.ParameterGenerics;
|
|
|
|
const OptionConfig = parameters.OptionConfig;
|
|
|
|
const FlagConfig = parameters.FlagConfig;
|
2023-04-01 13:04:02 -07:00
|
|
|
const ShortLongPair = parameters.ShortLongPair;
|
2023-03-30 17:00:49 -07:00
|
|
|
const FlagBias = parameters.FlagBias;
|
2023-08-05 13:41:21 -07:00
|
|
|
const makeOption = parameters.makeOption;
|
|
|
|
const makeArgument = parameters.makeArgument;
|
2023-03-30 17:00:49 -07:00
|
|
|
|
|
|
|
const Parser = parser.Parser;
|
|
|
|
const ParserInterface = parser.ParserInterface;
|
|
|
|
|
|
|
|
fn BuilderGenerics(comptime UserContext: type) type {
|
|
|
|
return struct {
|
|
|
|
OutputType: type = void,
|
|
|
|
value_count: ValueCount = .{ .fixed = 1 },
|
|
|
|
multi: bool = false,
|
|
|
|
|
2023-08-05 13:41:21 -07:00
|
|
|
pub fn argGen(comptime self: @This()) ParameterGenerics {
|
2023-03-30 17:00:49 -07:00
|
|
|
if (self.value_count == .flag) @compileError("argument may not be a flag");
|
|
|
|
if (self.value_count == .count) @compileError("argument may not be a count");
|
|
|
|
|
|
|
|
return ParameterGenerics{
|
|
|
|
.UserContext = UserContext,
|
|
|
|
.OutputType = self.OutputType,
|
|
|
|
.param_type = .Ordinal,
|
2023-08-05 13:41:21 -07:00
|
|
|
.value_count = ParameterGenerics.fixedValueCount(self.OutputType, self.value_count),
|
2023-03-30 17:00:49 -07:00
|
|
|
.multi = self.multi,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2023-08-05 13:41:21 -07:00
|
|
|
pub fn optGen(comptime self: @This()) ParameterGenerics {
|
2023-03-30 17:00:49 -07:00
|
|
|
if (self.value_count == .flag) @compileError("option may not be a flag");
|
2023-04-03 01:36:45 -07:00
|
|
|
if (self.value_count == .count) @compileError("option may not be a count");
|
2023-03-30 17:00:49 -07:00
|
|
|
|
|
|
|
return ParameterGenerics{
|
|
|
|
.UserContext = UserContext,
|
|
|
|
.OutputType = self.OutputType,
|
|
|
|
.param_type = .Nominal,
|
2023-08-05 13:41:21 -07:00
|
|
|
.value_count = ParameterGenerics.fixedValueCount(self.OutputType, self.value_count),
|
2023-03-30 17:00:49 -07:00
|
|
|
.multi = self.multi,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2023-08-05 13:41:21 -07:00
|
|
|
pub fn countGen(comptime _: @This()) ParameterGenerics {
|
2023-03-30 17:00:49 -07:00
|
|
|
return ParameterGenerics{
|
|
|
|
.UserContext = UserContext,
|
|
|
|
.OutputType = usize,
|
|
|
|
.param_type = .Nominal,
|
|
|
|
.value_count = .count,
|
|
|
|
.multi = true,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2023-08-05 13:41:21 -07:00
|
|
|
pub fn flagGen(comptime self: @This()) ParameterGenerics {
|
2023-03-30 17:00:49 -07:00
|
|
|
return ParameterGenerics{
|
|
|
|
.UserContext = UserContext,
|
|
|
|
.OutputType = bool,
|
|
|
|
.param_type = .Nominal,
|
|
|
|
.value_count = .flag,
|
|
|
|
.multi = self.multi,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2023-09-10 14:52:49 -07:00
|
|
|
pub const GroupOptions = struct {
|
|
|
|
help_flag: ShortLongPair = .{ .short_tag = "-h", .long_tag = "--help" },
|
|
|
|
description: []const u8,
|
|
|
|
};
|
|
|
|
|
|
|
|
pub fn commandGroup(allocator: std.mem.Allocator, comptime options: GroupOptions) !ParserInterface {
|
|
|
|
const cmd = comptime CommandBuilder(void){
|
|
|
|
.help_flag = options.help_flag,
|
|
|
|
.description = options.description,
|
|
|
|
.subcommand_required = true,
|
|
|
|
};
|
|
|
|
|
|
|
|
return try cmd.createInterface(allocator, cmd.noopCallback());
|
|
|
|
}
|
|
|
|
|
2023-09-10 14:50:44 -07:00
|
|
|
fn InterfaceCreator(comptime Command: type) type {
|
|
|
|
return if (Command.ICC.InputType()) |Type|
|
|
|
|
struct {
|
|
|
|
pub fn createInterface(
|
|
|
|
comptime self: Command,
|
|
|
|
allocator: std.mem.Allocator,
|
|
|
|
comptime callback: self.CallbackSignature(),
|
|
|
|
context: Type,
|
|
|
|
) !ParserInterface {
|
|
|
|
return try self._createInterfaceImpl(allocator, callback, context);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
struct {
|
|
|
|
pub fn createInterface(
|
|
|
|
comptime self: Command,
|
|
|
|
allocator: std.mem.Allocator,
|
|
|
|
comptime callback: self.CallbackSignature(),
|
|
|
|
) !ParserInterface {
|
|
|
|
return try self._createInterfaceImpl(allocator, callback, void{});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
pub const InterfaceContextCategory = union(enum) {
|
|
|
|
empty,
|
|
|
|
pointer: type,
|
|
|
|
value: type,
|
|
|
|
|
|
|
|
pub fn fromType(comptime ContextType: type) InterfaceContextCategory {
|
|
|
|
return switch (@typeInfo(ContextType)) {
|
|
|
|
.Void => .empty,
|
|
|
|
.Pointer => |info| if (info.size == .Slice) .{ .value = ContextType } else .{ .pointer = ContextType },
|
|
|
|
// technically, i0, u0, and struct{} should be treated as empty, probably
|
|
|
|
else => .{ .value = ContextType },
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn InputType(comptime self: InterfaceContextCategory) ?type {
|
|
|
|
return switch (self) {
|
|
|
|
.empty => null,
|
|
|
|
.pointer => |Type| Type,
|
|
|
|
.value => |Type| *const Type,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn OutputType(comptime self: InterfaceContextCategory) type {
|
|
|
|
return switch (self) {
|
|
|
|
.empty => void,
|
|
|
|
.pointer => |Type| Type,
|
|
|
|
.value => |Type| Type,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2023-03-30 17:00:49 -07:00
|
|
|
pub fn CommandBuilder(comptime UserContext: type) type {
|
|
|
|
return struct {
|
2023-04-01 13:04:02 -07:00
|
|
|
param_spec: ncmeta.TupleBuilder = .{},
|
|
|
|
// this is a strange hack, but it's easily the path of least resistance
|
|
|
|
help_flag: ShortLongPair = .{ .short_tag = "-h", .long_tag = "--help" },
|
|
|
|
/// if any subcommands are provided, one of them must be specified, or the command has failed.
|
|
|
|
subcommand_required: bool = true,
|
2023-04-04 23:22:12 -07:00
|
|
|
description: []const u8,
|
2023-03-30 17:00:49 -07:00
|
|
|
|
|
|
|
pub const UserContextType = UserContext;
|
2023-09-10 14:50:44 -07:00
|
|
|
pub const ICC: InterfaceContextCategory = InterfaceContextCategory.fromType(UserContextType);
|
2023-03-30 17:00:49 -07:00
|
|
|
|
2023-08-05 13:41:21 -07:00
|
|
|
pub fn createParser(
|
2023-04-01 13:04:02 -07:00
|
|
|
comptime self: @This(),
|
|
|
|
comptime callback: self.CallbackSignature(),
|
|
|
|
allocator: std.mem.Allocator,
|
parser: shove an arena allocator in there
Stay a while and listen to my story.
Due to the design of the parser execution flow, the only reasonable way
to avoid leaking memory in the parser is to use an arena allocator
because the parser itself doesn't have direct access to everything it
allocates, and everything it allocates needs to live for the duration
of whatever callbacks are called.
Now, you might say, if the items it allocates are stored for the
lifetime of whatever callbacks, then that means that the items it
allocates stay allocated for effectively the entire life of the
program. In which case there's really not much point in freeing them
at all, as it's just extra work on exit that the OS should normally
clean up. And you'd be right, except for two details: if the user uses
the current GeneralPurposeAllocator, it will complain about leaks when
deinitialized, which simply isn't groovy. The other detail is that
technically the user can run any code they want after the parser
execution finishes, so forcing the user to leak memory by having an
incomplete API is rude.
The other option would be, as before, forcing the user to supply their
own arena allocator if they don't want to leak, but that's kind of a
rude thing to do and goes against the "all allocators look the same"
design of the standard library, which is what makes it so easy to use
and create allocators with advanced functionality. That seems like an
ugly thing to do, so, instead, each parser gets to eat the memory cost
of storing a pointer to its arena allocator (and the heap cost of the
arena allocator itself).
In theory, subcommands could borrow the arena allocator of their parent
command to save a bit of heap space, but that would make a variety of
creation and cleanup-related tasks less isomorphic between the parents
and the subcommands. I like the current design where commands and
subcommands are the same thing, and I'm not in a rush to disturb that.
I don't think the overhead cost of the arena allocator itself, which
can be measured in double digit bytes, is a particularly steep price
to pay.
2023-07-20 23:15:37 -07:00
|
|
|
) !Parser(self, callback) {
|
|
|
|
// note: this is freed in Parser.deinit
|
|
|
|
var arena = try allocator.create(std.heap.ArenaAllocator);
|
|
|
|
arena.* = std.heap.ArenaAllocator.init(allocator);
|
|
|
|
const arena_alloc = arena.allocator();
|
|
|
|
|
2023-04-01 13:04:02 -07:00
|
|
|
return Parser(self, callback){
|
parser: shove an arena allocator in there
Stay a while and listen to my story.
Due to the design of the parser execution flow, the only reasonable way
to avoid leaking memory in the parser is to use an arena allocator
because the parser itself doesn't have direct access to everything it
allocates, and everything it allocates needs to live for the duration
of whatever callbacks are called.
Now, you might say, if the items it allocates are stored for the
lifetime of whatever callbacks, then that means that the items it
allocates stay allocated for effectively the entire life of the
program. In which case there's really not much point in freeing them
at all, as it's just extra work on exit that the OS should normally
clean up. And you'd be right, except for two details: if the user uses
the current GeneralPurposeAllocator, it will complain about leaks when
deinitialized, which simply isn't groovy. The other detail is that
technically the user can run any code they want after the parser
execution finishes, so forcing the user to leak memory by having an
incomplete API is rude.
The other option would be, as before, forcing the user to supply their
own arena allocator if they don't want to leak, but that's kind of a
rude thing to do and goes against the "all allocators look the same"
design of the standard library, which is what makes it so easy to use
and create allocators with advanced functionality. That seems like an
ugly thing to do, so, instead, each parser gets to eat the memory cost
of storing a pointer to its arena allocator (and the heap cost of the
arena allocator itself).
In theory, subcommands could borrow the arena allocator of their parent
command to save a bit of heap space, but that would make a variety of
creation and cleanup-related tasks less isomorphic between the parents
and the subcommands. I like the current design where commands and
subcommands are the same thing, and I'm not in a rush to disturb that.
I don't think the overhead cost of the arena allocator itself, which
can be measured in double digit bytes, is a particularly steep price
to pay.
2023-07-20 23:15:37 -07:00
|
|
|
.arena = arena,
|
|
|
|
.allocator = arena_alloc,
|
2023-11-08 22:56:11 -08:00
|
|
|
.subcommands = parser.CommandMap.init(arena_alloc),
|
parser: shove an arena allocator in there
Stay a while and listen to my story.
Due to the design of the parser execution flow, the only reasonable way
to avoid leaking memory in the parser is to use an arena allocator
because the parser itself doesn't have direct access to everything it
allocates, and everything it allocates needs to live for the duration
of whatever callbacks are called.
Now, you might say, if the items it allocates are stored for the
lifetime of whatever callbacks, then that means that the items it
allocates stay allocated for effectively the entire life of the
program. In which case there's really not much point in freeing them
at all, as it's just extra work on exit that the OS should normally
clean up. And you'd be right, except for two details: if the user uses
the current GeneralPurposeAllocator, it will complain about leaks when
deinitialized, which simply isn't groovy. The other detail is that
technically the user can run any code they want after the parser
execution finishes, so forcing the user to leak memory by having an
incomplete API is rude.
The other option would be, as before, forcing the user to supply their
own arena allocator if they don't want to leak, but that's kind of a
rude thing to do and goes against the "all allocators look the same"
design of the standard library, which is what makes it so easy to use
and create allocators with advanced functionality. That seems like an
ugly thing to do, so, instead, each parser gets to eat the memory cost
of storing a pointer to its arena allocator (and the heap cost of the
arena allocator itself).
In theory, subcommands could borrow the arena allocator of their parent
command to save a bit of heap space, but that would make a variety of
creation and cleanup-related tasks less isomorphic between the parents
and the subcommands. I like the current design where commands and
subcommands are the same thing, and I'm not in a rush to disturb that.
I don't think the overhead cost of the arena allocator itself, which
can be measured in double digit bytes, is a particularly steep price
to pay.
2023-07-20 23:15:37 -07:00
|
|
|
.help_builder = help.HelpBuilder(self).init(arena_alloc),
|
2023-04-01 13:04:02 -07:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2023-09-10 14:50:44 -07:00
|
|
|
pub usingnamespace InterfaceCreator(@This());
|
|
|
|
|
|
|
|
fn _createInterfaceImpl(
|
2023-08-04 00:14:40 -07:00
|
|
|
comptime self: @This(),
|
|
|
|
allocator: std.mem.Allocator,
|
2023-09-10 14:50:44 -07:00
|
|
|
comptime callback: self.CallbackSignature(),
|
|
|
|
context: (ICC.InputType() orelse void),
|
2023-08-04 00:14:40 -07:00
|
|
|
) !ParserInterface {
|
|
|
|
var arena = try allocator.create(std.heap.ArenaAllocator);
|
|
|
|
arena.* = std.heap.ArenaAllocator.init(allocator);
|
|
|
|
const arena_alloc = arena.allocator();
|
|
|
|
|
|
|
|
var this_parser = try arena_alloc.create(Parser(self, callback));
|
|
|
|
this_parser.* = .{
|
|
|
|
.arena = arena,
|
|
|
|
.allocator = arena_alloc,
|
2023-11-08 22:56:11 -08:00
|
|
|
.subcommands = parser.CommandMap.init(arena_alloc),
|
2023-08-04 00:14:40 -07:00
|
|
|
.help_builder = help.HelpBuilder(self).init(arena_alloc),
|
|
|
|
};
|
|
|
|
|
2023-09-10 14:50:44 -07:00
|
|
|
if (comptime ICC == .empty) {
|
2023-08-04 00:14:40 -07:00
|
|
|
return this_parser.interface();
|
|
|
|
} else {
|
|
|
|
return this_parser.interface(context);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-05 13:41:21 -07:00
|
|
|
pub fn setHelpFlag(
|
2023-04-01 13:04:02 -07:00
|
|
|
comptime self: *@This(),
|
|
|
|
comptime tags: ShortLongPair,
|
|
|
|
) void {
|
|
|
|
self.help_flag = tags;
|
|
|
|
}
|
|
|
|
|
2023-08-27 13:53:14 -07:00
|
|
|
const string_generics = BuilderGenerics(UserContext){ .OutputType = [:0]const u8 };
|
2023-04-01 13:04:02 -07:00
|
|
|
|
2023-08-05 13:41:21 -07:00
|
|
|
pub fn stringOption(
|
2023-04-03 01:38:00 -07:00
|
|
|
comptime self: *@This(),
|
2023-08-05 13:41:21 -07:00
|
|
|
comptime cfg: OptionConfig(string_generics.optGen()),
|
2023-04-03 01:38:00 -07:00
|
|
|
) void {
|
|
|
|
const config = if (cfg.nice_type_name == null)
|
2023-08-05 13:41:21 -07:00
|
|
|
ncmeta.copyStruct(@TypeOf(cfg), cfg, .{ .nice_type_name = "string" })
|
2023-04-03 01:38:00 -07:00
|
|
|
else
|
|
|
|
cfg;
|
|
|
|
|
2023-08-05 13:41:21 -07:00
|
|
|
self.addOption(string_generics, config);
|
2023-04-03 01:38:00 -07:00
|
|
|
}
|
|
|
|
|
2023-08-05 13:41:21 -07:00
|
|
|
pub fn stringArgument(
|
2023-04-03 01:38:00 -07:00
|
|
|
comptime self: *@This(),
|
2023-08-05 13:41:21 -07:00
|
|
|
comptime cfg: OptionConfig(string_generics.argGen()),
|
2023-04-03 01:38:00 -07:00
|
|
|
) void {
|
|
|
|
const config = if (cfg.nice_type_name == null)
|
2023-08-05 13:41:21 -07:00
|
|
|
ncmeta.copyStruct(@TypeOf(cfg), cfg, .{ .nice_type_name = "string" })
|
2023-04-03 01:38:00 -07:00
|
|
|
else
|
|
|
|
cfg;
|
|
|
|
|
2023-08-05 13:41:21 -07:00
|
|
|
self.addArgument(string_generics, config);
|
2023-04-03 01:38:00 -07:00
|
|
|
}
|
|
|
|
|
2023-08-05 13:41:21 -07:00
|
|
|
pub fn simpleFlag(
|
2023-04-03 01:38:00 -07:00
|
|
|
comptime self: *@This(),
|
2023-08-05 13:41:21 -07:00
|
|
|
comptime cfg: FlagConfig(string_generics.flagGen()),
|
2023-04-03 01:38:00 -07:00
|
|
|
) void {
|
2023-08-05 13:41:21 -07:00
|
|
|
self.addFlag(string_generics, cfg);
|
2023-03-30 17:00:49 -07:00
|
|
|
}
|
|
|
|
|
2023-08-05 13:41:21 -07:00
|
|
|
pub fn addArgument(
|
2023-03-30 17:00:49 -07:00
|
|
|
comptime self: *@This(),
|
|
|
|
comptime bgen: BuilderGenerics(UserContext),
|
2023-08-05 13:41:21 -07:00
|
|
|
comptime config: OptionConfig(bgen.argGen()),
|
2023-03-30 17:00:49 -07:00
|
|
|
) void {
|
2023-08-05 13:41:21 -07:00
|
|
|
self.param_spec.add(makeArgument(bgen.argGen(), config));
|
2023-03-30 17:00:49 -07:00
|
|
|
}
|
|
|
|
|
2023-08-05 13:41:21 -07:00
|
|
|
pub fn addOption(
|
2023-03-30 17:00:49 -07:00
|
|
|
comptime self: *@This(),
|
|
|
|
comptime bgen: BuilderGenerics(UserContext),
|
2023-08-05 13:41:21 -07:00
|
|
|
comptime config: OptionConfig(bgen.optGen()),
|
2023-03-30 17:00:49 -07:00
|
|
|
) void {
|
|
|
|
if (comptime bgen.value_count == .fixed and bgen.value_count.fixed == 0) {
|
|
|
|
@compileError(
|
|
|
|
"please use add_flag rather than add_option to " ++
|
|
|
|
"create a 0-argument option",
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2023-08-05 13:41:21 -07:00
|
|
|
self.param_spec.add(makeOption(bgen.optGen(), config));
|
2023-03-30 17:00:49 -07:00
|
|
|
}
|
|
|
|
|
2023-08-05 13:41:21 -07:00
|
|
|
pub fn addFlag(
|
2023-03-30 17:00:49 -07:00
|
|
|
comptime self: *@This(),
|
|
|
|
comptime bgen: BuilderGenerics(UserContext),
|
2023-08-05 13:41:21 -07:00
|
|
|
comptime config: FlagConfig(bgen.flagGen()),
|
2023-03-30 17:00:49 -07:00
|
|
|
) void {
|
|
|
|
comptime {
|
|
|
|
if (config.truthy == null and config.falsy == null and config.env_var == null) {
|
|
|
|
@compileError(
|
|
|
|
"flag " ++
|
|
|
|
config.name ++
|
|
|
|
" must have at least one of truthy flags, falsy flags, or env_var flags",
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2023-08-05 13:41:21 -07:00
|
|
|
const generics = bgen.flagGen();
|
2023-03-30 17:00:49 -07:00
|
|
|
var args = OptionConfig(generics){
|
|
|
|
.name = config.name,
|
|
|
|
//
|
|
|
|
.short_tag = null,
|
|
|
|
.long_tag = null,
|
|
|
|
.env_var = null,
|
|
|
|
//
|
|
|
|
.description = config.description,
|
|
|
|
.default = config.default,
|
|
|
|
.converter = config.converter,
|
|
|
|
//
|
|
|
|
.eager = config.eager,
|
|
|
|
.required = config.required,
|
|
|
|
.global = config.global,
|
|
|
|
//
|
|
|
|
.secret = config.secret,
|
|
|
|
.nice_type_name = "flag",
|
|
|
|
};
|
|
|
|
|
|
|
|
if (config.truthy) |truthy_pair| {
|
|
|
|
if (truthy_pair.short_tag == null and truthy_pair.long_tag == null) {
|
|
|
|
@compileError(
|
|
|
|
"flag " ++
|
|
|
|
config.name ++
|
|
|
|
" truthy pair must have at least short or long tags set",
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
args.short_tag = truthy_pair.short_tag;
|
|
|
|
args.long_tag = truthy_pair.long_tag;
|
|
|
|
args.flag_bias = .truthy;
|
|
|
|
|
2023-08-05 13:41:21 -07:00
|
|
|
self.param_spec.add(makeOption(generics, args));
|
2023-03-30 17:00:49 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if (config.falsy) |falsy_pair| {
|
|
|
|
if (falsy_pair.short_tag == null and falsy_pair.long_tag == null) {
|
|
|
|
@compileError(
|
|
|
|
"flag " ++
|
|
|
|
config.name ++
|
|
|
|
" falsy pair must have at least short or long tags set",
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
args.short_tag = falsy_pair.short_tag;
|
|
|
|
args.long_tag = falsy_pair.long_tag;
|
|
|
|
args.flag_bias = .falsy;
|
|
|
|
|
2023-08-05 13:41:21 -07:00
|
|
|
self.param_spec.add(makeOption(generics, args));
|
2023-03-30 17:00:49 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if (config.env_var) |env_var| {
|
|
|
|
// @compileLog(env_var);
|
|
|
|
args.short_tag = null;
|
|
|
|
args.long_tag = null;
|
|
|
|
args.env_var = env_var;
|
|
|
|
args.flag_bias = .unbiased;
|
|
|
|
|
2023-08-05 13:41:21 -07:00
|
|
|
self.param_spec.add(makeOption(generics, args));
|
2023-03-30 17:00:49 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn generate(comptime self: @This()) self.param_spec.TupleType() {
|
|
|
|
return self.param_spec.realTuple();
|
|
|
|
}
|
|
|
|
|
2023-09-10 14:52:49 -07:00
|
|
|
pub fn noopCallback(comptime self: @This()) self.CallbackSignature() {
|
|
|
|
return struct {
|
|
|
|
fn callback(_: UserContextType, _: self.Output()) !void {}
|
|
|
|
}.callback;
|
|
|
|
}
|
|
|
|
|
2023-03-30 17:00:49 -07:00
|
|
|
pub fn CallbackSignature(comptime self: @This()) type {
|
2023-09-10 14:50:44 -07:00
|
|
|
return *const fn (UserContextType, self.Output()) anyerror!void;
|
2023-03-30 17:00:49 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn Output(comptime self: @This()) type {
|
|
|
|
comptime {
|
|
|
|
const spec = self.generate();
|
|
|
|
var fields: []const StructField = &[_]StructField{};
|
|
|
|
var flag_skip = 0;
|
|
|
|
|
|
|
|
var tag_fields: []const StructField = &[_]StructField{};
|
|
|
|
var env_var_fields: []const StructField = &[_]StructField{};
|
|
|
|
|
|
|
|
paramloop: for (spec, 0..) |param, idx| {
|
2023-04-03 01:36:45 -07:00
|
|
|
const PType = @TypeOf(param);
|
2023-03-30 17:00:49 -07:00
|
|
|
// 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
|
|
|
|
// subcommands because those are attached at runtime. also, only
|
|
|
|
// global tags and env_vars would conflict, which is less common.
|
|
|
|
if (param.short_tag) |short|
|
|
|
|
tag_fields = tag_fields ++ &[_]StructField{.{
|
2024-01-15 22:42:26 -08:00
|
|
|
// this goofy construct coerces the comptime []const u8 to
|
|
|
|
// [:0]const u8.
|
|
|
|
.name = short ++ "",
|
2023-03-30 17:00:49 -07:00
|
|
|
.type = void,
|
|
|
|
.default_value = null,
|
|
|
|
.is_comptime = false,
|
|
|
|
.alignment = 0,
|
|
|
|
}};
|
|
|
|
|
|
|
|
if (param.long_tag) |long|
|
|
|
|
tag_fields = tag_fields ++ &[_]StructField{.{
|
2024-01-15 22:42:26 -08:00
|
|
|
.name = long ++ "",
|
2023-03-30 17:00:49 -07:00
|
|
|
.type = void,
|
|
|
|
.default_value = null,
|
|
|
|
.is_comptime = false,
|
|
|
|
.alignment = 0,
|
|
|
|
}};
|
|
|
|
|
|
|
|
if (param.env_var) |env_var|
|
|
|
|
env_var_fields = env_var_fields ++ &[_]StructField{.{
|
2024-01-15 22:42:26 -08:00
|
|
|
.name = env_var ++ "",
|
2023-03-30 17:00:49 -07:00
|
|
|
.type = void,
|
|
|
|
.default_value = null,
|
|
|
|
.is_comptime = false,
|
|
|
|
.alignment = 0,
|
|
|
|
}};
|
|
|
|
|
2023-04-03 01:36:45 -07:00
|
|
|
if (!PType.has_output) continue :paramloop;
|
|
|
|
|
2023-03-30 17:00:49 -07:00
|
|
|
while (flag_skip > 0) {
|
|
|
|
flag_skip -= 1;
|
|
|
|
continue :paramloop;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (PType.is_flag) {
|
|
|
|
var peek = idx + 1;
|
2023-08-05 13:41:21 -07:00
|
|
|
var bias_seen: [ncmeta.enumLength(FlagBias)]bool = [_]bool{false} ** ncmeta.enumLength(FlagBias);
|
2023-07-19 00:31:19 -07:00
|
|
|
bias_seen[@intFromEnum(param.flag_bias)] = true;
|
2023-03-30 17:00:49 -07:00
|
|
|
while (peek < spec.len) : (peek += 1) {
|
|
|
|
const peek_param = spec[peek];
|
|
|
|
|
|
|
|
if (@TypeOf(peek_param).is_flag and std.mem.eql(u8, param.name, peek_param.name)) {
|
2023-07-19 00:31:19 -07:00
|
|
|
if (bias_seen[@intFromEnum(peek_param.flag_bias)] == true) {
|
2023-03-30 17:00:49 -07:00
|
|
|
@compileError("redundant flag!!!! " ++ param.name);
|
|
|
|
} else {
|
2023-07-19 00:31:19 -07:00
|
|
|
bias_seen[@intFromEnum(peek_param.flag_bias)] = true;
|
2023-03-30 17:00:49 -07:00
|
|
|
}
|
|
|
|
flag_skip += 1;
|
|
|
|
} else {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// the default field is already the optional type. Stripping
|
|
|
|
// the optional wrapper is an interesting idea for required
|
|
|
|
// fields. I do not foresee this greatly increasing complexity here.
|
|
|
|
const FieldType = if (param.required or param.default != null)
|
|
|
|
PType.G.ConvertedType()
|
|
|
|
else
|
|
|
|
?PType.G.ConvertedType();
|
|
|
|
|
|
|
|
const default = if (param.default) |def| &@as(FieldType, def) else @as(?*const anyopaque, null);
|
|
|
|
|
|
|
|
fields = fields ++ &[_]StructField{.{
|
2024-01-15 22:42:26 -08:00
|
|
|
.name = param.name ++ "",
|
2023-03-30 17:00:49 -07:00
|
|
|
.type = FieldType,
|
2023-08-04 00:15:29 -07:00
|
|
|
.default_value = @ptrCast(default),
|
2023-03-30 17:00:49 -07:00
|
|
|
.is_comptime = false,
|
|
|
|
.alignment = @alignOf(FieldType),
|
|
|
|
}};
|
|
|
|
}
|
|
|
|
|
|
|
|
_ = @Type(.{ .Struct = .{
|
|
|
|
.layout = .Auto,
|
|
|
|
.fields = tag_fields,
|
|
|
|
.decls = &.{},
|
|
|
|
.is_tuple = false,
|
|
|
|
} });
|
|
|
|
|
|
|
|
_ = @Type(.{ .Struct = .{
|
|
|
|
.layout = .Auto,
|
|
|
|
.fields = env_var_fields,
|
|
|
|
.decls = &.{},
|
|
|
|
.is_tuple = false,
|
|
|
|
} });
|
|
|
|
|
|
|
|
return @Type(.{ .Struct = .{
|
|
|
|
.layout = .Auto,
|
|
|
|
.fields = fields,
|
|
|
|
.decls = &.{},
|
|
|
|
.is_tuple = false,
|
|
|
|
} });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn Intermediate(comptime self: @This()) type {
|
|
|
|
comptime {
|
|
|
|
const spec = self.generate();
|
|
|
|
var fields: []const StructField = &[0]StructField{};
|
|
|
|
var flag_skip = 0;
|
|
|
|
|
|
|
|
paramloop: for (spec, 0..) |param, idx| {
|
|
|
|
while (flag_skip > 0) {
|
|
|
|
flag_skip -= 1;
|
|
|
|
continue :paramloop;
|
|
|
|
}
|
|
|
|
|
|
|
|
const PType = @TypeOf(param);
|
|
|
|
if (PType.is_flag) {
|
|
|
|
var peek = idx + 1;
|
2023-08-05 13:41:21 -07:00
|
|
|
var bias_seen: [ncmeta.enumLength(FlagBias)]bool = [_]bool{false} ** ncmeta.enumLength(FlagBias);
|
2023-07-19 00:31:19 -07:00
|
|
|
bias_seen[@intFromEnum(param.flag_bias)] = true;
|
2023-03-30 17:00:49 -07:00
|
|
|
while (peek < spec.len) : (peek += 1) {
|
|
|
|
const peek_param = spec[peek];
|
|
|
|
|
|
|
|
if (@TypeOf(peek_param).is_flag and std.mem.eql(u8, param.name, peek_param.name)) {
|
2023-07-19 00:31:19 -07:00
|
|
|
if (bias_seen[@intFromEnum(peek_param.flag_bias)] == true) {
|
2023-03-30 17:00:49 -07:00
|
|
|
@compileError("redundant flag!!!! " ++ param.name);
|
|
|
|
} else {
|
2023-07-19 00:31:19 -07:00
|
|
|
bias_seen[@intFromEnum(peek_param.flag_bias)] = true;
|
2023-03-30 17:00:49 -07:00
|
|
|
}
|
|
|
|
flag_skip += 1;
|
|
|
|
} else {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const FieldType = if (PType.value_count == .count)
|
|
|
|
PType.G.IntermediateType()
|
|
|
|
else
|
|
|
|
?PType.G.IntermediateType();
|
|
|
|
|
|
|
|
fields = &(@as([fields.len]StructField, fields[0..fields.len].*) ++ [1]StructField{.{
|
2024-01-15 22:42:26 -08:00
|
|
|
.name = param.name ++ "",
|
2023-03-30 17:00:49 -07:00
|
|
|
.type = FieldType,
|
2023-07-19 00:31:19 -07:00
|
|
|
.default_value = @ptrCast(&@as(
|
|
|
|
FieldType,
|
|
|
|
if (PType.value_count == .count) 0 else null,
|
|
|
|
)),
|
2023-03-30 17:00:49 -07:00
|
|
|
.is_comptime = false,
|
|
|
|
.alignment = @alignOf(?[]const u8),
|
|
|
|
}});
|
|
|
|
}
|
|
|
|
|
|
|
|
return @Type(.{ .Struct = .{
|
|
|
|
.layout = .Auto,
|
|
|
|
.fields = fields,
|
|
|
|
.decls = &.{},
|
|
|
|
.is_tuple = false,
|
|
|
|
} });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|