325 lines
6.8 KiB
Lua
325 lines
6.8 KiB
Lua
local exports = {};
|
|
|
|
local function mk_module(name, init, parent)
|
|
local module;
|
|
local cache = {};
|
|
|
|
parent.modules = parent.modules or {};
|
|
module = setmetatable({
|
|
name = name,
|
|
exports = {},
|
|
init = init,
|
|
env = setmetatable({}, { __index = parent.env }),
|
|
}, { __index = parent });
|
|
|
|
module.returns = { n = 1, module.exports };
|
|
|
|
function module.resolve(id, list)
|
|
if cache[id] ~= nil then return cache[id] end
|
|
|
|
for i = 1, #module.resolvers do
|
|
local res, files = module.resolvers[i](module, id);
|
|
if res then
|
|
cache[id] = res;
|
|
return res;
|
|
elseif list then
|
|
for i = 1, #files do
|
|
list[#list + 1] = files[i];
|
|
end
|
|
end
|
|
end
|
|
|
|
return nil;
|
|
end
|
|
function module.import(id)
|
|
local paths = {};
|
|
local mod = module.resolve(id, paths);
|
|
if mod == nil then
|
|
error(table.concat {
|
|
"Unable to resolve module '",
|
|
id,
|
|
"' - tried the following paths:\n- ",
|
|
table.concat(paths, "\n- ")
|
|
}, 2);
|
|
elseif type(mod.init) == "function" then
|
|
local init = mod.init;
|
|
mod.init = nil;
|
|
init(mod);
|
|
end
|
|
|
|
return (unpack or table.unpack)(mod.returns, 1, mod.returns.n);
|
|
end
|
|
|
|
function module.require(id)
|
|
local original_id = id;
|
|
local match = id:match "[^/]+$";
|
|
|
|
if match == id then
|
|
local start = id:match "^%.+" or "";
|
|
local rest = id:sub(#start + 1):gsub("%.", "/");
|
|
|
|
if #start == 0 then
|
|
id = rest;
|
|
elseif #start == 1 then
|
|
id = "./" .. rest;
|
|
else
|
|
id = ("../"):rep(#start - 1) .. rest;
|
|
end
|
|
else
|
|
id = id:sub(1, -#match - 1) .. match:gsub("%.", "/");
|
|
end
|
|
|
|
local paths = {};
|
|
local mod = module.resolve(id .. ".lua", paths);
|
|
|
|
if mod == nil then
|
|
mod = module.resolve(id .. "/init.lua", paths);
|
|
end
|
|
|
|
if mod == nil then
|
|
error(table.concat {
|
|
"Unable to resolve module '",
|
|
original_id,
|
|
"' - tried the following paths:\n- ",
|
|
table.concat(paths, "\n- ")
|
|
}, 2);
|
|
elseif type(mod.init) == "function" then
|
|
local init = mod.init;
|
|
mod.init = nil;
|
|
init(mod);
|
|
end
|
|
|
|
return (unpack or table.unpack)(mod.returns, 1, mod.returns.n);
|
|
end
|
|
function module.export(exports)
|
|
for k, v in next, exports do
|
|
module.exports[k] = v;
|
|
end
|
|
end
|
|
function module.mk(name, init)
|
|
if module.modules[name] ~= nil then
|
|
return module.modules[name], true;
|
|
else
|
|
local mod = mk_module(name, init, module);
|
|
module.modules[name] = mod;
|
|
return mod, false;
|
|
end
|
|
end
|
|
|
|
module.env.module = module;
|
|
module.env.import = module.import;
|
|
module.env.require = module.require;
|
|
module.env.export = module.export;
|
|
module.env.exports = module.exports;
|
|
|
|
module.env.package = setmetatable({}, {
|
|
__index = function()
|
|
error "This is a TAL module, please use 'module' instead";
|
|
end,
|
|
__newindex = function()
|
|
error "This is a TAL module, please use 'module' instead";
|
|
end,
|
|
});
|
|
|
|
return module;
|
|
end
|
|
|
|
local function try_file(self, cwd, id, base, suffixes)
|
|
suffixes = suffixes or { "" };
|
|
local file, found;
|
|
local files = {};
|
|
|
|
for i = 1, #suffixes do
|
|
file = cwd(base, id .. suffixes[i]);
|
|
|
|
local f = io.open(file, "r");
|
|
if f ~= nil then
|
|
f:close();
|
|
found = true;
|
|
break;
|
|
end
|
|
|
|
files[#files + 1] = file;
|
|
end
|
|
|
|
if not found then
|
|
return nil, files;
|
|
end
|
|
|
|
return self.mk(file, function (self)
|
|
local func = assert(loadfile(file, "t", self.env));
|
|
|
|
local function fin(...)
|
|
if select("#", ...) > 0 then
|
|
self.exports = ...;
|
|
self.returns = { n = select("#", ...), ... };
|
|
end
|
|
end
|
|
fin(func());
|
|
end), file;
|
|
end
|
|
|
|
function exports.file_resolver(cwd, join)
|
|
return function(self, id)
|
|
if id:match "^%./" or id:match "^%.%./" then
|
|
return try_file(self, cwd, id, join(self.name, "..", self.extensions), self.extensions);
|
|
else
|
|
local files = {};
|
|
|
|
for i = 1, #self.paths do
|
|
local res, file = try_file(self, cwd, id, self.paths[i], self.extensions);
|
|
if res ~= nil then
|
|
return res, file;
|
|
end
|
|
|
|
for i = 1, #file do
|
|
files[#files + 1] = file[i];
|
|
end
|
|
end
|
|
|
|
return nil, files;
|
|
end
|
|
end
|
|
end
|
|
function exports.lua_resolver(opts)
|
|
local returns = {
|
|
modules = {},
|
|
};
|
|
|
|
function returns.resolver(self, id)
|
|
if id:match "^lua:" then
|
|
id = id:sub(5);
|
|
end
|
|
|
|
if returns.modules[id] ~= nil then
|
|
return returns.modules[id], "lua:" ..id;
|
|
else
|
|
return nil, { "lua:" .. id };
|
|
end
|
|
end
|
|
|
|
function returns.register(name, ...)
|
|
returns.modules[name] = {
|
|
name = "lua:" .. name,
|
|
exports = ...,
|
|
returns = { n = select("#", ...), ... },
|
|
};
|
|
end
|
|
|
|
if opts.glob then
|
|
returns.register("global.lua", opts.glob);
|
|
end
|
|
|
|
if opts.expose then
|
|
local expose = opts.expose;
|
|
if expose == true then
|
|
expose = {
|
|
"utf8", "bit32", "bit",
|
|
"table", "string", "os", "math", "jit", "io", "ffi", "debug", "coroutine",
|
|
"table.new", "table.clear",
|
|
"string.buffer",
|
|
"jit.profile",
|
|
};
|
|
end
|
|
|
|
for i = 1, #expose do
|
|
local ok, mod = pcall(require, expose[i]);
|
|
if ok then
|
|
returns.register(expose[i]:gsub("%.", "/") .. ".lua", mod);
|
|
end
|
|
end
|
|
end
|
|
|
|
if opts.root then
|
|
returns.register("root.lua", opts.root);
|
|
end
|
|
|
|
returns.register("lua-resolver.lua", returns);
|
|
|
|
return returns;
|
|
end
|
|
|
|
function exports.mk_root(opts)
|
|
local run_loop;
|
|
local root = mk_module(
|
|
opts.join(opts.pwd, "/<root>"),
|
|
nil,
|
|
{
|
|
paths = {},
|
|
resolvers = {},
|
|
modules = {},
|
|
env = opts.glob,
|
|
}
|
|
);
|
|
|
|
root.paths[#root.paths + 1] = opts.pwd .. "/.tal_mod";
|
|
|
|
if opts.tal_path then
|
|
for i = 1, #opts.tal_path do
|
|
local p = opts.tal_path[i];
|
|
|
|
if p:match "^~/" then
|
|
root.paths[#root.paths + 1] = opts.join(opts.home, p:sub(2));
|
|
elseif p:match "^/" then
|
|
root.paths[#root.paths + 1] = opts.join(p);
|
|
else
|
|
root.paths[#root.paths + 1] = opts.join(opts.pwd, p);
|
|
end
|
|
end
|
|
end
|
|
|
|
if opts.lua_path then
|
|
for v in opts.lua_path:gmatch "[^;]+" do
|
|
local match = v:match "^(.+)/%?%.lua$";
|
|
if match ~= nil and match ~= "." then
|
|
root.paths[#root.paths + 1] = match;
|
|
end
|
|
end
|
|
end
|
|
|
|
if opts.resolvers then
|
|
root.resolvers[#root.resolvers + 1] = exports.file_resolver(opts.cwd, opts.join);
|
|
root.resolvers[#root.resolvers + 1] = exports.lua_resolver {
|
|
glob = opts.glob,
|
|
expose = true,
|
|
root = root,
|
|
}.resolver;
|
|
end
|
|
|
|
return root, run_loop;
|
|
end
|
|
|
|
function exports.init(opts, msg)
|
|
if opts.glob == nil then
|
|
---@diagnostic disable-next-line: deprecated
|
|
opts.glob = _ENV or _G or (getfenv and getfenv());
|
|
if opts.glob == nil then
|
|
error "Environment is not accessible";
|
|
end
|
|
end
|
|
|
|
local root = exports.mk_root {
|
|
tal_path = opts.tal_path,
|
|
lua_path = opts.lua_path or package.path,
|
|
resolvers = true,
|
|
evn_loop = true,
|
|
|
|
pwd = opts.pwd or opts.fs.pwd(),
|
|
home = opts.home or opts.fs.home(),
|
|
|
|
join = opts.join or opts.path.join,
|
|
cwd = opts.cwd or opts.path.cwd,
|
|
glob = opts.glob,
|
|
};
|
|
|
|
local require = root.require;
|
|
|
|
require "core"(opts.target_glob or root.env);
|
|
local run_loop = require "core.evn-loop";
|
|
root.env.promise = require "promise";
|
|
|
|
return root, run_loop;
|
|
end
|
|
|
|
return exports;
|