This is a read-only snapshot of the ComputerCraft forums, taken in April 2020.
Janne's profile picture

libloader: Lua require support (5.3)

Started by Janne, 31 May 2018 - 10:50 PM
Janne #1
Posted 01 June 2018 - 12:50 AM
libloader

Decided to write a small library that adds support for lua require. I see now after writing this utility that it has been done before by Oddstr13 thought this one is a bit different. Instead of being based on 5.1 docs I based it on 5.3 docs, the only difference afaik being that 5.3 renamed package.loaders to package.searchers.

One thing this library supports which Oddstr13 didn't is the possibility of loading libraries from the program's working directory by using "./" in the package.path string

Basic usage

local _libloader = dofile("path/to/libloader")
local mylibrary = require("mylibrary")

mylibrary.doSomething()
By default libloader is set up to look for libraries in the program's working directory (including <workdir>/lib and <workdir>/bin)
if it doesn't find anything there it will look in /lib and /bin
By default it will not find non lua files when looking in <workdir> due to the possibility of accidentally loading
a non library like a config file.

package.path is set to "./?.lua;./lib/?.lua;./lib/?;./bin/?.lua;./bin/?;/lib/?.lua;/lib/?;/bin/?.lua;/bin/?"
but it can be changed by calling setPath

local _libloader = dofile("path/to/libloader")

_libloader.setPath("./?.lua")

local mylibrary = require("mylibrary")

mylibrary.doSomething()


Lua 5.3 doc: http://www.lua.org/m...al.html#Modules
Pastebin: https://pastebin.com/tt8eQALP

Moonscript source:
Spoiler

preload = {}
loaded = {}
searchers = {}

searchpath = (name, path, sep=".", rep="/") ->
  err = "Could not find #{name}\n"

  currDir = shell.getRunningProgram()
  path = path\gsub("%./", "#{currDir}/")
  name = name\gsub("[#{sep}]", rep)
  path = path\gsub("[?]", name)
  for test in path\gmatch("[^;]+")
	if fs.exists(test) and not fs.isDir(test)
	  return test
	else
	  err ..= "  #{test}\n"

  return nil, err


preloadSearcher = (name) ->
  if package.preload[name]
	return package.preload[name]
  return nil, "Could not find #{name} in package.preload"


simplePathSearcher = (name) ->
  path = searchpath(name, package.path)
  return dofile, path


table.insert(searchers, preloadSearcher)
table.insert(searchers, simplePathSearcher)


export require = (name) ->
  errors = {}
  if not loaded[name]
	loader = nil
	arg = nil
	for _, searcher in ipairs(searchers)
	  loader, arg = searcher(name)
	  if loader != nil
		break
	  else
		table.insert(errors, arg)
	if loader != nil
	  status, res = xpcall(
		() ->
		  loader(arg),
		(err) ->
		  return "An error occured while loading #{name} #{tostring(err)}"
	  )
	  if(not status)
		error(res)

	  loaded[name] = res or {}

	else
	  errStr = "Failed to load module #{name}\n"
	  for _, err in ipairs(errors)
		errStr ..= "#{err}\n"
	  error(errStr)

  return loaded[name]


export package = {
  :preload,
  :searchers,
  :loaded,
  path: "./?.lua;./lib/?.lua;./lib/?;./bin/?.lua;./bin/?;/lib/?.lua;/lib/?;/bin/?.lua;/bin/?",
  :searchpath
}


return {
  setPath: (path) ->
	package.path = path  
}

Transpiled lua:
Spoiler

local preload = { }
local loaded = { }
local searchers = { }
local searchpath
searchpath = function(name, path, sep, rep)
  if sep == nil then
	sep = "."
  end
  if rep == nil then
	rep = "/"
  end
  local err = "Could not find " .. tostring(name) .. "\n"
  local currDir = shell.getRunningProgram()
  path = path:gsub("%./", tostring(currDir) .. "/")
  name = name:gsub("[" .. tostring(sep) .. "]", rep)
  path = path:gsub("[?]", name)
  for test in path:gmatch("[^;]+") do
	if fs.exists(test) and not fs.isDir(test) then
	  return test
	else
	  err = err .. "  " .. tostring(test) .. "\n"
	end
  end
  return nil, err
end
local preloadSearcher
preloadSearcher = function(name)
  if package.preload[name] then
	return package.preload[name]
  end
  return nil, "Could not find " .. tostring(name) .. " in package.preload"
end
local simplePathSearcher
simplePathSearcher = function(name)
  local path = searchpath(name, package.path)
  return dofile, path
end
table.insert(searchers, preloadSearcher)
table.insert(searchers, simplePathSearcher)
require = function(name)
  local errors = { }
  if not loaded[name] then
	local loader = nil
	local arg = nil
	for _, searcher in ipairs(searchers) do
	  loader, arg = searcher(name)
	  if loader ~= nil then
		break
	  else
		table.insert(errors, arg)
	  end
	end
	if loader ~= nil then
	  local status, res = xpcall(function()
		return loader(arg)
	  end, function(err)
		return "An error occured while loading " .. tostring(name) .. " " .. tostring(tostring(err))
	  end)
	  if (not status) then
		error(res)
	  end
	  loaded[name] = res or { }
	else
	  local errStr = "Failed to load module " .. tostring(name) .. "\n"
	  for _, err in ipairs(errors) do
		errStr = errStr .. tostring(err) .. "\n"
	  end
	  error(errStr)
	end
  end
  return loaded[name]
end
package = {
  preload = preload,
  searchers = searchers,
  loaded = loaded,
  path = "./?.lua;./lib/?.lua;./lib/?;./bin/?.lua;./bin/?;/lib/?.lua;/lib/?;/bin/?.lua;/bin/?",
  searchpath = searchpath
}
return {
  setPath = function(path)
	package.path = path
  end
}

Edited on 01 June 2018 - 11:44 AM
EveryOS #2
Posted 01 June 2018 - 12:25 PM
I know this one is 5.3, but would it not be better off if the user just updated to 1.8? If they aren't using 1.12.2, it can be obtained here
Janne #3
Posted 01 June 2018 - 01:49 PM
Not quite sure I understand, does 1.8 support require? If it does that is great!
Bomb Bloke #4
Posted 01 June 2018 - 02:54 PM
Yep.

https://github.com/dan200/ComputerCraft/commit/61b2ed36a96c4c1977d39b9bc27c644f311a886c