2
votes

I'm making a Lua library for Love2D, which contains quite a bit of internal sub-modules, class files, and the like.

What I'm doing now looks like this:

File ./libname/init.lua
lib.prefix = (...):match("(.-)[^%.]+$") .. "libname."
lib = {}

lib.class = require(lib.prefix .. "lib.class")
lib.types.Blah = require(lib.prefix .. "types.Blah")

return lib
File ./libname/types/Blah.lua
local Blah = lib.class()
...
return Blah

Except the thing here is that lib is a global, and if I make it into a local, I cannot properly structure the submodules like Blah, because they no longer have access to the lib table.

This is obviously a stripped down example, but I think it demonstrates my problem well - I would like to make the lib table local, and return it, so that inclusion of the library goes like lib = require "libs.libname", rather than have the whole thing imported into the global scope when I require the module itself. Is it possible?

2

2 Answers

3
votes

I made a guide for this very concept. You can find it here:

http://kiki.to/blog/2014/03/30/a-guide-to-authoring-lua-modules/

To solve the specific problem that you mention in your question, I would use 3 files: core.lua to share the state, the "real files" which change the core, and init to tie everything up.

./libname/core.lua is where lib is defined, as a local bar. It does not define lib.types. It "sets the ground up" with utilities that other files might want to use, like setting up a prefix or a utility class.

local lib = {}

lib.prefix = (...):match("(.-)[^%.]+$") .. "libname."

lib.class = require(lib.prefix .. "lib.class")

return lib

The 'regular files', like ./libname/types/Blah.lua, use those utilities, but don't modify lib at all:

local lib = require 'core' -- or libname.core or using the current_folder trick

local Blah = lib.class()
...
return Blah

init.lua binds everything together:

local lib = require 'core' -- or libname.core or current_folder trick

lib.types.Blah = require(lib.prefix .. "types.Blah")

return lib

The "current folder trick" mentioned in the comments is here: http://kiki.to/blog/2014/04/12/rule-5-beware-of-multiple-files/#the-current_folder-trick

1
votes

Let's take the relevant paragraph from the documentation:

require (modname)

[...]

Once a loader is found, require calls the loader with two arguments: modname and an extra value dependent on how it got the loader. (If the loader came from a file, this extra value is the file name.) If the loader returns any non-nil value, require assigns the returned value to package.loaded[modname]. If the loader does not return a non-nil value and has not assigned any value to package.loaded[modname], then require assigns true to this entry. In any case, require returns the final value of package.loaded[modname].

So, the way to go for a module with such recursive dependencies is:

  1. Get the module name modname (first unnamed var-arg argument)
  2. Load modules needed for basic initializing
  3. Return if the module was fully set-up in 2 (recursive call)
  4. Set up the module-table under package.loaded[modname]
  5. Set up enough scaffolding to start the sub-modules
  6. require the sub-modules
  7. Do the rest of the modules setup.
  8. Return (No need to return anything due to step 4).
local _M, modname = {}, {...}[1]
local sub = require(modname..".sub")
if package.loaded[modname] then return end
package.loaded[modname] = _M
-- Populate _M with everything needed to set up more modules
_M.X = require(modname..".X")
--Do the rest of this modules setup and that's it
--return

That allows you to not create any globals at all, as is proper for modern modules.


If you do not need any submodules while setting up the main-module, consider using on-demand-loading instead:

setmetatable(_M, {__index =
  function(t, k)
    t[k] = require(modname.."."..k)
    return t[k]
  end})

Should your module only be structured into submodules for external consumption, you could put it all in the main-module and let it register the appropriate member-tables as sub-modules in their own right (See step 2).
In that case, the sub-module loaders would only require the main-module and return nothing.