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(), comptime callback: self.CallbackSignature(),
allocator: std.mem.Allocator, allocator: std.mem.Allocator,
) Parser(self, callback) { ) 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 const ParserInterface = struct {
// provide a field value conditionally. const Vtable = struct {
fn ParserInterfaceImpl(comptime Interface: type) type { execute: *const fn (parser: *anyopaque, context: *anyopaque) anyerror!void,
return struct { parse: *const fn (parser: *anyopaque, context: *anyopaque, args: [][:0]u8, env: std.process.EnvMap) anyerror!void,
pub fn execute(self: Interface) anyerror!void { finish: *const fn (parser: *anyopaque, context: *anyopaque) 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,
}; };
// we can actually bind the user context object in the interface, since parser: *anyopaque,
// it is exclusively parameterized around the context type. context: *anyopaque,
return if (@typeInfo(UserContext) == .Void) methods: *const Vtable,
struct {
ctx: *anyopaque,
context: *UserContext = @constCast(&void{}),
methods: *const InterfaceVtable,
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 { fn InterfaceGen(comptime ParserType: type, comptime UserContext: type) type {
return if (@typeInfo(UserContext) == .Void) struct { return if (@typeInfo(UserContext) == .Void) struct {
pub fn interface(self: *ParserType) ParserInterface(UserContext) { pub fn interface(self: *ParserType) ParserInterface {
return .{ .ctx = self, .methods = &.{ .execute = ParserType.wrap_execute } }; return .{
.parser = self,
.context = @constCast(&void{}),
.methods = &.{
.execute = ParserType.wrap_execute,
.parse = ParserType.wrap_parse,
.finish = ParserType.wrap_finish,
},
};
} }
} else struct { } else struct {
pub fn interface(self: *ParserType, context: *UserContext) ParserInterface(UserContext) { pub fn interface(self: *ParserType, context: *UserContext) ParserInterface {
return .{ .ctx = self, .context = context, .methods = &.{ .execute = ParserType.wrap_execute } }; 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, progname: ?[]const u8 = null,
has_global_tags: bool = false, has_global_tags: bool = false,
allocator: std.mem.Allocator, 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 { pub fn add_subcommand(self: *@This(), verb: []const u8, parser: ParserInterface) !void {
// self.subcommands try self.subcommands.put(verb, parser);
// } }
// This is a slightly annoying hack to work around the fact that there's no way to // This is a slightly annoying hack to work around the fact that there's no way to
// provide a method signature conditionally. // provide a method signature conditionally.
pub usingnamespace InterfaceGen(@This(), UserContext); pub usingnamespace InterfaceGen(@This(), UserContext);
fn wrap_execute(ctx: *anyopaque, context: *UserContext) anyerror!void { fn wrap_execute(parser: *anyopaque, ctx: *anyopaque) anyerror!void {
const self = @ptrCast(*@This(), @alignCast(@alignOf(@This()), ctx)); 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); 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 { pub fn execute(self: *@This(), context: *UserContext) anyerror!void {
const args = try std.process.argsAlloc(self.allocator); const args = try std.process.argsAlloc(self.allocator);
defer std.process.argsFree(self.allocator, args); 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; if (args.len < 1) return ParseError.EmptyArgs;
self.progname = args[0]; 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| { try self.subparse(context, args[1..], env);
// @compileLog(@typeName(field.type)); try self.finish(context);
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).?, "");
}
}
} }
fn print_value(self: @This(), value: anytype, comptime indent: []const u8) void { 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( pub fn parse(
self: *@This(), self: *@This(),
args: [][:0]u8, args: [][:0]u8,
) anyerror!void { ) anyerror!usize {
// run pre-parse pass if we have any global parameters // run pre-parse pass if we have any global parameters
// try self.preparse() // try self.preparse()
@ -830,8 +876,14 @@ fn Parser(comptime command: anytype, comptime callback: anytype) type {
forced_ordinal = true; 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( inline fn parse_long_tag(
@ -888,9 +940,7 @@ fn Parser(comptime command: anytype, comptime callback: anytype) type {
self: *@This(), self: *@This(),
arg: []const u8, arg: []const u8,
argit: *ncmeta.SliceIterator([][:0]u8), argit: *ncmeta.SliceIterator([][:0]u8),
) ParseError!void { ) ParseError!?ParserInterface {
_ = arg;
comptime var arg_index: u32 = 0; comptime var arg_index: u32 = 0;
inline for (comptime parameters) |param| { inline for (comptime parameters) |param| {
comptime if (@TypeOf(param).param_type != .Ordinal) continue; 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); try self.apply_param_values(param, argit, false);
} }
self.consumed_args += 1; self.consumed_args += 1;
return; return null;
} }
arg_index += 1; arg_index += 1;
} }
// look for subcommands now return self.subcommands.get(arg) orelse ParseError.ExtraValue;
} }
inline fn push_intermediate_value( inline fn push_intermediate_value(
@ -1055,18 +1105,34 @@ const cli = cmd: {
.truthy = .{ .short_tag = "-M" }, .truthy = .{ .short_tag = "-M" },
.env_var = "NOCLIP_MULTIFLAG", .env_var = "NOCLIP_MULTIFLAG",
}); });
cmd.add_argument(.{ .OutputType = []const u8, .multi = true }, .{ cmd.add_argument(.{ .OutputType = []const u8 }, .{
.name = "arg", .name = "arg",
}); });
break :cmd cmd; 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 { fn cli_handler(context: *u32, result: cli.Output()) !void {
_ = context; _ = context;
std.debug.print("callback is working {any}\n", .{result.multi.?.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.multiflag.?.items});
std.debug.print("callback is working {any}\n", .{result.choice}); 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 parser = cli.bind(cli_handler, allocator);
var context: u32 = 2; var context: u32 = 2;
var subcon = subcommand.bind(sub_handler, allocator);
try parser.add_subcommand("verb", subcon.interface());
const iface = parser.interface(&context); const iface = parser.interface(&context);
try iface.execute(); try iface.execute();
} }