functioning subcommands

Fairly straightforward but only very lightly tested.
This commit is contained in:
torque 2023-03-30 00:19:00 -07:00
parent 1ab4a113d2
commit 21af82acea
Signed by: torque
SSH Key Fingerprint: SHA256:nCrXefBNo6EbjNSQhv0nXmEg/VuNq3sMF5b8zETw3Tk

View File

@ -653,54 +653,62 @@ fn CommandBuilder(comptime UserContext: type) type {
comptime callback: self.CallbackSignature(),
allocator: std.mem.Allocator,
) Parser(self, callback) {
return Parser(self, callback){ .allocator = allocator };
return Parser(self, callback){
.allocator = allocator,
.subcommands = std.hash_map.StringHashMap(ParserInterface).init(allocator),
};
}
};
}
// This is a slightly annoying hack to work around the fact that there's no way to
// provide a field value conditionally.
fn ParserInterfaceImpl(comptime Interface: type) type {
return struct {
pub fn execute(self: Interface) anyerror!void {
return try self.methods.execute(self.ctx, self.context);
}
};
}
fn ParserInterface(comptime UserContext: type) type {
const InterfaceVtable = struct {
execute: *const fn (ctx: *anyopaque, context: *UserContext) anyerror!void,
const ParserInterface = struct {
const Vtable = struct {
execute: *const fn (parser: *anyopaque, context: *anyopaque) anyerror!void,
parse: *const fn (parser: *anyopaque, context: *anyopaque, args: [][:0]u8, env: std.process.EnvMap) anyerror!void,
finish: *const fn (parser: *anyopaque, context: *anyopaque) anyerror!void,
};
// we can actually bind the user context object in the interface, since
// it is exclusively parameterized around the context type.
return if (@typeInfo(UserContext) == .Void)
struct {
ctx: *anyopaque,
context: *UserContext = @constCast(&void{}),
methods: *const InterfaceVtable,
parser: *anyopaque,
context: *anyopaque,
methods: *const Vtable,
pub usingnamespace ParserInterfaceImpl(@This());
pub fn execute(self: @This()) anyerror!void {
return try self.methods.execute(self.parser, self.context);
}
else
struct {
ctx: *anyopaque,
context: *UserContext,
methods: *const InterfaceVtable,
pub usingnamespace ParserInterfaceImpl(@This());
pub fn parse(self: @This(), args: [][:0]u8, env: std.process.EnvMap) anyerror!void {
return try self.methods.parse(self.parser, self.context, args, env);
}
pub fn finish(self: @This()) anyerror!void {
return try self.methods.finish(self.parser, self.context);
}
};
}
fn InterfaceGen(comptime ParserType: type, comptime UserContext: type) type {
return if (@typeInfo(UserContext) == .Void) struct {
pub fn interface(self: *ParserType) ParserInterface(UserContext) {
return .{ .ctx = self, .methods = &.{ .execute = ParserType.wrap_execute } };
pub fn interface(self: *ParserType) ParserInterface {
return .{
.parser = self,
.context = @constCast(&void{}),
.methods = &.{
.execute = ParserType.wrap_execute,
.parse = ParserType.wrap_parse,
.finish = ParserType.wrap_finish,
},
};
}
} else struct {
pub fn interface(self: *ParserType, context: *UserContext) ParserInterface(UserContext) {
return .{ .ctx = self, .context = context, .methods = &.{ .execute = ParserType.wrap_execute } };
pub fn interface(self: *ParserType, context: *UserContext) ParserInterface {
return .{
.parser = self,
.context = context,
.methods = &.{
.execute = ParserType.wrap_execute,
.parse = ParserType.wrap_parse,
.finish = ParserType.wrap_finish,
},
};
}
};
}
@ -720,20 +728,69 @@ fn Parser(comptime command: anytype, comptime callback: anytype) type {
progname: ?[]const u8 = null,
has_global_tags: bool = false,
allocator: std.mem.Allocator,
subcommands: std.hash_map.StringHashMap(ParserInterface),
subcommand: ?ParserInterface = null,
// pub fn add_subcommand(self: *@This(), verb: []const u8, parser: anytype) void {
// self.subcommands
// }
pub fn add_subcommand(self: *@This(), verb: []const u8, parser: ParserInterface) !void {
try self.subcommands.put(verb, parser);
}
// This is a slightly annoying hack to work around the fact that there's no way to
// provide a method signature conditionally.
pub usingnamespace InterfaceGen(@This(), UserContext);
fn wrap_execute(ctx: *anyopaque, context: *UserContext) anyerror!void {
const self = @ptrCast(*@This(), @alignCast(@alignOf(@This()), ctx));
fn wrap_execute(parser: *anyopaque, ctx: *anyopaque) anyerror!void {
const self = @ptrCast(*@This(), @alignCast(@alignOf(*@This()), parser));
// this is a slightly annoying hack to work around the problem that void has
// 0 alignment, which alignCast chokes on.
const context = if (@alignOf(UserContext) > 0)
@ptrCast(*UserContext, @alignCast(@alignOf(UserContext), ctx))
else
@ptrCast(*UserContext, ctx);
return try self.execute(context);
}
fn wrap_parse(parser: *anyopaque, ctx: *anyopaque, args: [][:0]u8, env: std.process.EnvMap) anyerror!void {
const self = @ptrCast(*@This(), @alignCast(@alignOf(@This()), parser));
const context = if (@alignOf(UserContext) > 0)
@ptrCast(*UserContext, @alignCast(@alignOf(UserContext), ctx))
else
@ptrCast(*UserContext, ctx);
return try self.subparse(context, args, env);
}
fn wrap_finish(parser: *anyopaque, ctx: *anyopaque) anyerror!void {
const self = @ptrCast(*@This(), @alignCast(@alignOf(@This()), parser));
const context = if (@alignOf(UserContext) > 0)
@ptrCast(*UserContext, @alignCast(@alignOf(UserContext), ctx))
else
@ptrCast(*UserContext, ctx);
return try self.finish(context);
}
pub fn subparse(self: *@This(), context: *UserContext, args: [][:0]u8, env: std.process.EnvMap) anyerror!void {
const sliceto = try self.parse(args);
try self.read_environment(env);
try self.convert(context);
inline for (@typeInfo(@TypeOf(self.intermediate)).Struct.fields) |field| {
if (@field(self.intermediate, field.name) == null) {
std.debug.print("{s}: null,\n", .{field.name});
} else {
std.debug.print("{s}: ", .{field.name});
self.print_value(@field(self.intermediate, field.name).?, "");
}
}
if (self.subcommand) |verb| try verb.parse(args[sliceto..], env);
}
pub fn finish(self: *@This(), context: *UserContext) anyerror!void {
try callback(context, self.output);
if (self.subcommand) |verb| try verb.finish();
}
pub fn execute(self: *@This(), context: *UserContext) anyerror!void {
const args = try std.process.argsAlloc(self.allocator);
defer std.process.argsFree(self.allocator, args);
@ -743,20 +800,9 @@ fn Parser(comptime command: anytype, comptime callback: anytype) type {
if (args.len < 1) return ParseError.EmptyArgs;
self.progname = args[0];
try self.parse(args[1..]);
try self.read_environment(env);
try self.convert(context);
try callback(context, self.output);
inline for (@typeInfo(@TypeOf(self.intermediate)).Struct.fields) |field| {
// @compileLog(@typeName(field.type));
if (@field(self.intermediate, field.name) == null) {
std.debug.print("{s}: null,\n", .{field.name});
} else {
std.debug.print("{s}: ", .{field.name});
self.print_value(@field(self.intermediate, field.name).?, "");
}
}
try self.subparse(context, args[1..], env);
try self.finish(context);
}
fn print_value(self: @This(), value: anytype, comptime indent: []const u8) void {
@ -774,7 +820,7 @@ fn Parser(comptime command: anytype, comptime callback: anytype) type {
pub fn parse(
self: *@This(),
args: [][:0]u8,
) anyerror!void {
) anyerror!usize {
// run pre-parse pass if we have any global parameters
// try self.preparse()
@ -830,10 +876,16 @@ fn Parser(comptime command: anytype, comptime callback: anytype) type {
forced_ordinal = true;
}
try self.parse_ordinals(arg, &argit);
if (try self.parse_ordinals(arg, &argit)) |verb| {
self.subcommand = verb;
// TODO: return slice of remaining or offset index
return argit.index;
}
}
return 0;
}
inline fn parse_long_tag(
self: *@This(),
arg: []const u8,
@ -888,9 +940,7 @@ fn Parser(comptime command: anytype, comptime callback: anytype) type {
self: *@This(),
arg: []const u8,
argit: *ncmeta.SliceIterator([][:0]u8),
) ParseError!void {
_ = arg;
) ParseError!?ParserInterface {
comptime var arg_index: u32 = 0;
inline for (comptime parameters) |param| {
comptime if (@TypeOf(param).param_type != .Ordinal) continue;
@ -903,13 +953,13 @@ fn Parser(comptime command: anytype, comptime callback: anytype) type {
try self.apply_param_values(param, argit, false);
}
self.consumed_args += 1;
return;
return null;
}
arg_index += 1;
}
// look for subcommands now
return self.subcommands.get(arg) orelse ParseError.ExtraValue;
}
inline fn push_intermediate_value(
@ -1055,18 +1105,34 @@ const cli = cmd: {
.truthy = .{ .short_tag = "-M" },
.env_var = "NOCLIP_MULTIFLAG",
});
cmd.add_argument(.{ .OutputType = []const u8, .multi = true }, .{
cmd.add_argument(.{ .OutputType = []const u8 }, .{
.name = "arg",
});
break :cmd cmd;
};
const subcommand = cmd: {
var cmd = command_builder(void);
cmd.add_flag(.{}, .{
.name = "flag",
.truthy = .{ .short_tag = "-f", .long_tag = "--flag" },
.falsy = .{ .long_tag = "--no-flag" },
.env_var = "NOCLIP_SUBFLAG",
});
cmd.add_argument(.{ .OutputType = []const u8 }, .{ .name = "argument" });
break :cmd cmd;
};
fn sub_handler(_: *void, result: subcommand.Output()) !void {
std.debug.print("subcommand: {s}\n", .{result.argument});
}
fn cli_handler(context: *u32, result: cli.Output()) !void {
_ = context;
std.debug.print("callback is working {any}\n", .{result.multi.?.items});
std.debug.print("callback is working {any}\n", .{result.multiflag.?.items});
// std.debug.print("callback is working {any}\n", .{result.multi.?.items});
// std.debug.print("callback is working {any}\n", .{result.multiflag.?.items});
std.debug.print("callback is working {any}\n", .{result.choice});
}
@ -1077,6 +1143,10 @@ pub fn main() !void {
var parser = cli.bind(cli_handler, allocator);
var context: u32 = 2;
var subcon = subcommand.bind(sub_handler, allocator);
try parser.add_subcommand("verb", subcon.interface());
const iface = parser.interface(&context);
try iface.execute();
}