mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-08-06 22:04:39 +00:00
Move the package library into a separate module
Hopefully this makes it a little easier for people to use in custom shells (and anything else where it might be useful).
This commit is contained in:
parent
a4ae36b6b3
commit
086fccd997
@ -0,0 +1,121 @@
|
|||||||
|
--- This provides a pure Lua implementation of the builtin @{require} function
|
||||||
|
-- and @{package} library.
|
||||||
|
--
|
||||||
|
-- Generally you do not need to use this module - it is injected into the
|
||||||
|
-- every program's environment. However, it may be useful when building a
|
||||||
|
-- custom shell or when running programs yourself.
|
||||||
|
--
|
||||||
|
-- @module cc.require
|
||||||
|
-- @usage Construct the package and require function, and insert them into a
|
||||||
|
-- custom environment.
|
||||||
|
--
|
||||||
|
-- local env = setmetatable({}, { __index = _ENV })
|
||||||
|
-- local r = require "cc.require"
|
||||||
|
-- env.require, env.package = r.make(env, "/")
|
||||||
|
|
||||||
|
local expect = require and require("cc.expect") or dofile("rom/modules/main/cc/expect.lua")
|
||||||
|
local expect = expect.expect
|
||||||
|
|
||||||
|
local function preload(package)
|
||||||
|
return function(name)
|
||||||
|
if package.preload[name] then
|
||||||
|
return package.preload[name]
|
||||||
|
else
|
||||||
|
return nil, "no field package.preload['" .. name .. "']"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function from_file(package, env, dir)
|
||||||
|
return function(name)
|
||||||
|
local fname = string.gsub(name, "%.", "/")
|
||||||
|
local sError = ""
|
||||||
|
for pattern in string.gmatch(package.path, "[^;]+") do
|
||||||
|
local sPath = string.gsub(pattern, "%?", fname)
|
||||||
|
if sPath:sub(1, 1) ~= "/" then
|
||||||
|
sPath = fs.combine(dir, sPath)
|
||||||
|
end
|
||||||
|
if fs.exists(sPath) and not fs.isDir(sPath) then
|
||||||
|
local fnFile, sError = loadfile(sPath, nil, env)
|
||||||
|
if fnFile then
|
||||||
|
return fnFile, sPath
|
||||||
|
else
|
||||||
|
return nil, sError
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if #sError > 0 then
|
||||||
|
sError = sError .. "\n "
|
||||||
|
end
|
||||||
|
sError = sError .. "no file '" .. sPath .. "'"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return nil, sError
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function make_require(package)
|
||||||
|
local sentinel = {}
|
||||||
|
return function(name)
|
||||||
|
expect(1, name, "string")
|
||||||
|
|
||||||
|
if package.loaded[name] == sentinel then
|
||||||
|
error("loop or previous error loading module '" .. name .. "'", 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
if package.loaded[name] then
|
||||||
|
return package.loaded[name]
|
||||||
|
end
|
||||||
|
|
||||||
|
local sError = "module '" .. name .. "' not found:"
|
||||||
|
for _, searcher in ipairs(package.loaders) do
|
||||||
|
local loader = table.pack(searcher(name))
|
||||||
|
if loader[1] then
|
||||||
|
package.loaded[name] = sentinel
|
||||||
|
local result = loader[1](name, table.unpack(loader, 2, loader.n))
|
||||||
|
if result == nil then result = true end
|
||||||
|
|
||||||
|
package.loaded[name] = result
|
||||||
|
return result
|
||||||
|
else
|
||||||
|
sError = sError .. "\n " .. loader[2]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
error(sError, 2)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Build an implementation of Lua's @{package} library, and a @{require}
|
||||||
|
-- function to load modules within it.
|
||||||
|
--
|
||||||
|
-- @tparam table env The environment to load packages into.
|
||||||
|
-- @tparam string dir The directory that relative packages are loaded from.
|
||||||
|
-- @treturn function The new @{require} function.
|
||||||
|
-- @treturn table The new @{package} library.
|
||||||
|
local function make_package(env, dir)
|
||||||
|
expect(1, env, "table")
|
||||||
|
expect(2, dir, "string")
|
||||||
|
|
||||||
|
local package = {}
|
||||||
|
package.loaded = {
|
||||||
|
_G = _G,
|
||||||
|
bit32 = bit32,
|
||||||
|
coroutine = coroutine,
|
||||||
|
math = math,
|
||||||
|
package = package,
|
||||||
|
string = string,
|
||||||
|
table = table,
|
||||||
|
}
|
||||||
|
package.path = "?;?.lua;?/init.lua;/rom/modules/main/?;/rom/modules/main/?.lua;/rom/modules/main/?/init.lua"
|
||||||
|
if turtle then
|
||||||
|
package.path = package.path .. ";/rom/modules/turtle/?;/rom/modules/turtle/?.lua;/rom/modules/turtle/?/init.lua"
|
||||||
|
elseif commands then
|
||||||
|
package.path = package.path .. ";/rom/modules/command/?;/rom/modules/command/?.lua;/rom/modules/command/?/init.lua"
|
||||||
|
end
|
||||||
|
package.config = "/\n;\n?\n!\n-"
|
||||||
|
package.preload = {}
|
||||||
|
package.loaders = { preload(package), from_file(package, env, dir) }
|
||||||
|
|
||||||
|
return make_require(package), package
|
||||||
|
end
|
||||||
|
|
||||||
|
return { make = make_package }
|
@ -11,6 +11,7 @@
|
|||||||
-- @module[module] shell
|
-- @module[module] shell
|
||||||
|
|
||||||
local expect = dofile("rom/modules/main/cc/expect.lua").expect
|
local expect = dofile("rom/modules/main/cc/expect.lua").expect
|
||||||
|
local make_package = dofile("rom/modules/main/cc/require.lua").make
|
||||||
|
|
||||||
local multishell = multishell
|
local multishell = multishell
|
||||||
local parentShell = shell
|
local parentShell = shell
|
||||||
@ -28,94 +29,10 @@ local tCompletionInfo = parentShell and parentShell.getCompletionInfo() or {}
|
|||||||
local tProgramStack = {}
|
local tProgramStack = {}
|
||||||
|
|
||||||
local shell = {} --- @export
|
local shell = {} --- @export
|
||||||
local function createShellEnv(sDir)
|
local function createShellEnv(dir)
|
||||||
local tEnv = {}
|
local env = { shell = shell, multishell = multishell }
|
||||||
tEnv.shell = shell
|
env.require, env.package = make_package(env, dir)
|
||||||
tEnv.multishell = multishell
|
return env
|
||||||
|
|
||||||
local package = {}
|
|
||||||
package.loaded = {
|
|
||||||
_G = _G,
|
|
||||||
bit32 = bit32,
|
|
||||||
coroutine = coroutine,
|
|
||||||
math = math,
|
|
||||||
package = package,
|
|
||||||
string = string,
|
|
||||||
table = table,
|
|
||||||
}
|
|
||||||
package.path = "?;?.lua;?/init.lua;/rom/modules/main/?;/rom/modules/main/?.lua;/rom/modules/main/?/init.lua"
|
|
||||||
if turtle then
|
|
||||||
package.path = package.path .. ";/rom/modules/turtle/?;/rom/modules/turtle/?.lua;/rom/modules/turtle/?/init.lua"
|
|
||||||
elseif commands then
|
|
||||||
package.path = package.path .. ";/rom/modules/command/?;/rom/modules/command/?.lua;/rom/modules/command/?/init.lua"
|
|
||||||
end
|
|
||||||
package.config = "/\n;\n?\n!\n-"
|
|
||||||
package.preload = {}
|
|
||||||
package.loaders = {
|
|
||||||
function(name)
|
|
||||||
if package.preload[name] then
|
|
||||||
return package.preload[name]
|
|
||||||
else
|
|
||||||
return nil, "no field package.preload['" .. name .. "']"
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
function(name)
|
|
||||||
local fname = string.gsub(name, "%.", "/")
|
|
||||||
local sError = ""
|
|
||||||
for pattern in string.gmatch(package.path, "[^;]+") do
|
|
||||||
local sPath = string.gsub(pattern, "%?", fname)
|
|
||||||
if sPath:sub(1, 1) ~= "/" then
|
|
||||||
sPath = fs.combine(sDir, sPath)
|
|
||||||
end
|
|
||||||
if fs.exists(sPath) and not fs.isDir(sPath) then
|
|
||||||
local fnFile, sError = loadfile(sPath, nil, tEnv)
|
|
||||||
if fnFile then
|
|
||||||
return fnFile, sPath
|
|
||||||
else
|
|
||||||
return nil, sError
|
|
||||||
end
|
|
||||||
else
|
|
||||||
if #sError > 0 then
|
|
||||||
sError = sError .. "\n "
|
|
||||||
end
|
|
||||||
sError = sError .. "no file '" .. sPath .. "'"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return nil, sError
|
|
||||||
end,
|
|
||||||
}
|
|
||||||
|
|
||||||
local sentinel = {}
|
|
||||||
local function require(name)
|
|
||||||
expect(1, name, "string")
|
|
||||||
if package.loaded[name] == sentinel then
|
|
||||||
error("loop or previous error loading module '" .. name .. "'", 0)
|
|
||||||
end
|
|
||||||
if package.loaded[name] then
|
|
||||||
return package.loaded[name]
|
|
||||||
end
|
|
||||||
|
|
||||||
local sError = "module '" .. name .. "' not found:"
|
|
||||||
for _, searcher in ipairs(package.loaders) do
|
|
||||||
local loader = table.pack(searcher(name))
|
|
||||||
if loader[1] then
|
|
||||||
package.loaded[name] = sentinel
|
|
||||||
local result = loader[1](name, table.unpack(loader, 2, loader.n))
|
|
||||||
if result == nil then result = true end
|
|
||||||
|
|
||||||
package.loaded[name] = result
|
|
||||||
return result
|
|
||||||
else
|
|
||||||
sError = sError .. "\n " .. loader[2]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
error(sError, 2)
|
|
||||||
end
|
|
||||||
|
|
||||||
tEnv.package = package
|
|
||||||
tEnv.require = require
|
|
||||||
|
|
||||||
return tEnv
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Colours
|
-- Colours
|
||||||
|
@ -0,0 +1,79 @@
|
|||||||
|
describe("cc.require", function()
|
||||||
|
local r = require "cc.require"
|
||||||
|
local function mk()
|
||||||
|
local env = setmetatable({}, { __index = _ENV })
|
||||||
|
env.require, env.package = r.make({}, "/test-files/modules")
|
||||||
|
return env.require, env.package
|
||||||
|
end
|
||||||
|
|
||||||
|
local function setup(path, contents)
|
||||||
|
fs.delete("/test-files/modules")
|
||||||
|
io.open(path, "w"):write(contents):close()
|
||||||
|
end
|
||||||
|
|
||||||
|
describe("require", function()
|
||||||
|
it("errors on recursive modules", function()
|
||||||
|
local require, package = mk()
|
||||||
|
package.preload.pkg = function() require "pkg" end
|
||||||
|
expect.error(require, "pkg"):eq("loop or previous error loading module 'pkg'")
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("supplies the current module name", function()
|
||||||
|
local require, package = mk()
|
||||||
|
package.preload.pkg = table.pack
|
||||||
|
expect(require("pkg")):same { n = 1, "pkg" }
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("returns true instead of nil", function()
|
||||||
|
local require, package = mk()
|
||||||
|
package.preload.pkg = function() return nil end
|
||||||
|
expect(require("pkg")):eq(true)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("returns a constant value", function()
|
||||||
|
local require, package = mk()
|
||||||
|
package.preload.pkg = function() return {} end
|
||||||
|
expect(require("pkg")):eq(require("pkg"))
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("returns an error on not-found modules", function()
|
||||||
|
local require, package = mk()
|
||||||
|
package.path = "/?;/?.lua"
|
||||||
|
expect.error(require, "pkg"):eq(
|
||||||
|
"module 'pkg' not found:\n" ..
|
||||||
|
" no field package.preload['pkg']\n" ..
|
||||||
|
" no file '/pkg'\n" ..
|
||||||
|
" no file '/pkg.lua'")
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe("the file loader", function()
|
||||||
|
local function get(path)
|
||||||
|
local require, package = mk()
|
||||||
|
if path then package.path = path end
|
||||||
|
return require
|
||||||
|
end
|
||||||
|
|
||||||
|
it("works on absolute paths", function()
|
||||||
|
local require = get("/test-files/?.lua")
|
||||||
|
setup("test-files/some_module.lua", "return 123")
|
||||||
|
expect(require("some_module")):eq(123)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("works on relative paths", function()
|
||||||
|
local require = get("?.lua")
|
||||||
|
setup("test-files/modules/some_module.lua", "return 123")
|
||||||
|
expect(require("some_module")):eq(123)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("fails on syntax errors", function()
|
||||||
|
local require = get("?.lua")
|
||||||
|
setup("test-files/modules/some_module.lua", "1")
|
||||||
|
expect.error(require, "some_module"):str_match(
|
||||||
|
"^module 'some_module' not found:\n" ..
|
||||||
|
" no field package.preload%['some_module'%]\n" ..
|
||||||
|
" [^:]*some_module.lua:1: unexpected symbol near '1'$"
|
||||||
|
)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end)
|
Loading…
x
Reference in New Issue
Block a user