refactor: move command and EventHandler out of the tui module
This commit is contained in:
parent
f41fb97d02
commit
16c5471126
45 changed files with 132 additions and 81 deletions
237
src/command.zig
Normal file
237
src/command.zig
Normal file
|
@ -0,0 +1,237 @@
|
|||
const std = @import("std");
|
||||
const tp = @import("thespian");
|
||||
const log = @import("log");
|
||||
|
||||
pub var context_check: ?*const fn() void = null;
|
||||
|
||||
pub const ID = usize;
|
||||
pub const ID_unknown = std.math.maxInt(ID);
|
||||
|
||||
pub const Result = anyerror!void;
|
||||
pub const Context = struct {
|
||||
args: tp.message = .{},
|
||||
|
||||
pub fn fmt(value: anytype) Context {
|
||||
return .{ .args = tp.message.fmtbuf(&context_buffer, value) catch @panic("command.Context.fmt failed") };
|
||||
}
|
||||
};
|
||||
threadlocal var context_buffer: [tp.max_message_size]u8 = undefined;
|
||||
pub const fmt = Context.fmt;
|
||||
|
||||
const Vtable = struct {
|
||||
id: ID = ID_unknown,
|
||||
name: []const u8,
|
||||
run: *const fn (self: *Vtable, ctx: Context) tp.result,
|
||||
meta: Metadata,
|
||||
};
|
||||
|
||||
const Metadata = struct {
|
||||
description: []const u8 = &[_]u8{},
|
||||
interactive: bool = true,
|
||||
};
|
||||
|
||||
pub fn Closure(comptime T: type) type {
|
||||
return struct {
|
||||
vtbl: Vtable,
|
||||
f: FunT,
|
||||
data: T,
|
||||
|
||||
const FunT: type = *const fn (T, ctx: Context) Result;
|
||||
const Self = @This();
|
||||
|
||||
pub fn init(f: FunT, data: T, name: []const u8, meta: Metadata) Self {
|
||||
return .{
|
||||
.vtbl = .{
|
||||
.run = run,
|
||||
.name = name,
|
||||
.meta = meta,
|
||||
},
|
||||
.f = f,
|
||||
.data = data,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn register(self: *Self) !void {
|
||||
if (command_names.get(self.vtbl.name)) |id| {
|
||||
self.vtbl.id = id;
|
||||
reAddCommand(&self.vtbl) catch |e| return log.err("cmd", "reAddCommand", e);
|
||||
// log.print("cmd", "reAddCommand({s}) => {d}", .{ self.vtbl.name, self.vtbl.id });
|
||||
} else {
|
||||
self.vtbl.id = try addCommand(&self.vtbl);
|
||||
command_names.put(self.vtbl.name, self.vtbl.id) catch |e| return log.err("cmd", "addCommand", e);
|
||||
// log.print("cmd", "addCommand({s}) => {d}", .{ self.vtbl.name, self.vtbl.id });
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unregister(self: *Self) void {
|
||||
removeCommand(self.vtbl.id);
|
||||
}
|
||||
|
||||
fn run(vtbl: *Vtable, ctx: Context) tp.result {
|
||||
const self: *Self = fromVtable(vtbl);
|
||||
return self.f(self.data, ctx) catch |e| tp.exit_error(e, @errorReturnTrace());
|
||||
}
|
||||
|
||||
fn fromVtable(vtbl: *Vtable) *Self {
|
||||
return @fieldParentPtr("vtbl", vtbl);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const CommandTable = std.ArrayList(?*Vtable);
|
||||
pub var commands: CommandTable = CommandTable.init(command_table_allocator);
|
||||
var command_names: std.StringHashMap(ID) = std.StringHashMap(ID).init(command_table_allocator);
|
||||
const command_table_allocator = std.heap.c_allocator;
|
||||
|
||||
fn addCommand(cmd: *Vtable) !ID {
|
||||
try commands.append(cmd);
|
||||
return commands.items.len - 1;
|
||||
}
|
||||
|
||||
fn reAddCommand(cmd: *Vtable) !void {
|
||||
if (commands.items[cmd.id] != null) return error.DuplicateCommand;
|
||||
commands.items[cmd.id] = cmd;
|
||||
}
|
||||
|
||||
pub fn removeCommand(id: ID) void {
|
||||
commands.items[id] = null;
|
||||
}
|
||||
|
||||
pub fn execute(id: ID, ctx: Context) tp.result {
|
||||
if(context_check) |check| check();
|
||||
if (id >= commands.items.len)
|
||||
return tp.exit_fmt("CommandNotFound: {d}", .{id});
|
||||
const cmd = commands.items[id];
|
||||
if (cmd) |p| {
|
||||
// var buf: [tp.max_message_size]u8 = undefined;
|
||||
// log.print("cmd", "execute({s}) {s}", .{ p.name, ctx.args.to_json(&buf) catch "" }) catch |e| return tp.exit_error(e, @errorReturnTrace());
|
||||
return p.run(p, ctx);
|
||||
} else {
|
||||
return tp.exit_fmt("CommandNotAvailable: {d}", .{id});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn getId(name: []const u8) ?ID {
|
||||
for (commands.items) |cmd| {
|
||||
if (cmd) |p|
|
||||
if (std.mem.eql(u8, p.name, name))
|
||||
return p.id;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn get_id_cache(name: []const u8, id: *?ID) ?ID {
|
||||
for (commands.items) |cmd| {
|
||||
if (cmd) |p|
|
||||
if (std.mem.eql(u8, p.name, name)) {
|
||||
id.* = p.id;
|
||||
return p.id;
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const suppressed_errors = .{
|
||||
"enable_fast_scroll",
|
||||
"disable_fast_scroll",
|
||||
"clear_diagnostics",
|
||||
};
|
||||
|
||||
pub fn executeName(name: []const u8, ctx: Context) tp.result {
|
||||
const id = getId(name);
|
||||
if (id) |id_| return execute(id_, ctx);
|
||||
inline for (suppressed_errors) |err| if (std.mem.eql(u8, err, name)) return;
|
||||
return tp.exit_fmt("CommandNotFound: {s}", .{name});
|
||||
}
|
||||
|
||||
fn CmdDef(comptime T: type) type {
|
||||
return struct {
|
||||
const Fn = fn (T, Context) anyerror!void;
|
||||
name: [:0]const u8,
|
||||
f: *const Fn,
|
||||
meta: Metadata,
|
||||
};
|
||||
}
|
||||
|
||||
fn getTargetType(comptime Namespace: type) type {
|
||||
return @field(Namespace, "Target");
|
||||
}
|
||||
|
||||
fn getCommands(comptime Namespace: type) []const CmdDef(*getTargetType(Namespace)) {
|
||||
@setEvalBranchQuota(10_000);
|
||||
comptime switch (@typeInfo(Namespace)) {
|
||||
.Struct => |info| {
|
||||
var count = 0;
|
||||
const Target = getTargetType(Namespace);
|
||||
// @compileLog(Namespace, Target);
|
||||
for (info.decls) |decl| {
|
||||
// @compileLog(decl.name, @TypeOf(@field(Namespace, decl.name)));
|
||||
if (@TypeOf(@field(Namespace, decl.name)) == CmdDef(*Target).Fn)
|
||||
count += 1;
|
||||
}
|
||||
var cmds: [count]CmdDef(*Target) = undefined;
|
||||
var i = 0;
|
||||
for (info.decls) |decl| {
|
||||
if (@TypeOf(@field(Namespace, decl.name)) == CmdDef(*Target).Fn) {
|
||||
cmds[i] = .{
|
||||
.f = &@field(Namespace, decl.name),
|
||||
.name = decl.name,
|
||||
.meta = if (@hasDecl(Namespace, decl.name ++ "_meta"))
|
||||
@field(Namespace, decl.name ++ "_meta")
|
||||
else
|
||||
@compileError(decl.name ++ " has no meta"),
|
||||
};
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
const cmds_const = cmds;
|
||||
return &cmds_const;
|
||||
},
|
||||
else => @compileError("expected tuple or struct type"),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn Collection(comptime Namespace: type) type {
|
||||
const cmds = comptime getCommands(Namespace);
|
||||
const Target = getTargetType(Namespace);
|
||||
const Clsr = Closure(*Target);
|
||||
var fields_var: [cmds.len]std.builtin.Type.StructField = undefined;
|
||||
inline for (cmds, 0..) |cmd, i| {
|
||||
@setEvalBranchQuota(10_000);
|
||||
fields_var[i] = .{
|
||||
.name = cmd.name,
|
||||
.type = Clsr,
|
||||
.default_value = null,
|
||||
.is_comptime = false,
|
||||
.alignment = if (@sizeOf(Clsr) > 0) @alignOf(Clsr) else 0,
|
||||
};
|
||||
}
|
||||
const fields: [cmds.len]std.builtin.Type.StructField = fields_var;
|
||||
const Fields = @Type(.{
|
||||
.Struct = .{
|
||||
.is_tuple = false,
|
||||
.layout = .auto,
|
||||
.decls = &.{},
|
||||
.fields = &fields,
|
||||
},
|
||||
});
|
||||
return struct {
|
||||
fields: Fields,
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub fn init(self: *Self, targetPtr: *Target) !void {
|
||||
if (cmds.len == 0)
|
||||
@compileError("no commands found in type " ++ @typeName(Target) ++ " (did you mark them public?)");
|
||||
inline for (cmds) |cmd| {
|
||||
@field(self.fields, cmd.name) = Closure(*Target).init(cmd.f, targetPtr, cmd.name, cmd.meta);
|
||||
try @field(self.fields, cmd.name).register();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self) void {
|
||||
inline for (cmds) |cmd|
|
||||
Closure(*Target).unregister(&@field(self.fields, cmd.name));
|
||||
}
|
||||
};
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue