tl;dr:我想创建具有自定义目录名称模式的lua包,但搜索路径有问题。
问题所在
我有一个应用程序,我想允许用户编写插件,遵循与Lightroom类似的模型:
- 一组默认插件存储在
<app data>/plugins/<name>.myplugin
-
<name>.myplugin
是一个目录包,可能包含一组脚本、二进制文件或其他资源 - 插件可以通过不同的脚本将许多不同的功能导出到应用程序中
- 导出函数的名称列在应用读取的
info.lua
文件中
我正在努力解决的问题是如何最好地将插件包装为包(模块 + 子模块)或常规脚本。我设想插件可能包含第三方模块:
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
如果我设置了包搜索路径,package.path
包括
<appdata>/?.myplugin/?.lua
然后我可以用Foo=require 'Foo'
加载包. 但是,我无法弄清楚如何加载子模块。如果Foo.lua
调用UsefulFunctions=require 'UsefulFunctions'
则此加载失败,因为 lua 的搜索路径尝试查找UsefulFunctions.myplugin/UsefulFunctions.lua
。出于类似的原因,我也无法用require 'Foo.UsefulFunctions'
加载它。
一些选项:
- 一种解决方法是将每个插件的路径显式添加到包路径中,但如果两个插件各自包含一个同名的子模块,这将导致问题。
- 另一种选择是编写插件以使用常规 lua 脚本而不是提供模块,但这仍然意味着必须在每个插件中设置搜索路径。
- 回退选项可能是丢失
.myplugin
后缀,这将简化包搜索路径。 - 补丁 Lua 以明确支持这种类型的搜索路径
有什么方法可以提供我需要的功能吗?
我目前使用的是Lua 5.1。我知道 5.2 对包搜索路径有更多的控制权,但我认为我目前无法选择更新到它。我也在使用luabind,尽管我认为它与此无关。
自定义搜索器函数自定义Lua搜索模块的方式,使用require
和package.loaders
文档中概述的机制。
诀窍是检测模块是否可以在带有.myplugins
后缀的目录中找到,并跟踪捆绑包的路径。请考虑以下脚本。
-- <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()
运行main.lua
将产生以下输出:
叫:福布尼卡!!调用:辅助功能!!
我希望这会让你走上正轨。可能它没有涵盖所有可能性,错误处理也根本不完整,但它应该给你一个良好的工作基础。