Create a set of interdependent Lua files without affecting the global namespace

tl; dr : what design template allows you to split the Lua code into several files that need to share some information without affecting the global table?

Background

Bad form for creating a library in Lua, where it is required for the library to affect the global namespace:

--> somelib.lua <--
SomeLib = { ... }

--> usercode.lua <--
require 'somelib'
print(SomeLib) -- global key created == bad

Instead, it is considered best practice to create a library that uses local variables, and then returns them for the user to designate as they see fit:

--> somelib.lua <--
local SomeLib = { ... }
return SomeLib

--> usercode.lua <--
local theLib = require 'somelib' -- consumers name lib as they wish == good

The above template works fine when using a single file. However, this becomes much more difficult if you have several files that reference each other.

Concrete example

, ? . ( , ;)

--> test_usage.lua <--
require 'master'

assert(MASTER.Simple)
assert(MASTER.simple)
assert(MASTER.Shared)
assert(MASTER.Shared.go1)
assert(MASTER.Shared.go2)
assert(MASTER.Simple.ref1()==MASTER.Multi1)
assert(pcall(MASTER.Simple.ref2))
assert(_G.MASTER == nil)                   -- Does not currently pass 

 

--> master.lua <--
MASTER = {}
require 'simple'
require 'multi'
require 'shared1'
require 'shared2'
require 'shared3'
require 'reference'

--> simple.lua <--
MASTER.Simple = {}
function MASTER:simple() end

--> multi.lua <--
MASTER.Multi1 = {}
MASTER.Multi2 = {}

--> shared1.lua <--
MASTER.Shared = {}

--> shared2.lua <--
function MASTER.Shared:go1() end

--> shared3.lua <--
function MASTER.Shared:go2() end

--> reference.lua <--
function MASTER.Simple:ref1() return MASTER.Multi1 end
function MASTER.Simple:ref2() MASTER:simple()      end

:

, . , require, :

--> master.lua <--
foo = "original"
local MASTER = setmetatable({foo="captured"},{__index=_G})
MASTER.MASTER = MASTER
setfenv(1,MASTER)
require 'simple'

--> simple.lua <--
print(foo)         --> "original"
MASTER.Simple = {} --> attempt to index global 'MASTER' (a nil value)
+5
5

master.lua :

(1) :

--> common.lua <--
return {}

--> master.lua <--
require 'simple'
require 'multi'
require 'shared1'
require 'shared2'
require 'shared3'
require 'reference'
return require'common' -- return the common table

--> simple.lua <--
local MASTER = require'common' -- import the common table
MASTER.Simple = {}
function MASTER:simple() end

.

, test_usage.lua :

--> test_usage.lua <--
local MASTER = require'master'
...

.

+4

. Git, , : https://github.com/catwell/dont-touch-global-namespace/commit/34b390fa34931464c1dc6f32a26dc4b27d5ebd69

, , , .

, master.lua, loadstring, ( master.lua , ), , . :)

EDIT: Andrew Stark, , MASTER . , , , simple.lua, multi.lua reference.lua.

+3

, , , :

--> master.lua <--
local m = {}                        -- The actual master table
local env = getfenv(0)              -- The current environment
local sandbox = { MASTER=m }        -- Environment for all requires
setmetatable(sandbox,{__index=env}) -- ...also exposes read access to real env

setfenv(0,sandbox)                  -- Use the sandbox as the environment
-- require all files as before
setfenv(0,env)                      -- Restore the original environment

return m

sandbox - , _G, MASTER, . , , "" .

, - , .

+1

:

  • .
  • , , .

"return as table" Lua, , , .

, . , , , , . , , local a = require('a').

, :

--callbacks.lua a -- sub-module
return function(self)
    local callbacks = {}
    callbacks.StartElement =  function(parser, elementName, attributes)
        local res = {}
            local stack = self.stack

    ---awesome stuff for about 150 lines...

    return callbacks
end

, ...

local make_callbacks = require'callbacks'
self.callbacks = make_callbacks(self)

, , require , :

self.callbacks = require'trms.xml.callbacks'(self)

. , , . , , , . , - , , .

, , , , , , . , . , , - , , , .:)

, local. ...

, , . , , reset _ENV, - , . , _ENV.

_ENV = {print = print, 
    pairs = pairs, --etc
}

, , lua , - , . , . _ENV.lua.

. "init.lua" , , , , ...

_ENV.lua :

--_ENV.lua
_ENV = {
    type = type,  pairs = pairs,  ipairs = ipairs,  next = next,  print =
    print,  require = require, io = io,  table = table,  string = string,        
    lxp = require"lxp", lfs = require"lfs",
    socket = require("socket"), lpeg = require'lpeg', --etc..
}
return _ENV

. , :

 _ENV = require'root_mod._ENV' --where root_mod is the base of my module.

. -, . , - _G ( , , tostring!), _ENV.lua . , , , - 0 .

-, , , "return module as table", , " , ".

+1

TL; DR: return , package.loaded[...] = your_module ( ), require , .


- require, . :

require (modname)

. package.loaded, , modname. , require , package.loaded[modname]. [ , .] . [ Lua , .]

[...]

, require : modname , , . ( , - .) , . return ], require package.loaded[modname]. , , package.loaded[modname], require true. require package.loaded[modname].

( , [] .)

return mymodule , - . ( ( !), .)

local _M = { }           -- your module, however you define / name it
package.loaded[...] = _M -- recall: require calls loader( modname, something )
                 -- so `...` is `modname, something` which is shortened
                 -- to just `modname` because only one value is used

, require return ed. (, , , ).

package.loaded[...] = mymodule 5.1-5.3 ( LuaJIT).


master.lua

1c1,2
< MASTER = {}
---
> local MASTER = {}
> package.loaded[...] = MASTER

0a1
> local MASTER = require "master"

.

+1

All Articles