How to organize Lua module path and write "require" calls without losing flexibility?

15,159

Solution 1

When you require a module, the string parameter from require gets passed into the module which you can access using the variable-argument syntax .... You can use this to include other dependent modules which reside in the same path as the current module being requireed without making it dependent on a fixed hard-coded module name.

For your example, instead of doing:

-- model/a.lua
require "model.b"

and

-- view/a.lua
require "view.b"

You can do:

-- model/a.lua
local thispath = select('1', ...):match(".+%.") or ""
require(thispath.."b")

and

-- view/a.lua
local thispath = select('1', ...):match(".+%.") or ""
require(thispath.."b")

Now if you change directory structure, eg. move view to something like control/subcontrol/foobar, then control/subcontrol/foobar/a.lua (formerly view/a.lua) will now try to require control/subcontrol/foobar/b.lua instead and "do the right thing".

Of course main.lua will still need to fully qualify the paths since you need some way to disambiguate between model/a.lua and view/a.lua.

Solution 2

How to fix the module path issue without hard code paths in module files?

I don't have any better cross-platform solution, maybe you should plan the folder structure early on.

Why Lua doesn't use the module search rule of Node.js, which looks easier?

Because Lua tries its best to rely only on ANSI C, which is really successful. And in ANSI C, there's no such concept of directories.

Solution 3

There are a couple approaches you can use.

You can add relative paths to package.path as in this SO answer. In your case you'd want to add paths in main.lua that correspond to the various ways you might access the files. This keeps all the changes required when changing your directory structure local to one file.

You can add absolute paths to package.pathusing debug.getinfo -- this may be a little easier since you don't need to account for all the relative accesses, but you still need to do this in main.lua when changing your directory structure, and you need to do string manipulation on the value returned by debug.getinfo to strip the module name and add the subdirectory names.

> lunit = require "lunit"
> info = debug.getinfo(lunit.run, "S")
> =info.source
@/usr/local/share/lua/5.2/lunit.lua
> =info.short_src
/usr/local/share/lua/5.2/lunit.lua

Solution 4

The solution is to add the folder of main.lua (project root) to package.path in main.lua.

A naive way to support folders of 1 level deep:

-- main.lua
package.path = package.path .. ";../?.lua"

Note for requires in (project root) will look up files outside of project root, which is not desirable.

A better way of to use some library (e.g.: paths, penlight) to resolve the absolute path and add it instead:

-- main.lua
local projectRoot = lib.abspath(".")
package.path = package.path .. ";" .. projectRoot .. "/?.lua"

Then in you source use the folder name to scope the files:

-- model/a.lua
require "model.b"
-- you can even do this
require "view.b"

and

-- view/a.lua
require "view.b"
Share:
15,159
Chen
Author by

Chen

software developer

Updated on July 24, 2022

Comments

  • Chen
    Chen almost 2 years

    Say I have a project, whose folder structure looks like below:

    | main.lua
    |
    |---<model> // this is a folder
    |    |a.lua
    |    |b.lua
    |
    |---<view>
         |a.lua
         |b.lua
    

    model/a.lua requries model/b.lua: require "b"

    view/a.lua requries view/b.lua: require "b"

    main.lua requries files in model and view.


    Now I have problem to get these modules loaded correctly. I know I can fix it by changing the require calls to:

    model/a.lua: require "model.b"

    view/a.lua: require "view.b"

    But if I do that, I have to modify these files every time when I change the folder structure.

    So my questions are:

    1. How to fix the module path issue without hard code paths in module files?
    2. Why Lua doesn't use the module search rule of Node.js, which looks easier?
  • Chen
    Chen almost 11 years
    Thanks for your editing and answer. But projects always go big from small, and they always need refactor. It's really difficult to make a plan at early phases. As to no external dependency except ANSI C, I agree it's a big advantage. But this doesn't conflict with a more portable module search rule.