Package System

lua-users home
wiki

VersionNotice: This page is a bit outdated. It's a initial proposal for a module system implemented on top of Lua 5.0, which lacked a standard module system. A standard module system was formally incorporated in Lua 5.1, and that module system was also backported to Lua 5.0 via [LuaCompat]. I'm not sure if this page has any remaining educational value. If it does have remaining value, it probably could be described or reworked in light of the 5.1 module work.

This is an embryonic package system for Lua 5. Its main function is

use "packagename" {options}
(where {options} is optional ;-) That call is similar to a require, except that

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


RecentChanges · preferences
edit · history
Last edited January 2, 2007 5:36 am GMT (diff)