mirror of
https://github.com/CommunityOx/ox_lib.git
synced 2026-08-17 06:56:03 +01:00
refactor(require): improve module resolution and debugging info
- Improved error messages. - Include checked file paths on failed module resolution. - Moved repeated code into separate functions. - Workaround for incorrect "circular-dependency" error. - Added additional package.path "./?/init.lua". - Added support for package.searchers.
This commit is contained in:
@@ -1,30 +1,30 @@
|
|||||||
local loaded = {}
|
local loaded = {}
|
||||||
|
local _require = require
|
||||||
|
|
||||||
package = {
|
package = {
|
||||||
|
path = './?.lua;./?/init.lua',
|
||||||
|
preload = {},
|
||||||
loaded = setmetatable({}, {
|
loaded = setmetatable({}, {
|
||||||
__index = loaded,
|
__index = loaded,
|
||||||
__newindex = noop,
|
__newindex = noop,
|
||||||
__metatable = false,
|
__metatable = false,
|
||||||
}),
|
})
|
||||||
path = './?.lua;'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
local _require = require
|
---@param modName string
|
||||||
|
---@return string
|
||||||
---@param modpath string
|
---@return string
|
||||||
---@param modname? string
|
local function getModuleInfo(modName)
|
||||||
---@return string, string?
|
|
||||||
local function getModuleInfo(modpath, modname)
|
|
||||||
local resourceSrc
|
local resourceSrc
|
||||||
|
|
||||||
if not modpath:find('^@') then
|
if not modName:find('^@') then
|
||||||
local idx = 1
|
local idx = 1
|
||||||
|
|
||||||
while true do
|
while true do
|
||||||
local di = debug.getinfo(idx, 'S')
|
local di = debug.getinfo(idx, 'S')
|
||||||
|
|
||||||
if di then
|
if di then
|
||||||
if not di.short_src:find('^@ox_lib/imports/require') and not di.short_src:find('^%[C%]') and not di.short_src:find('^citizen') and di.short_src ~= '?' then
|
if not di.short_src ~= '?' and di.source:match('^@([^%s]+)') and not di.short_src:find('^@ox_lib/imports/require') and not di.short_src:find('^citizen') then
|
||||||
resourceSrc = di.source:gsub('^%@+([^/]+)/.+', '%1')
|
resourceSrc = di.source:gsub('^%@+([^/]+)/.+', '%1')
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
@@ -35,138 +35,139 @@ local function getModuleInfo(modpath, modname)
|
|||||||
|
|
||||||
idx += 1
|
idx += 1
|
||||||
end
|
end
|
||||||
|
else
|
||||||
if modname and resourceSrc ~= cache.resource then
|
resourceSrc = modName:gsub('^@(.-)/.+', '%1')
|
||||||
modname = ('@%s.%s'):format(resourceSrc, modname)
|
modName = modName:sub(#resourceSrc + 3)
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return resourceSrc, modname
|
return resourceSrc, modName
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local tempData = {}
|
||||||
|
|
||||||
|
---@param name string
|
||||||
|
---@param path string
|
||||||
|
---@return string? filename
|
||||||
|
---@return string? errmsg
|
||||||
|
function package.searchpath(name, path)
|
||||||
|
local resourceSrc, modName = getModuleInfo(name:gsub('%.', '/'))
|
||||||
|
local tried = {}
|
||||||
|
|
||||||
|
for template in path:gmatch('[^;]+') do
|
||||||
|
local fileName = template:gsub('^%./', ''):gsub('?', modName:gsub('%.', '/') or modName)
|
||||||
|
local file = LoadResourceFile(resourceSrc, fileName)
|
||||||
|
|
||||||
|
if file then
|
||||||
|
tempData[1] = file
|
||||||
|
tempData[2] = resourceSrc
|
||||||
|
return fileName
|
||||||
|
end
|
||||||
|
|
||||||
|
tried[#tried + 1] = fileName
|
||||||
|
end
|
||||||
|
|
||||||
|
return nil, table.concat(tried, "\n\t")
|
||||||
|
end
|
||||||
|
|
||||||
|
---Attempts to load a module at the given path relative to the resource root directory.\
|
||||||
|
---Returns a function to load the module chunk, or a string containing all tested paths.
|
||||||
|
---@param modName string
|
||||||
|
---@param env? table
|
||||||
|
local function loadModule(modName, env)
|
||||||
|
local fileName, err = package.searchpath(modName, package.path)
|
||||||
|
|
||||||
|
if fileName then
|
||||||
|
local file = tempData[1]
|
||||||
|
local resource = tempData[2]
|
||||||
|
|
||||||
|
table.wipe(tempData)
|
||||||
|
return assert(load(file, ('@@%s/%s'):format(resource, modName), 't', env or _ENV))
|
||||||
|
end
|
||||||
|
|
||||||
|
return nil, err or 'unknown error'
|
||||||
|
end
|
||||||
|
|
||||||
|
package.searchers = {
|
||||||
|
function(modName) return package.preload[modName] end,
|
||||||
|
function(modName)
|
||||||
|
local ok, result = pcall(_require, modName)
|
||||||
|
|
||||||
|
if ok then return result end
|
||||||
|
|
||||||
|
return ok, result
|
||||||
|
end,
|
||||||
|
function(modName) return loadModule(modName) end,
|
||||||
|
}
|
||||||
|
|
||||||
---@param filePath string
|
---@param filePath string
|
||||||
---@param env? table
|
---@param env? table
|
||||||
---@return any
|
---@return unknown
|
||||||
---Loads and runs a Lua file at the given path. Unlike require, the chunk is not cached for future use.
|
---Loads and runs a Lua file at the given path. Unlike require, the chunk is not cached for future use.
|
||||||
function lib.load(filePath, env)
|
function lib.load(filePath, env)
|
||||||
local resourceSrc
|
if type(filePath) ~= 'string' then
|
||||||
local modpath = filePath:gsub('%.', '/')
|
error(("file path must be a string (received '%s')"):format(filePath), 2)
|
||||||
|
|
||||||
if not modpath:find('^@') then
|
|
||||||
resourceSrc = getModuleInfo(modpath)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
if not resourceSrc then
|
local result, err = loadModule(filePath, env)
|
||||||
resourceSrc = modpath:gsub('^@(.-)/.+', '%1')
|
|
||||||
modpath = modpath:sub(#resourceSrc + 3)
|
|
||||||
end
|
|
||||||
|
|
||||||
for path in package.path:gmatch('[^;]+') do
|
if result then return result() end
|
||||||
local scriptPath = path:gsub('?', modpath):gsub('%.+%/+', '')
|
|
||||||
local resourceFile = LoadResourceFile(resourceSrc, scriptPath)
|
|
||||||
|
|
||||||
if resourceFile then
|
error(err)
|
||||||
|
|
||||||
local chunk, err = load(resourceFile, ('@@%s/%s'):format(resourceSrc, modpath), 't', env or _ENV)
|
|
||||||
|
|
||||||
if not chunk or err then
|
|
||||||
error(err or 'an unknown error occurred', 2)
|
|
||||||
end
|
|
||||||
|
|
||||||
return chunk()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
error(('cannot load file at path %s'):format(modpath))
|
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param filePath string
|
---@param filePath string
|
||||||
---@return table
|
---@return table
|
||||||
---Loads and decodes a json file at the given path.
|
---Loads and decodes a json file at the given path.
|
||||||
function lib.loadJson(filePath)
|
function lib.loadJson(filePath)
|
||||||
local resourceSrc
|
if type(filePath) ~= 'string' then
|
||||||
local modpath = filePath:gsub('%.', '/')
|
error(("file path must be a string (received '%s')"):format(filePath), 2)
|
||||||
|
|
||||||
if not modpath:find('^@') then
|
|
||||||
resourceSrc = getModuleInfo(modpath)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
if not resourceSrc then
|
local resourceSrc, modPath = getModuleInfo(filePath)
|
||||||
resourceSrc = modpath:gsub('^@(.-)/.+', '%1')
|
local resourceFile = LoadResourceFile(resourceSrc, ('%s.json'):format(modPath))
|
||||||
modpath = modpath:sub(#resourceSrc + 3)
|
|
||||||
end
|
|
||||||
|
|
||||||
local scriptPath = ('%s.json'):format(modpath)
|
|
||||||
local resourceFile = LoadResourceFile(resourceSrc, scriptPath)
|
|
||||||
|
|
||||||
if resourceFile then
|
if resourceFile then
|
||||||
return json.decode(resourceFile)
|
return json.decode(resourceFile)
|
||||||
end
|
end
|
||||||
|
|
||||||
error(('cannot load json file at path %s'):format(modpath))
|
error(('cannot load json file at path %s'):format(modPath))
|
||||||
end
|
end
|
||||||
|
|
||||||
---Loads the given module inside the current resource, returning any values returned by the file or `true` when `nil`.
|
---Loads the given module, returns any value returned by the seacher (`true` when `nil`).\
|
||||||
---@param modname string
|
---Passing `@resourceName.modName` loads a module from a remote resource.\
|
||||||
|
---@param modName string
|
||||||
---@return unknown
|
---@return unknown
|
||||||
function lib.require(modname)
|
function lib.require(modName)
|
||||||
if type(modname) ~= 'string' then return end
|
if type(modName) ~= 'string' then
|
||||||
|
error(("module name must be a string (received '%s')"):format(modName), 3)
|
||||||
local modpath = modname:gsub('%.', '/')
|
|
||||||
local module = loaded[modname]
|
|
||||||
|
|
||||||
if module then return module end
|
|
||||||
|
|
||||||
local success, result = pcall(_require, modname)
|
|
||||||
|
|
||||||
if success then
|
|
||||||
loaded[modname] = result
|
|
||||||
return result
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local resourceSrc
|
local module = loaded[modName]
|
||||||
|
|
||||||
if not modpath:find('^@') then
|
if module == '__loading' then
|
||||||
resourceSrc, modname = getModuleInfo(modpath, modname) --[[@as string]]
|
error(("^1circular-dependency occurred when loading module '%s'^0"):format(modName), 2)
|
||||||
end
|
end
|
||||||
|
|
||||||
if not module then
|
if module ~= nil then return module end
|
||||||
if module == false then
|
|
||||||
error(("^1circular-dependency occurred when loading module '%s'^0"):format(modname), 2)
|
loaded[modName] = '__loading'
|
||||||
|
|
||||||
|
local err = {}
|
||||||
|
|
||||||
|
for i = 1, #package.searchers do
|
||||||
|
local result, errMsg = package.searchers[i](modName)
|
||||||
|
|
||||||
|
if result then
|
||||||
|
if type(result) == 'function' then result = result() end
|
||||||
|
loaded[modName] = result or result == nil
|
||||||
|
|
||||||
|
return loaded[modName]
|
||||||
end
|
end
|
||||||
|
|
||||||
if not resourceSrc then
|
err[#err + 1] = errMsg
|
||||||
resourceSrc = modpath:gsub('^@(.-)/.+', '%1')
|
|
||||||
modpath = modpath:sub(#resourceSrc + 3)
|
|
||||||
end
|
|
||||||
|
|
||||||
for path in package.path:gmatch('[^;]+') do
|
|
||||||
local scriptPath = path:gsub('?', modpath):gsub('%.+%/+', '')
|
|
||||||
local resourceFile = LoadResourceFile(resourceSrc, scriptPath)
|
|
||||||
|
|
||||||
if resourceFile then
|
|
||||||
loaded[modname] = false
|
|
||||||
scriptPath = ('@@%s/%s'):format(resourceSrc, scriptPath)
|
|
||||||
|
|
||||||
local chunk, err = load(resourceFile, scriptPath)
|
|
||||||
|
|
||||||
if err or not chunk then
|
|
||||||
loaded[modname] = nil
|
|
||||||
return error(err or ("unable to load module '%s'"):format(modname), 3)
|
|
||||||
end
|
|
||||||
|
|
||||||
local result = chunk(modname)
|
|
||||||
module = result or result == nil
|
|
||||||
loaded[modname] = module
|
|
||||||
|
|
||||||
return module
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return error(result, 2)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return module
|
error(("%s"):format(table.concat(err, "\n\t")))
|
||||||
end
|
end
|
||||||
|
|
||||||
return lib.require
|
return lib.require
|
||||||
|
|||||||
Reference in New Issue
Block a user