Something went wrong. Try again.
🦦 a tiny cute lua web framework
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276const std = @import("std");const Io = std.Io;
const fern = @import("fern");const options = @import("options");const zlua = @import("zlua");pub const Lua = zlua.Lua;
const maivi = @import("../root.zig");const helpers = maivi.helpers;pub const read_data = @import("./data.zig").read;const embed = @import("embed.zig");const lua_display = @import("lua_display.zig");const lua_md4c = if (options.md4c) @import("lua_md4c.zig");const lua_print = @import("lua_print.zig").lua_print;const lua_read = @import("lua_read.zig").lua_read;const lua_str = @import("lua_str.zig");pub const pcall = @import("pcall.zig").pcall;
pub var lua: *Lua = undefined;
pub fn init() !void { fern.trace().ctx("lua.init").log();
lua = try lua_init();}
pub fn thread() *Lua { return lua.newThread();}
pub fn lua_init() !*Lua { const l = try Lua.init(maivi.allocator); errdefer l.deinit(); l.openBase(); l.openOS(); l.openDebug(); l.openString(); l.openTable(); l.openMath(); l.openBit32();
// package ================================================================
l.openPackage();
_ = l.getGlobal("package");
{ const path = try std.fmt.allocPrint(maivi.allocator, "{s}/?.lua;{s}/?/init.lua", .{ maivi.config.luaroot, maivi.config.luaroot }); defer maivi.allocator.free(path); _ = l.pushString(path); l.setField(-2, "path"); }
if (options.runtime) { const path = try std.fmt.allocPrint(maivi.allocator, "{s}/?.lua;{s}/?/init.lua", .{ maivi.config.runtime, maivi.config.runtime }); defer maivi.allocator.free(path);
_ = l.getField(-1, "path"); _ = l.pushString(";"); _ = l.pushString(path); _ = l.concat(3); l.setField(-2, "path"); }
l.pop(1); // pops "package"
// global fn ==============================================================
l.register("print", zlua.wrap(lua_print)); l.register("read", zlua.wrap(lua_read));
// maivi ======================================================================
if (options.runtime) try do_require(l, "maivi") else try do_buffer(l, embed.maivi_module, "@runtime/maivi.lua"); l.setGlobal("maivi");
_ = l.getGlobal("maivi");
_ = try lua_str.lua_openstr(l); l.setField(-2, "str");
_ = try lua_display.lua_opendisplay(l); l.setField(-2, "display");
try do_buffer(l, embed.inspect_module, "@inspect_lua"); l.setField(-2, "inspect");
_ = try maivi.Router.lua_openrouter(l); l.setField(-2, "router");
l.pop(1); // pops "maivi"
// h ==========================================================================
if (options.runtime) try do_require(l, "h") else try do_buffer(l, embed.h_module, "@runtime/h.lua"); l.setGlobal("h");
// this =======================================================================
if (options.runtime) try do_require(l, "this") else try do_buffer(l, embed.this_module, "@runtime/this.lua");
_ = l.pushString(maivi.config.base_url); l.setField(-2, "base_url"); _ = l.pushString(maivi.rev orelse "dev"); l.setField(-2, "rev"); _ = l.pushBoolean(maivi.dev); l.setField(-2, "dev");
l.setGlobal("this");
// iter =======================================================================
if (options.runtime) try do_require(l, "iter") else try do_buffer(l, embed.iter_module, "@runtime/iter.lua"); l.setGlobal("Iter");
// preload ================================================================
if (options.md4c) { _ = l.getGlobal("package"); _ = l.getField(-1, "loaded"); defer l.pop(2); // pops loaded, package _ = try lua_md4c.lua_openmd4c(l); l.setField(-2, "md4c"); }
if (l.getTop() != 0) return error.UnexpectedStackSize;
return l;}
pub fn deinit() void { lua.deinit();}
/// * Pops from Stack: `0`/// * Pushes to Stack: `1`fn do_buffer(l: *Lua, buf: [:0]const u8, chunkname: [:0]const u8) !void { try l.loadBuffer(buf, chunkname); try pcall(l, .{ .results = 1 });}
/// * Pops from Stack: `0`/// * Pushes to Stack: `1`fn do_require(l: *Lua, modname: []const u8) !void { _ = l.getGlobal("require"); _ = l.pushString(modname); try pcall(l, .{ .args = 1, .results = 1 });}
pub fn setup(io: Io) !void { fern.info().ctx("lua.setup").msg("loading plugins").log(); // load plugins blk: { const dir = try Io.Dir.openDirAbsolute(io, maivi.config.root, .{}); defer dir.close(io); const plugindir = dir.openDir(io, "plugins", .{ .iterate = true }) catch |err| switch (err) { error.FileNotFound => break :blk, else => return err, }; defer plugindir.close(io); var it = plugindir.iterate(); while (try it.next(io)) |entry| { if (entry.kind != .file) continue; if (!std.mem.endsWith(u8, entry.name, ".lua")) continue; const contents = try helpers.read_file(io, maivi.allocator, plugindir, entry.name); defer maivi.allocator.free(contents); const filename = try maivi.allocator.dupeZ(u8, entry.name); defer maivi.allocator.free(filename); try lua.loadBuffer(contents, filename); try pcall(lua, .{}); } }
fern.info().ctx("lua.setup").msg("running lua init module").log(); try do_require(lua, "init"); lua.pop(1);}
pub fn toRef(l: *Lua, i: ?i32) i32 { if (i) |idx| l.pushValue(idx); return l.ref(zlua.registry_index);}
pub fn setMethodTable(l: *Lua, comptime T: type) !void { const type_info = @typeInfo(T); comptime { if (type_info != .@"struct") @compileError("non-struct type passed as method table"); const fields = type_info.@"struct".fields; if (fields.len > 0) @compileError("method table cannot contain fields"); }
l.createTable(0, 1);
{ const decls = type_info.@"struct".decls; l.createTable(0, decls.len); inline for (decls) |field| { try l.pushAny(field.name); l.pushFunction(zlua.wrap(@field(T, field.name))); l.setTable(-3); } l.setField(-2, "__index"); }
l.setMetatable(-2);}
pub fn pushCtx(l: *Lua, comptime T: type, ctx: *T) !void { { const stack_size_on_entry = l.getTop();
// ctx
const useref = @hasField(T, "ref") and @FieldType(T, "ref") == ?i32;
if (useref and ctx.ref != null) { fern.debug().msg("reusing ctx ref").str("name", @typeName(T)).log(); _ = l.getIndexRaw(zlua.registry_index, ctx.ref.?); } else try l.pushAny(ctx.*);
if (useref and ctx.ref == null) { ctx.ref = maivi.lua.toRef(l, -1); }
if (l.getTop() != stack_size_on_entry + 1) return error.UnexpectedStackSize; }
l.pushLightUserdata(@ptrCast(ctx)); l.setField(-2, "ptr");}
pub const Event = enum { handle, read_data,
pub fn str(self: @This()) []const u8 { return switch (self) { else => @tagName(self), }; }};
pub fn trigger(l: *Lua, comptime T: type, ctx: *T, event: Event, props: anytype) !void { // trigger
_ = l.getGlobal("maivi"); defer l.pop(1); _ = l.getField(-1, "trigger"); // -1 => f, -2 => maivi errdefer l.pop(2);
try pushCtx(l, T, ctx); // -1 => ctx, -2 => f, -3 => maivi
const hook_str = event.str(); _ = l.pushString(hook_str); try l.pushAny(props);
try pcall(l, .{ .args = 3 });}