mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-01-12 02:10:30 +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
|
||||
|
||||
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 parentShell = shell
|
||||
@ -28,94 +29,10 @@ local tCompletionInfo = parentShell and parentShell.getCompletionInfo() or {}
|
||||
local tProgramStack = {}
|
||||
|
||||
local shell = {} --- @export
|
||||
local function createShellEnv(sDir)
|
||||
local tEnv = {}
|
||||
tEnv.shell = shell
|
||||
tEnv.multishell = multishell
|
||||
|
||||
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
|
||||
local function createShellEnv(dir)
|
||||
local env = { shell = shell, multishell = multishell }
|
||||
env.require, env.package = make_package(env, dir)
|
||||
return env
|
||||
end
|
||||
|
||||
-- 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…
Reference in New Issue
Block a user