如何设置搜索路径以允许将Lua插件添加为包。

tl;dr:我想使用自定义目录名称模式创建Lua包,但在搜索路径方面遇到了问题。

问题

我有一个应用程序,想要允许用户编写插件,遵循类似于Lightroom的模型:

  • 默认一组插件存储在<app data>/plugins/<name>.myplugin
  • <name>.myplugin是一个目录束,它可以包含一组脚本、二进制文件或其他资源
  • 插件可以通过不同的脚本向应用导出许多不同的函数
  • 导出函数的名称在由应用读取的info.lua文件中列出

我正在解决的问题是如何将插件封装为包(模块+子模块)或常规脚本。我设想插件可能会包括第三方模块:

Foo.myplugin/
    info.lua - 返回包含插件名称、版本信息、已导出函数列表等的表
    Foo.lua - 定义此插件导出的主要函数,调用其他脚本:
    UsefulFunctions.lua - 被Foo.lua使用
    3rdparty/3rdparty.lua - 第三方模块

如果我将包搜索路径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,尽管我认为它与此无关。

点赞
用户2633423
用户2633423

你可以使用自定义的搜索函数来自定义 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 将产生以下输出:

Called: Foobnicator!!
Called: AuxFunction!!

希望这能让你找到正确的方向。可能它不能涵盖所有可能性,并且错误处理完全不完整,但它应该为你提供了一个很好的基础。

2013-10-29 19:54:51