summaryrefslogtreecommitdiff
path: root/src/lua_tool.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/lua_tool.zig')
-rw-r--r--src/lua_tool.zig85
1 files changed, 81 insertions, 4 deletions
diff --git a/src/lua_tool.zig b/src/lua_tool.zig
index 3064d2b..21b3e51 100644
--- a/src/lua_tool.zig
+++ b/src/lua_tool.zig
@@ -25,6 +25,10 @@ pub const LuaTool = struct {
allocator: Allocator,
// Owned, NUL-terminated for `luaL_loadfilex`.
script_path_z: [:0]u8,
+ /// Optional directory to prepend to `package.path` so the script can
+ /// `require` sibling files. Owned, NUL-terminated. Null for single-file
+ /// extensions.
+ package_root_z: ?[:0]u8,
name_owned: []u8,
description_owned: []u8,
schema_owned: []u8,
@@ -39,16 +43,31 @@ pub const LuaTool = struct {
name: []const u8,
description: []const u8,
schema_json: []const u8,
+ package_root: ?[]const u8,
) !panto.Tool {
const self = try allocator.create(LuaTool);
errdefer allocator.destroy(self);
+ const script_path_z = try allocator.dupeZ(u8, script_path);
+ errdefer allocator.free(script_path_z);
+ const name_owned = try allocator.dupe(u8, name);
+ errdefer allocator.free(name_owned);
+ const description_owned = try allocator.dupe(u8, description);
+ errdefer allocator.free(description_owned);
+ const schema_owned = try allocator.dupe(u8, schema_json);
+ errdefer allocator.free(schema_owned);
+ const package_root_z: ?[:0]u8 = if (package_root) |p|
+ try allocator.dupeZ(u8, p)
+ else
+ null;
+
self.* = .{
.allocator = allocator,
- .script_path_z = try allocator.dupeZ(u8, script_path),
- .name_owned = try allocator.dupe(u8, name),
- .description_owned = try allocator.dupe(u8, description),
- .schema_owned = try allocator.dupe(u8, schema_json),
+ .script_path_z = script_path_z,
+ .package_root_z = package_root_z,
+ .name_owned = name_owned,
+ .description_owned = description_owned,
+ .schema_owned = schema_owned,
};
return panto.Tool{
@@ -63,6 +82,7 @@ pub const LuaTool = struct {
fn freeAll(self: *LuaTool) void {
const a = self.allocator;
a.free(self.script_path_z);
+ if (self.package_root_z) |p| a.free(p);
a.free(self.name_owned);
a.free(self.description_owned);
a.free(self.schema_owned);
@@ -102,6 +122,10 @@ fn runLuaHandler(
c.luaL_openlibs(L);
lua_bridge.install(L);
+ if (self.package_root_z) |root| {
+ try prependPackagePath(L, root);
+ }
+
// Run the script so register_tool fires.
lua_bridge.loadFile(L, self.script_path_z) catch |err| {
logTopAsError(L, "lua: failed to load extension");
@@ -176,6 +200,27 @@ pub fn loadExtension(
registry: *panto.ToolRegistry,
script_path: []const u8,
) !usize {
+ return loadExtensionImpl(allocator, registry, script_path, null);
+}
+
+/// Like `loadExtension`, but extends `package.path` with `<package_root>/?.lua`
+/// and `<package_root>/?/init.lua` so the script can `require` sibling files.
+/// `package_root` should be the directory containing the script.
+pub fn loadExtensionWithPackageRoot(
+ allocator: Allocator,
+ registry: *panto.ToolRegistry,
+ script_path: []const u8,
+ package_root: []const u8,
+) !usize {
+ return loadExtensionImpl(allocator, registry, script_path, package_root);
+}
+
+fn loadExtensionImpl(
+ allocator: Allocator,
+ registry: *panto.ToolRegistry,
+ script_path: []const u8,
+ package_root: ?[]const u8,
+) !usize {
const path_z = try allocator.dupeZ(u8, script_path);
defer allocator.free(path_z);
@@ -184,6 +229,13 @@ pub fn loadExtension(
c.luaL_openlibs(L);
lua_bridge.install(L);
+ // Configure require() for directory-style extensions before running.
+ if (package_root) |root| {
+ const root_z = try allocator.dupeZ(u8, root);
+ defer allocator.free(root_z);
+ try prependPackagePath(L, root_z);
+ }
+
lua_bridge.loadFile(L, path_z) catch |err| {
logTopAsError(L, "lua: failed to load extension");
return err;
@@ -200,6 +252,7 @@ pub fn loadExtension(
r.name,
r.description,
r.schema_json,
+ package_root,
);
// If registration fails (e.g. duplicate name), free the tool we just
// built and propagate the error.
@@ -211,6 +264,30 @@ pub fn loadExtension(
return regs.len;
}
+/// Prepend `<root>/?.lua` and `<root>/?/init.lua` to `package.path` so that
+/// `require("foo")` resolves against files next to the extension's
+/// `init.lua`. We prepend (not replace) so the standard library remains
+/// available.
+///
+/// Stack effect: net zero.
+fn prependPackagePath(L: *c.lua_State, root: [:0]const u8) !void {
+ // Run a tiny Lua snippet rather than fiddle with the stack manually:
+ // string concatenation is so much nicer in Lua.
+ const snippet =
+ \\local root = ...
+ \\package.path = root .. "/?.lua;" .. root .. "/?/init.lua;" .. package.path
+ ;
+ if (c.luaL_loadstring(L, snippet) != 0) {
+ logTopAsError(L, "lua: package.path loader failed to compile");
+ return error.LuaPackagePathLoadFailed;
+ }
+ _ = c.lua_pushlstring(L, root.ptr, root.len);
+ if (c.lua_pcallk(L, 1, 0, 0, 0, null) != 0) {
+ logTopAsError(L, "lua: package.path setup failed");
+ return error.LuaPackagePathSetupFailed;
+ }
+}
+
// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------