-- LTN-11 derivative package management system. -- Written by Rici Lake, released into the public domain. -- Each "file" is assumed to be an object constructor: it takes some arguments -- and returns an object. If no object can be constructed, it *should* return -- <nil, error>, but it is currently called inside a pcall anyway. -- In an ideal world, a chunk itself could be a constructor function but -- Lua (as of 5.0.2) does not provide a mechanism to pass arguments to -- chunk functions. So for now we use a shim which actually calls the chunk -- and returns the constructor function. It is highly recommended that -- the chunk have the format: -- -- comments -- return function(<parameters>) -- ... -- return <new object> -- end -- We also define a specific type of package: a module. -- A module constructor is a function of one argument: -- pkgtable = ctr(pkgtable) -- The return value must be the actual package table, filled in with -- package functions. If the function is called by import, there -- will always be a packagetable. If it is called by new (or in -- any other way) all bets are off. -- Object construction: -- -- ctr, err = new(pkgname) -- -- but usually: -- object = new(pkgname)(ctr args) -- -- new actually returns the constructor function for the named -- package, if it can find one; otherwise it returns nil and an error. -- Constructors are cached, so the filesystem is only consulted once. -- -- Although this may seem odd, it allows the syntax: -- aFoo = new "Foo" (42) -- which I find clearer than new("Foo", 42). -- -- If "Foo" cannot be found, the error message will, unfortunately, be lost. -- So some might prefer: -- -- constructor = new -- function new(pkgname) return assert(constructor, pkgname) end -- or: -- local Foo = assert(new "Foo") -- -- Importing a package: -- -- pkgtable = import(pkgname) -- -- Package tables are cached, so this will only consult the filesystem -- once (and only call the package constructor function once). -- -- import will throw an error if it is not successful. To avoid this, use pcall. -- Additional interfaces are provided in the package table "package" -- -- Register a constructor: -- func, x = package.registerCtr(name, func, x) -- -- Register a package table: -- pkgtable, x = package.registerPkg(name, pkgtable, x) -- -- Note: You can invalidate a cache by setting the second argument -- to nil (or just omitting it altogether), but in order to get -- a new constructor to run for a package, both caches need to -- be invalidated: package.registerCtr(name, pkg.registerPkg(name)) -- -- Possible interfaces: -- pkgtable = package.reload(name) -- but this needs work still -- -- Utility functions: -- chunk, err = package.loadFromPath(path, name) -- -- loads the chunk which results from compiling the file found by -- applying name to path. If the file isn't found or there is a syntax -- error, returns nil and an error message. This uses the loadfile -- function in what package thinks is the global environment, and sets -- the global environment of the chunk to what package thinks is the -- global environment. -- -- for val = package.paths(path, name) do ... end -- -- produces each substitution in turn. -- -- Configuration: -- package.loadpackage -- -- is the function used by the package package to find constructors. -- To be compatible with loadfile, the function is expected to return -- a function of no arguments which returns the constructor. -- The find function has the interface: -- -- func, err = finder(name) -- -- where err is used only if func is nil, and is a string indicating -- the error. -- -- package.path -- The default path for the default package loader (which uses -- loadFromPath). -- -- Note 1: -- loadFromPath depends on paths and loadfile. paths depends on the string -- library. If the string library isn't present (!), paths will return the -- null iterator. If loadfile isn't present, loadFromPath will return nil -- and an error. (If string isn't present and loadfile is, loadfile will -- never be called.) -- -- Note 2: -- If you want to substitute your own loadfile, it must return an -- error message starting "cannot read" if the file (or whatever) doesn't -- exist. This is crude, but I can't see another way of doing it. -- -- Note 3: -- package is itself a packaged object (not a module), but that only helps when -- constructing sandboxes. The constructor takes a table which will be the -- global table, and inserts the packaging utilities. -- some default functions local empty = function() end local ident = function(x) return x end -- This will need to be changed if string needs to be preinitialised local strfind = string and string.find or empty local gsub = string and string.gsub or ident -- safe version of setfenv. Note that the arguments are -- reversed, so that it can be used with loadfile, simply -- passing through error indications. local mysetfenv = setfenv and function(env, func, err) if type(func) == "function" then setfenv(func, env) else func = nil end return func, not func and (err or "mysetfenv failed") end or function(_, func) return func end -- The actual package constructor, which is put into the initial -- constructor cache local function packageCtr(g) g = g or {} -- FIXME local package = {} local _CTR = {package = packageCtr} local _PKG = {package = package} -- On the initial call, g should be the globals table. If this is called -- with new "package", it's up to the caller to supply a globals table -- if they have one set up. -- if loadfile and/or loadpackage don't exist, set them to something which -- always fails. They can always be updated later. loadfile is used by the -- default loadpackage; we probably don't actually need both of them. if not g.loadfile then function g.loadfile(modname) return nil, "cannot read '"..modname .. "'; no package loader" end end if not package.loadpackage then function package.loadpackage(modname) return nil, "cannot read '"..modname .. "'; no package loader" end end if not package.path then package.path = "" end -- shim function which runs the chunk in order to get the constructor -- This is not ideal; if Lua changes to allow chunks to have arguments, -- this could just be ditched. local function loadfileshim(fname) local chunk, err = mysetfenv(g, g.loadfile(fname)) if chunk then return chunk() else return chunk, err end end local function new(pkgname) local ctr, err = _CTR[pkgname] if ctr then return ctr else ctr, err = package.loadpackage(pkgname) _CTR[pkgname] = ctr return ctr, err end end g.new = new -- import is subtler because we need to precreate the package table -- in case we have a circular reference. -- TODO: -- Should actually use Wim's trigger to report an error on use of circular -- dependency. -- Or maybe it's ok to just have a stack overflow error instead of trying. -- We might want to track module dependencies here. function g.import(pkgname) local pkg = _PKG[pkgname] if pkg then return pkg else pkg = {} end _PKG[pkgname] = pkg local ok, rv, err = pcall(new, pkgname) if ok and rv then ok, rv, err = pcall(rv, pkg) end if ok and rv then return rv end -- If there was an error we need to reset the package cache and -- propagate the error. _PKG[pkgname] = nil -- First, figure out which one is the error :) if not ok then err = rv else err = err or "module returned nothing" end -- The error might or might not have a line number and might or -- might not have a traceback. We use a heuristic: if strfind(err, "^[^\n]*:%d+:") then -- seems to have a line number at least, so we'll add our own -- whine at the beginning. err = "import failed for '" .. pkgname .. "':\n" .. err end error(err, 2) end -- (Un)register packages and/or constructors. -- Perhaps new/import should actually use these functions :) function package.registerCtr(name, func, x) _CTR[name] = func return func, x end function package.registerPkg(name, pkgtable, x) _PKG[name] = pkgtable return pkgtable, x end -- This stuff should probably go somewhere else. But where? -- This would probably have been easier as a coroutine local function paths(path, name) local ee = 1 local function aux(path) local _, e, seg = strfind(path, "([^;]+)", ee) if seg then ee = e + 2; return (gsub(seg, "%?", name)) end end return aux, path, name end package.paths = paths function package.loadFromPath(path, name) for seg in paths(path, name) do local ctr, err = loadfileshim(seg) if ctr then return ctr end -- we found it, fine -- (presumably) the error result is either a read error or -- a syntax error. "Read error" is not quite good enough; -- we would like to continue on file not found but stop on, -- for example, removable media ejected during read. Furthermore, -- there are possibilities other than syntax error, but we're -- going to try to suppress tracebacks on those, so there. local _, _, errline = strfind(err or "", "(^[^\n]+)") if errline and not strfind(errline, "^cannot read") then -- there was a syntax error, return it. return ctr, errline end -- otherwise, keep trying end return nil, "cannot find '"..name.."' in '"..path.."'" end -- return the newly created globals table. return g end -- If we've done this right, we'll only be called once by a correctly -- functioning program. Hope that's true, cause we're going to construct -- the initial package in the (supposed) root environment. packageCtr(getfenv()) package = import "package" -- The initialisation makes it possible for import to work with -- standard libraries, without modifying code. -- -- We only put the library packages into _PKG, not into _CTR, for now. -- If any library is not in the globals table, the corresponding line -- is a silent no-op. package.registerPkg("table", table) package.registerPkg("string", string) package.registerPkg("math", math) package.registerPkg("io", io) package.registerPkg("os", os) package.registerPkg("coroutine", coroutine) package.registerPkg("coro", coroutine) -- coroutine is too long :) package.registerPkg("debug", debug) -- default path package.path = LUA_PATH or (os and os.env and os.env "LUA_PATH") or "?.lua;?" -- default top-level loadpackage function package.loadpackage(fname) return package.loadFromPath(package.path, fname) end
Produced by TNT, the Lua-linter. TNT/0.5 Copyright (C) 2004 Rici Lake