Package System |
|
use "packagename" {options}
(where {options} is optional ;-) That call is similar to
a require, except that
_init function, it is run;
_options function or a default options function is called to handle options.
Currently, the current option function handles only the import option.
import="*" means to declare all global names of the package into
the global space of the importing package;
import={"name1", "name2", ...} imports only the selected names.
It also defines a declare ("name1", "name2", ...) function,
that turns on enforcing declaration of names, and also declares the
given names.
Any access to an undefined/undeclared global raises an error.
--
-- auxiliar error function
--
local function error (level, fmt, ...)
_G.error(string.format(fmt, unpack(arg)), level+1)
end
--
-- this package cannot use the package system (itself!), so use old
-- package tricks (but most of its functions are global anyway...)
--
_G.Package = {}
local function loadfrompath (packname)
LUA_PATH = LUA_PATH or os.getenv"LUA_PATH" or "?.lua;?"
for k in string.gfind(LUA_PATH, "[^;]+") do
local fname = string.gsub(k, "?", packname)
local f, err = loadfile(fname)
if f then return f end
if not string.find(err, "^cannot read") then
error(err)
end
end
error(3, "cannot find package `%s' in path `%s'", packname, LUA_PATH)
end
--
-- Metatable for Global tables
-- Inherit absent fields from main global
--
local global_mt = {
__index = function (t,n)
local val = _G[n] -- get value from main global
rawset(t, n, val) -- save it for next time
return val
end,
}
--
-- Alternative metatable, that enforces declarations
--
local Predefined = {} -- table for predefined variables
setmode(Predefined, "k")
local req_global_mt = {
__index = function (t,n)
local val = global_mt.__index(t, n)
if val then return val end
if not Predefined[t][n] then
error(2, "attempt to read undeclared variable `%s'", n)
end
return nil
end,
__newindex = function (t,n, val)
if not Predefined[t][n] then
error(2, "attempt to write to undeclared variable `%s'", n)
end
rawset(t, n, val)
end,
}
--
-- Declare variables (and turn on declaration enforcing)
--
function _G.declare (...)
local predec = Predefined[getglobals(2)]
if predec == nil then -- package didn't enforce declarations
local g = getglobals(2) -- get package global table
setmetatable(g, req_global_mt)
predec = {}
Predefined[g] = predec
end
for _, name in ipairs(arg) do
predec[name] = true
end
end
--
-- Default function to handle `use' options
-- (where `oldpack' is using `newpack')
--
function Package.defaultoptions (oldpack, newpack, options)
for k, v in pairs(options) do
if k == "version" then
-- ???
elseif k == "import" then
if v == "*" then -- import all?
for k,v in pairs(newpack) do
-- do not import names starting with `_'
if not string.find(k, "^_") then oldpack[k] = v end
end
elseif type(v) == "table" then -- import list?
for _,n in ipairs(v) do oldpack[n] = newpack[n] end
else error(3, "invalid value for `import' option")
end
else error(3, "invalid option `"..k.."'")
end
end
end
--
-- Import a package, initialize it, and install it in current package
--
function _G.use (packname)
local g = _G[packname]
if not g then
local f = loadfrompath(packname)
g = {_name = packname} -- new global table
g._self = g
_G[packname] = g
setmetatable(g, global_mt)
setglobals(f, g) -- change global table of calling function
f() -- run main
end
local init = rawget(g, "_init")
if init then init(getglobals(2)) end
return function (options)
(rawget(g, "_options") or Package.defaultoptions)(getglobals(2), g, options)
end
end