functioning subcommands
Fairly straightforward but only very lightly tested.
This commit is contained in:
parent
1ab4a113d2
commit
21af82acea
@ -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();
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user