Reputation: 30862
tl;dr: I want to create lua packages with a custom directory name pattern, having problem with search paths.
The problem
I've got an application that I'm wanted to allow the user to write plugins for, following a similar model to Lightroom:
<app data>/plugins/<name>.myplugin
<name>.myplugin
is a directory bundle which may contain a set of scripts, binaries or other resourcesinfo.lua
file that is read by the appThe problem I'm grappling with is how best to wrap the plugins as packages (modules + submodules) or regular scripts. I envisage that a plugin might include 3rd party modules:
Foo.myplugin/
info.lua - returns a table with plugin name, version info, list of exported functions, etc
Foo.lua - defines the main functions exported by this plugin, which calls other scripts:
UsefulFunctions.lua - used by Foo.lua
3rdparty/3rdparty.lua - 3rd party module
If I set the package search path, package.path
to include
<appdata>/?.myplugin/?.lua
then I can load the package with Foo=require 'Foo'
. However, I can't work out how to get submodules loaded. If Foo.lua
calls UsefulFunctions=require 'UsefulFunctions'
then this load fails because lua's search path tries to look for UsefulFunctions.myplugin/UsefulFunctions.lua
. I can't load it with require 'Foo.UsefulFunctions'
either, for similar reasons.
Some options:
.myplugin
suffix which would simplify the package search path. Is there any way of providing the functionality I need?
I'm currently on Lua 5.1. I know 5.2 has more control over package search paths, but I don't think I have the option of updating to it at the moment. I'm also using luabind, though I don't think it is relevant to this.
Upvotes: 2
Views: 3434
Reputation: 7005
You could customize the way Lua searches for modules using a custom searcher function, using the mechanisms outlined in the documentation of require
and package.loaders
.
The trick is to detect that the module can be found in a directory with the .myplugins
suffix and to keep track of the path of the bundles. Consider the following scripts.
-- <appdata>/plugins/foo.myplugin/foo.lua
local auxlib = require 'foo.auxlib'
local M = {}
function M.Foobnicator()
print "Called: Foobnicator!!"
auxlib.AuxFunction()
end
return M
-- <appdata>/plugins/foo.myplugin/auxlib.lua
local M = {}
function M.AuxFunction()
print "Called: AuxFunction!!"
end
return M
-- main.lua
package.path = package.path .. ";"
.. [[<appdata>/plugins/?.myplugin/?.lua]]
local bundles = {} -- holds bundle names and pathnames
local function custom_searcher( module_name )
if string.match( module_name, '%.' ) then
-- module name has a dot in it - it is a submodule,
-- let's check if it is inside a bundle
local main_module_name, subname =
string.match( module_name, '^([^.]-)%.(.+)' )
local main_path = bundles[ main_module_name ]
if main_path then -- OK, it's a submodule of a known bundle
local sub_fname = string.gsub( subname, '%.', '/' )
-- replace main module filename with that of submodule
local path = string.match( main_path, '^.*[/\\]' )
.. sub_fname .. '.lua'
return loadfile( path )
else -- not a bundle - give up the search
return
end
end
-- search for the module scanning package.path
for template in string.gmatch( package.path, '[^;]+' ) do
if string.match( template, '%.myplugin' ) then -- bundle?
local module_path =
string.gsub( template, '%?', module_name )
local fh = io.open( module_path ) -- file exists?
if fh then -- module found
fh:close()
bundles[ module_name ] = module_path
return loadfile( module_path )
end
end
end
end
-- sets the custom searcher as the first one so to take
-- precedence over default ones
table.insert( package.loaders, 1, custom_searcher )
local foo = require 'foo'
foo.Foobnicator()
Running main.lua
will produce the following output:
Called: Foobnicator!! Called: AuxFunction!!
I hope this will put you on the right track. Probably it doesn't cover every possibility and the error handling is not at all complete, but it should give you a good base to work on.
Upvotes: 4