如何设置搜索路径允许 lua 插件作为包



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搜索模块的方式,使用requirepackage.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将产生以下输出:

叫:福布尼卡!!调用:辅助功能!!

我希望这会让你走上正轨。可能它没有涵盖所有可能性,错误处理也根本不完整,但它应该给你一个良好的工作基础。