Initial code
This commit is contained in:
parent
a0fcd80f0b
commit
bbccf2b18a
200
README.md
200
README.md
@ -1,4 +1,202 @@
|
||||
minetest-lib-ModLib
|
||||
===================
|
||||
|
||||
Library for Minetest mods and libraries to load other libraries with version requirements and one-time loading
|
||||
Library for Minetest mods and libraries to load other libraries with version
|
||||
requirements and one-time loading
|
||||
|
||||
General
|
||||
=======
|
||||
|
||||
This is NOT a "mod"; it is a LIBRARY for use by mods and other libraries. That
|
||||
means it does not modify how Minetest works in any way, but provides a useful
|
||||
API to help mods do so. This particular library is used purely for managing
|
||||
the loading of other libraries, and has no dependencies.
|
||||
|
||||
Installation
|
||||
============
|
||||
|
||||
The file "lib/ModLib.lua" in this package should be installed in a "lib"
|
||||
subdirectory of the main directory of the mod that depends on it. If other
|
||||
mods under the same game also include versions of ModLib, only one instance of
|
||||
any given version will be loaded. This is the same for any libraries that
|
||||
ModLib is used to load as well.
|
||||
|
||||
Other libraries the mod depends on, and that will be loaded using ModLib,
|
||||
should also be included in this "lib" subdirectory. Their filenames must have
|
||||
the form "LibName_version.lua". LibName is the name of the library, which must
|
||||
start with a letter and contain only letters and digits. And version is a
|
||||
version number like "x.y.z" (with any number of components) where the periods
|
||||
are replaced with dashes ("x-y-z"). Due to a definiciency in the current
|
||||
version of Lua, another file called "ModLib_list.txt" must exist in the "lib"
|
||||
subdirectory and must contain the name of each library file on a separate line,
|
||||
with no extra characters or empty lines. In Linux this list can be generated
|
||||
using the command "ls -1 >ModLib_list.txt" from within the "lib" subdirectory.
|
||||
|
||||
The ONLY exception to including the version number in the name of the library
|
||||
file is for "ModLib.lua", and that is because it includes special boostrap code
|
||||
and a version of it must be loadable from any mod that loads a library, without
|
||||
depending on a specific version during bootstrap.
|
||||
|
||||
For example, if a mod "my_mod" depends on LibMod 1.0 and version 2.3 of another
|
||||
mod library "MyLib" and is installed to:
|
||||
|
||||
.../mods/minetest/my_mod
|
||||
|
||||
then the following files must exist:
|
||||
|
||||
.../mods/minetest/my_mod/lib/ModLib_list.txt
|
||||
.../mods/minetest/my_mod/lib/ModLib.lua
|
||||
.../mods/minetest/my_mod/lib/MyLib_2-3.lua
|
||||
|
||||
and ModLib_list.txt must contain the following lines:
|
||||
|
||||
ModLib_list.txt
|
||||
ModLib.lua
|
||||
MyLib_2-3.lua
|
||||
|
||||
(The inclusion of "ModLib_list.txt" and "ModLib.lua" in this file is optional.)
|
||||
|
||||
Use
|
||||
===
|
||||
|
||||
All libraries a mod depends on should be included with the mod. This ensures
|
||||
these libraries are available to the mod whether or not they are also included
|
||||
in other mods, and whether or not the version in any other mods is incompatible
|
||||
with the version this mod needs. The exception would be if the mod depends on
|
||||
another mod and uses a library the dependency includes in a limited fashion
|
||||
rather than being directly dependent on details of the library API.
|
||||
|
||||
In order to load one of these dependent libraries from your mod through ModLib,
|
||||
first you must bootstrap ModLib, then add your "lib" subdirectory to its
|
||||
library load path, then ask it to load your library, specifying what versions
|
||||
of the library are compatible with your mod. For the above example using
|
||||
ModLib 1.0 and MyLib 2.3 this would look like:
|
||||
|
||||
-- Bootstrap ModLib
|
||||
local MOD_NAME = minetest.get_current_modname()
|
||||
local MOD_PATH = minetest.get_modpath(MOD_NAME)
|
||||
local ModLib = dofile(MOD_PATH.."/lib/ModLib.lua")
|
||||
|
||||
-- Add to the library path:
|
||||
ModLib.addDir(MOD_PATH.."/lib")
|
||||
|
||||
-- Load the dependent library. Note that this MyLib 2.3 OR LATER. If the
|
||||
-- exact version were required, or if a range of versions were acceptable,
|
||||
-- then a second (maximum) version number would be added to the call.
|
||||
local MyLib = ModLib.load("MyLib", "2.3")
|
||||
|
||||
Note carefully the use of local variable rather than globals. This MUST always
|
||||
be how a mod or library loads another library, in order to allow multiple
|
||||
versions to coexist. Note, however, that you may add the module API as a field
|
||||
of the module's own API in order to use it from other files or even other mods
|
||||
(see the note above about libraries included by dependent mods). So my_mod
|
||||
could then do this:
|
||||
|
||||
my_mod.MyLib = MyLib
|
||||
|
||||
which would allow my_mod.MyLib to be accessed from any Lua file in the mod or
|
||||
any other mod that depends on it.
|
||||
|
||||
The use of ModLib from another library is only slightly different. The library
|
||||
should assume that the mod that is loading it has added any library directories
|
||||
to the load path already, so it can skip this step. So from a library this
|
||||
looks like:
|
||||
|
||||
-- Bootstrap ModLib
|
||||
local LOADING_MOD = minetest.get_current_modname()
|
||||
local LOADING_MOD_PATH = minetest.get_modpath(MOD_NAME)
|
||||
local ModLib = dofile(LOADING_MOD_PATH.."/lib/ModLib.lua")
|
||||
|
||||
-- Load the dependent library, with a minimum version of 1.0.
|
||||
local MyOtherLib = ModLib.load("MyOtherLib", "1.0")
|
||||
|
||||
local MyLib = {}
|
||||
...
|
||||
return MyLib
|
||||
|
||||
Now let's assume that MyLib is updated a few times and everything works up to
|
||||
version 3.0, but then 3.1 comes along and gets included in a bunch of mods, and
|
||||
we discover that this update breaks my_mod. Until we can update my_mod to
|
||||
account for the changes, we make a one-line change to make sure it will not
|
||||
load anything above version 3.0 for my_mod:
|
||||
|
||||
local MyLib = ModLib.load("MyLib", "2.3", "3.0")
|
||||
|
||||
The other modules that can use version 3.1 will continue to use it and will not
|
||||
be affected by this change.
|
||||
|
||||
Advanced Use
|
||||
============
|
||||
|
||||
What if newer versions of ModLib come out, and your module depends on newer
|
||||
features? Well, there must always be an unversioned "lib/ModLib.lua" that any
|
||||
mod may load during bootstrap (from any mod that might load the library).
|
||||
However, ModLib itself acts as another library that it can load by version, and
|
||||
it is perfectly okay to have both the unversioned file name (with any actual
|
||||
version in it) and versioned filenames in the load path. So just go ahead and
|
||||
include your newer version of ModLib twice in the mod, with the two paths
|
||||
"lib/ModLib.lua" and "lib/ModLib_x-y-z.lua".
|
||||
|
||||
What about more complex version dependencies? The API currently allows a
|
||||
minimum version of a library to be specified, a maximum version, or both. What
|
||||
if there are multiple ranges, or one particular plagued version that we want to
|
||||
exclude? In future versions of ModLib the API may allow for more complex
|
||||
version specification, but for now you can take advantage of the builtin error
|
||||
handling mechanism of Lua to successively try to load acceptable versions or
|
||||
ranges.
|
||||
|
||||
For example, take the above scenario and assume that MyLib comes out with
|
||||
version 3.2 that fixes the issues my_mod had with version 3.1. How can we load
|
||||
verison 3.2 if it is available, and otherwise load versions 2.3 through 3.0?
|
||||
First, we could choose to obsolete use of the older versions and switch my_mod
|
||||
to ship with version 3.2 instead of the old 2.3 to ensure it is available. But
|
||||
we could also do this:
|
||||
|
||||
local status, MyLib = pcall(ModLib.load, "MyLib", "3.2")
|
||||
if not (status and MyLib) then
|
||||
status, MyLib = pcall(ModLib.load, "2.3", "3.0")
|
||||
end
|
||||
if not (status and MyLib) then
|
||||
error("No compatible version of MyLib found")
|
||||
end
|
||||
|
||||
Suggested Version Dependencies
|
||||
==============================
|
||||
|
||||
How specific you make the version requirements is really up to you. It depends
|
||||
on how you want to balance stability versus flexibility. For maximum
|
||||
stability, you should require only versions that you have thoroughly tested
|
||||
your mod or library against. For more flexibility, allowing future library
|
||||
versions that might fix defects or enhanced behavior can be enabled by only
|
||||
requiring a minimum version, at least until such time as up update to the
|
||||
library breaks things.
|
||||
|
||||
A middle ground could be to require exact versions of unstable or uncertain
|
||||
libraries, but allow libraries from more consistent developers to update
|
||||
without limit, or until some large minor version number (e.g. 2.99999) but not
|
||||
the next major version (e.g. 3.0).
|
||||
|
||||
Testing
|
||||
=======
|
||||
|
||||
A couple of unit tests are included with ModLib. From the base directory of
|
||||
the package, you can run the following, and should get the listed output:
|
||||
|
||||
$ lua test/ModLib_test.lua
|
||||
|
||||
ModLib tests PASSED
|
||||
|
||||
$ lua test/ModLib_Version.lua
|
||||
|
||||
ModLib.Version tests PASSED
|
||||
|
||||
These tests can also be run within Minetest by including the ModLib library in
|
||||
a mod in the normal way, adding the "test" directory and all contents from this
|
||||
package as a subdirectory in the mod's main directory, and running the
|
||||
following from the mod's code:
|
||||
|
||||
local MOD_NAME = minetest.get_current_modname()
|
||||
local MOD_PATH = minetest.get_modpath(MOD_NAME)
|
||||
dofile(MOD_PATH.."test/ModLib_test.lua")
|
||||
dofile(MOD_PATH.."test/ModLib_Version_test.lua")
|
||||
|
||||
|
348
lib/ModLib.lua
Normal file
348
lib/ModLib.lua
Normal file
@ -0,0 +1,348 @@
|
||||
-- Constants that must NEVER change
|
||||
|
||||
local REGISTRY_KEY = "ModLib:registry"
|
||||
local LIB_DIRS_KEY = "ModLib:libdirs"
|
||||
local THIS_LIB_NAME = "ModLib"
|
||||
|
||||
|
||||
-- Constants for THIS version
|
||||
|
||||
local THIS_VERSION_STR = "1.0"
|
||||
|
||||
local LIB_NAME_PATT = "^[a-zA-Z][a-zA-Z0-9]*$"
|
||||
|
||||
local LIB_FILENAME_PATT = "^([a-zA-Z][a-zA-Z0-9]*)_([0-9]+)()"
|
||||
local LIB_FILENAME_EXTRA_PATT = "^%-([0-9]+)()"
|
||||
local LIB_FILENAME_END_PATT = "^%.lua$"
|
||||
|
||||
local HACKY_DIR_LIST_FILENAME = "ModLib_list.txt"
|
||||
|
||||
|
||||
-- Class Version, used to store both a hash and sorted table of library
|
||||
-- instances by canonicalized version number.
|
||||
|
||||
|
||||
--- Type initialized from "x.y.z" version strings, able to compare
|
||||
-- lexicographically, component by component (x, then y, then z, etc.).
|
||||
-- For example, if these versions are defined:
|
||||
--
|
||||
-- verA = ModLib.Version("1")
|
||||
-- verB = ModLib.Version("1.0")
|
||||
-- verC = ModLib.Version("1.0.0.3")
|
||||
-- verD = ModLib.Version("1.1")
|
||||
-- verE = ModLib.Version("2.0")
|
||||
--
|
||||
-- then the following expression will evaluate to true:
|
||||
--
|
||||
-- verA == verB and verB < verC and verC < verD and verD < verE
|
||||
--
|
||||
-- Converting a version back to a string will return the "canonicalized"
|
||||
-- version number by stripping all trailing zeroes (until there is only one
|
||||
-- component remaining). For example, given the above definitions, all of the
|
||||
-- following are true:
|
||||
--
|
||||
-- tostring(verB) == "1"
|
||||
-- tostring(verD) == "1.1"
|
||||
-- tostring(ModLib.Version("0.0.0")) = "0"
|
||||
-- tostring(ModLib.Version("0.1")) = "0.1"
|
||||
local Version = {}
|
||||
local Version_meta = {}
|
||||
local Version_inst_meta = {}
|
||||
setmetatable(Version, Version_meta)
|
||||
|
||||
local function Version_constr(class, verstr)
|
||||
self = {}
|
||||
|
||||
local len = string.len(verstr)
|
||||
local si = 1
|
||||
local ei, comp
|
||||
while si <= len do
|
||||
ei = string.find(verstr, ".", si, true)
|
||||
if not ei then ei = len+1 end
|
||||
if ei <= si or ei == len then
|
||||
self = nil
|
||||
break
|
||||
end
|
||||
comp = string.sub(verstr, si, ei-1)
|
||||
if not string.find(comp, "^[0-9]+$") then
|
||||
self = nil
|
||||
break;
|
||||
end
|
||||
table.insert(self, tonumber(comp))
|
||||
si = ei+1
|
||||
end
|
||||
if self then
|
||||
while #self > 1 and self[#self] == 0 do
|
||||
table.remove(self)
|
||||
end
|
||||
setmetatable(self, Version_inst_meta)
|
||||
end
|
||||
return self
|
||||
end
|
||||
Version_meta.__call = Version_constr
|
||||
|
||||
local function Version_cmp(ver1, ver2)
|
||||
local diff
|
||||
for i = 1, math.min(#ver1, #ver2) do
|
||||
diff = ver1[i] - ver2[i]
|
||||
if diff > 0 then return 1 elseif diff < 0 then return -1 end
|
||||
end
|
||||
diff = #ver1 - #ver2
|
||||
if diff > 0 then return 1 elseif diff < 0 then return -1 end
|
||||
return 0
|
||||
end
|
||||
Version_inst_meta.__eq = function(ver1, ver2)
|
||||
if #ver1 ~= #ver2 then return false end
|
||||
return (Version_cmp(ver1, ver2) == 0)
|
||||
end
|
||||
Version_inst_meta.__lt = function(ver1, ver2)
|
||||
return (Version_cmp(ver1, ver2) < 0)
|
||||
end
|
||||
|
||||
local function Version_concat(val1, val2)
|
||||
if type(val1) == "table" then
|
||||
val1 = tostring(val1)
|
||||
end
|
||||
if type(val2) == "table" then
|
||||
val2 = tostring(val2)
|
||||
end
|
||||
return val1..val2
|
||||
end
|
||||
Version_inst_meta.__concat = Version_concat
|
||||
|
||||
local function Version_tostring(ver)
|
||||
return table.concat(ver, ".")
|
||||
end
|
||||
Version_inst_meta.__tostring = Version_tostring
|
||||
|
||||
|
||||
-- Bootstrap stuff, so we only "load" this library once
|
||||
|
||||
local THIS_VERSION = Version(THIS_VERSION_STR)
|
||||
|
||||
local registry = _G[REGISTRY_KEY] or {}
|
||||
_G[REGISTRY_KEY] = registry
|
||||
|
||||
local libDirs = _G[LIB_DIRS_KEY] or {}
|
||||
_G[LIB_DIRS_KEY] = libDirs
|
||||
|
||||
local modLibVersions = registry[THIS_LIB_NAME] or {}
|
||||
registry[THIS_LIB_NAME] = modLibVersions
|
||||
local modLibVersionsByValue = modLibVersions.byValue or {}
|
||||
modLibVersions.byValue = modLibVersionsByValue
|
||||
local modLibVersionsSorted = modLibVersions.sorted or {}
|
||||
modLibVersions.sorted = modLibVersionsSorted
|
||||
|
||||
local ModLib = modLibVersionsByValue[tostring(THIS_VERSION)]
|
||||
if ModLib then return ModLib end
|
||||
|
||||
|
||||
-- Finally the ModLib class itself, with Version enclosed for external use:
|
||||
|
||||
|
||||
--- Used to load versions of libraries with version requirements, and load each
|
||||
-- version only once.
|
||||
--
|
||||
-- Example of use:
|
||||
--
|
||||
-- local MOD_NAME = minetest.get_current_modname()
|
||||
-- local MOD_PATH = minetest.get_modpath(MOD_NAME)
|
||||
-- local ModLib = dofile(MOD_PATH.."/lib/ModLib_1-0.lua")
|
||||
-- ModLib.addDir(MOD_PATH.."/lib")
|
||||
-- local MyLib = ModLib.load("MyLib", "i.j.k", "m.n.p")
|
||||
--
|
||||
-- where "i.j.k" and "m.n.p" are version strings indicating the minimum and
|
||||
-- maximum versions of "MyLib" that can be used by the module (with any number
|
||||
-- of components, not limited to the three shown here). This will attempt to
|
||||
-- load a file called "MyLib_a-b-c.lua" from any of the directories added
|
||||
-- through ModLib.addDir(dir) meeting the requirement that
|
||||
-- "i.j.k" <= "a.b.c" <= "m.n.p" lexicographically (after trailing zero
|
||||
-- components are stripped from each version string). If multiple files meet
|
||||
-- the requirement, the one with greatest "a.b.c" will be used. To require an
|
||||
-- exact version, use the same value for the minimum and maximum versions. To
|
||||
-- require ANY version (the highest version will be used), omit both
|
||||
-- versions (or use nil for them). To specify a half-open range, use nil for
|
||||
-- either (or omit the maximum).
|
||||
--
|
||||
-- These are the requirements for mod libraries:
|
||||
--
|
||||
-- 1.) The library name must begin with a letter, and must contain only
|
||||
-- letters and digits.
|
||||
-- 2.) They MUST NOT set any global variables except in the rare case that it
|
||||
-- is always going to be okay to share the value between ALL versions
|
||||
-- of the library (note the local variables in the example above). In
|
||||
-- particular, the "MyLib" variable here must NOT be set as a global
|
||||
-- variable by the library, and must instead be a local variable returned
|
||||
-- from the file.
|
||||
-- 3.) They MUST return the main class of the library itself from the file.
|
||||
-- 4.) They MAY optionally set MyLib.VERSION in the returned main class, but
|
||||
-- this will be overwritten by ModLib with the canonicalized version
|
||||
-- string "a.b.c", constructed from the name of the file the library is
|
||||
-- loaded from.
|
||||
--
|
||||
-- Note that ModLib itself is a mod library, so it could conceivably be used
|
||||
-- to load a different version of itself after the initial bootstrap loading
|
||||
-- (though it's unlikely you'd want to)!
|
||||
--
|
||||
-- Here is an example of a dirt simple mod library that defines a field and a
|
||||
-- function:
|
||||
--
|
||||
-- -- MyDumbLib_1-0.lua
|
||||
-- local MyDumbLib = {}
|
||||
-- MyDumbLib.value = 1
|
||||
-- function MyDumbLib.foo() print("foo!") end
|
||||
-- return MyDumbLib
|
||||
--
|
||||
-- NOTE: Due to a deficiency in the Lua 5.1 standard library, directories
|
||||
-- cannot be listed without the aid of a C extension to the API. Therefore,
|
||||
-- you must create a file called "ModLib_list.txt" in each directory added
|
||||
-- through ModLib.addDir(dir) that contains one simple filename per line of
|
||||
-- the valid library files in that directory. This can be done in Linux using
|
||||
-- the command "ls -1 >ModLib_list.txt" from within the directory. Hopefully
|
||||
-- this library becomes a part of the Minetest core at some point, and all of
|
||||
-- this can be done flawlessly from the C++ instead.
|
||||
ModLib = {}
|
||||
ModLib.Version = Version
|
||||
ModLib.VERSION = tostring(THIS_VERSION)
|
||||
|
||||
-- Returns index of the last array element from a sorted array less than or
|
||||
-- equal to a value, zero if all elements of the table are greater than the
|
||||
-- value or if the table is empty.
|
||||
local function binarySearchUpperBound(table, value)
|
||||
if #table < 1 then return 0 end
|
||||
if table[#table] <= value then return #table end
|
||||
if table[1] > value then return 0 end
|
||||
|
||||
local si = 1
|
||||
local ei = #table
|
||||
local mi = math.floor((si+ei)/2)
|
||||
local mv = table[mi]
|
||||
|
||||
while mi > si do
|
||||
local mv = table[mi]
|
||||
if mv <= value then
|
||||
si = mi
|
||||
else
|
||||
ei = mi
|
||||
end
|
||||
mi = math.floor((si+ei)/2)
|
||||
mv = table[mi]
|
||||
end
|
||||
|
||||
return si
|
||||
end
|
||||
|
||||
local function addLib(libName, ver, filePath)
|
||||
local verStr = tostring(ver)
|
||||
if not registry[libName].byValue[verStr] then
|
||||
local loader = loadfile(filePath)
|
||||
local lib = loader(libName)
|
||||
lib.VERSION = tostring(ver)
|
||||
|
||||
registry[libName].byValue[verStr] = lib
|
||||
|
||||
local sorted = registry[libName].sorted
|
||||
local pos = binarySearchUpperBound(sorted, ver)
|
||||
if not pos then pos = 0 end
|
||||
table.insert(sorted, pos+1, ver)
|
||||
end
|
||||
end
|
||||
|
||||
local function ModLib_load(libName, minVerStr, maxVerStr)
|
||||
local minVer = minVerStr and Version(minVerStr)
|
||||
local maxVer = maxVerStr and Version(maxVerStr)
|
||||
if minVerStr and not minVer then
|
||||
error("Bad version string '"..minVerStr.."'", 2)
|
||||
end
|
||||
if maxVerStr and not maxVer then
|
||||
error("Bad version string '"..maxVerStr.."'", 2)
|
||||
end
|
||||
if not string.match(libName, LIB_NAME_PATT) then
|
||||
error("Bad library name '"..libName.."'", 2)
|
||||
end
|
||||
if minVer and maxVer and minVer > maxVer then
|
||||
error("Min version greater than max version", 2)
|
||||
end
|
||||
|
||||
local libVersions = registry[libName] or {}
|
||||
registry[libName] = libVersions
|
||||
local libVersionsByValue = libVersions.byValue or {}
|
||||
libVersions.byValue = libVersionsByValue
|
||||
local libVersionsSorted = libVersions.sorted or {}
|
||||
libVersions.sorted = libVersionsSorted
|
||||
|
||||
if maxVer then
|
||||
local lib = libVersionsByValue[tostring(maxVer)]
|
||||
if lib then return lib end
|
||||
end
|
||||
|
||||
for i, dir in ipairs(libDirs) do
|
||||
local listFile = io.open(dir.."/"..HACKY_DIR_LIST_FILENAME)
|
||||
if listFile then
|
||||
listFile:close()
|
||||
for fileName in io.lines(dir.."/"..HACKY_DIR_LIST_FILENAME) do
|
||||
local name, verStr, i = string.match(fileName, LIB_FILENAME_PATT)
|
||||
if name and verStr and i then
|
||||
while i <= #fileName do
|
||||
local verComp, ni = string.match(fileName,
|
||||
LIB_FILENAME_EXTRA_PATT,
|
||||
i)
|
||||
if verComp and ni then
|
||||
verStr = verStr.."."..verComp
|
||||
i = ni
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
if not string.match(fileName, LIB_FILENAME_END_PATT, i) then
|
||||
name = nil
|
||||
verStr = nil
|
||||
end
|
||||
end
|
||||
if name and verStr and name == libName then
|
||||
local ver = Version(verStr)
|
||||
if (not minVer or ver >= minVer) and
|
||||
(not maxVer or ver <= maxVer)
|
||||
then
|
||||
local filePath = dir.."/"..fileName
|
||||
local file = io.open(filePath)
|
||||
if file then
|
||||
file:close()
|
||||
addLib(libName, ver, filePath)
|
||||
if maxVer and ver == maxVer then
|
||||
return libVersionsByValue[tostring(maxVer)]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local pos = (maxVer) and binarySearchUpperBound(libVersionsSorted, maxVer)
|
||||
or #libVersionsSorted
|
||||
local ver = (pos and pos >= 1 and pos <= #libVersionsSorted)
|
||||
and libVersionsSorted[pos]
|
||||
or nil
|
||||
if not ver or (minVer and ver < minVer) then
|
||||
local msg = "Library "
|
||||
if minVer then msg = msg..tostring(minVer).." < " end
|
||||
msg = msg.."'"..libName.."'"
|
||||
if maxVer then msg = msg.." < "..tostring(maxVer) end
|
||||
msg = msg.." not found"
|
||||
error(msg, 2)
|
||||
end
|
||||
|
||||
return libVersionsByValue[tostring(ver)]
|
||||
end
|
||||
ModLib.load = ModLib_load
|
||||
|
||||
local function ModLib_addDir(dir)
|
||||
if not libDirs[dir] then
|
||||
table.insert(libDirs, dir)
|
||||
libDirs[dir] = #libDirs
|
||||
end
|
||||
end
|
||||
ModLib.addDir = ModLib_addDir
|
||||
|
||||
return ModLib
|
41
test/ModLib_Version_test.lua
Normal file
41
test/ModLib_Version_test.lua
Normal file
@ -0,0 +1,41 @@
|
||||
local BASE_DIR
|
||||
if minetest then
|
||||
local LOADING_MOD = minetest.get_current_modname()
|
||||
BASE_DIR = minetest.get_modpath(LOADING_MOD)
|
||||
else
|
||||
BASE_DIR = os.getenv("PWD")
|
||||
end
|
||||
|
||||
local ModLib = dofile(BASE_DIR.."/lib/ModLib.lua")
|
||||
|
||||
local verA = ModLib.Version("1")
|
||||
local verB = ModLib.Version("1.0")
|
||||
local verC = ModLib.Version("1.0.0.3")
|
||||
local verD = ModLib.Version("1.1")
|
||||
local verE = ModLib.Version("2.0")
|
||||
|
||||
assert("1" == tostring(verA))
|
||||
assert("1" == tostring(verB))
|
||||
assert("1.0.0.3" == tostring(verC))
|
||||
assert("1.1" == tostring(verD))
|
||||
assert("2" == tostring(verE))
|
||||
|
||||
assert(verA == verB)
|
||||
assert(verB < verC)
|
||||
assert(verC < verD)
|
||||
assert(verD < verE)
|
||||
|
||||
assert(verA <= verB)
|
||||
assert(verB <= verC)
|
||||
assert(verC <= verD)
|
||||
assert(verD <= verE)
|
||||
|
||||
assert("0" == tostring(ModLib.Version("0.0.0")))
|
||||
assert("0.1" == tostring(ModLib.Version("0.1")))
|
||||
|
||||
local verF = ModLib.Version("3.14")
|
||||
local verG = ModLib.Version("3.4")
|
||||
|
||||
assert(verG < verF)
|
||||
|
||||
print("ModLib.Version tests PASSED")
|
94
test/ModLib_test.lua
Normal file
94
test/ModLib_test.lua
Normal file
@ -0,0 +1,94 @@
|
||||
local BASE_DIR
|
||||
if minetest then
|
||||
local LOADING_MOD = minetest.get_current_modname()
|
||||
BASE_DIR = minetest.get_modpath(LOADING_MOD)
|
||||
else
|
||||
BASE_DIR = os.getenv("PWD")
|
||||
end
|
||||
|
||||
local ModLib = dofile(BASE_DIR.."/lib/ModLib.lua")
|
||||
ModLib.addDir(BASE_DIR.."/lib")
|
||||
ModLib.addDir(BASE_DIR.."/test/lib")
|
||||
|
||||
assert(not pcall(ModLib.load, "Zoog"))
|
||||
|
||||
local MyLib1 = ModLib.load("MyLib", "1", "1")
|
||||
local MyLib1b = ModLib.load("MyLib", "1.0", "1.0")
|
||||
|
||||
assert(MyLib1)
|
||||
assert(MyLib1b)
|
||||
assert(MyLib1 == MyLib1b)
|
||||
assert("1" == MyLib1.VERSION)
|
||||
assert("1.0" == MyLib1.v)
|
||||
|
||||
local MyLib1_1 = ModLib.load("MyLib", "1.1", "1.1")
|
||||
|
||||
assert(MyLib1_1)
|
||||
assert(MyLib1_1 ~= MyLib1)
|
||||
assert("1.1" == MyLib1_1.VERSION)
|
||||
assert("1.1" == MyLib1_1.v)
|
||||
|
||||
local MyLib2 = ModLib.load("MyLib", "2.0.0.0.0.0", "2")
|
||||
|
||||
assert(MyLib2)
|
||||
assert(MyLib2 ~= MyLib1)
|
||||
assert(MyLib2 ~= MyLib1_1)
|
||||
assert("2" == MyLib2.VERSION)
|
||||
assert("2.0.0" == MyLib2.v)
|
||||
|
||||
local MyLib2_0_5 = ModLib.load("MyLib", "2.0.5", "2.0.5")
|
||||
|
||||
assert(MyLib2_0_5)
|
||||
assert(MyLib2_0_5 ~= MyLib1)
|
||||
assert(MyLib2_0_5 ~= MyLib1_1)
|
||||
assert(MyLib2_0_5 ~= MyLib2)
|
||||
assert("2.0.5" == MyLib2_0_5.VERSION)
|
||||
assert("2.0.5" == MyLib2_0_5.v)
|
||||
|
||||
local MyLib_any = ModLib.load("MyLib")
|
||||
|
||||
assert(MyLib_any)
|
||||
assert(MyLib_any == MyLib2_0_5)
|
||||
|
||||
local MyLib_min0_5 = ModLib.load("MyLib", "0.5")
|
||||
local MyLib_min1_0_5 = ModLib.load("MyLib", "1.0.5")
|
||||
local MyLib_min1_5 = ModLib.load("MyLib", "1.0.5")
|
||||
local MyLib_min2 = ModLib.load("MyLib", "2")
|
||||
assert(not pcall(ModLib.load, "MyLib", "3"))
|
||||
|
||||
assert(MyLib_min0_5)
|
||||
assert(MyLib_min1_0_5)
|
||||
assert(MyLib_min1_5)
|
||||
assert(MyLib_min2)
|
||||
assert(MyLib_min0_5 == MyLib2_0_5)
|
||||
assert(MyLib_min1_0_5 == MyLib2_0_5)
|
||||
assert(MyLib_min1_5 == MyLib2_0_5)
|
||||
assert(MyLib_min2 == MyLib2_0_5)
|
||||
|
||||
assert(not pcall(ModLib.load, "MyLib", nil, "0.5"))
|
||||
local MyLib_max1 = ModLib.load("MyLib", nil, "1")
|
||||
local MyLib_max1_0_1 = ModLib.load("MyLib", nil, "1.0.1")
|
||||
local MyLib_max1_1 = ModLib.load("MyLib", nil, "1.1")
|
||||
local MyLib_max1_5 = ModLib.load("MyLib", nil, "1.5")
|
||||
local MyLib_max2 = ModLib.load("MyLib", nil, "2")
|
||||
local MyLib_max3 = ModLib.load("MyLib", nil, "3")
|
||||
|
||||
assert(MyLib_max1 == MyLib1)
|
||||
assert(MyLib_max1_0_1 == MyLib1)
|
||||
assert(MyLib_max1_1 == MyLib1_1)
|
||||
assert(MyLib_max1_5 == MyLib1_1)
|
||||
assert(MyLib_max2 == MyLib2)
|
||||
assert(MyLib_max3 == MyLib2_0_5)
|
||||
|
||||
assert(not pcall(ModLib.load, "MyLib", "2", "1"))
|
||||
local MyLib_1_to_2 = ModLib.load("MyLib", "1", "2")
|
||||
local MyLib_1_1_to_2 = ModLib.load("MyLib", "1.1", "2")
|
||||
local MyLib_2_to_3 = ModLib.load("MyLib", "2", "3")
|
||||
assert(not pcall(ModLib.load, "MyLib", "2.5", "3"))
|
||||
|
||||
assert(MyLib_1_to_2)
|
||||
assert(MyLib_1_to_2 == MyLib2)
|
||||
assert(MyLib_1_1_to_2 == MyLib2)
|
||||
assert(MyLib_2_to_3 == MyLib2_0_5)
|
||||
|
||||
print("ModLib tests PASSED")
|
4
test/lib/ModLib_list.txt
Normal file
4
test/lib/ModLib_list.txt
Normal file
@ -0,0 +1,4 @@
|
||||
MyLib_1-0.lua
|
||||
MyLib_1-1.lua
|
||||
MyLib_2-0-0.lua
|
||||
MyLib_2-0-5.lua
|
3
test/lib/MyLib_1-0.lua
Normal file
3
test/lib/MyLib_1-0.lua
Normal file
@ -0,0 +1,3 @@
|
||||
local MyLib = {}
|
||||
MyLib.v = "1.0"
|
||||
return MyLib
|
3
test/lib/MyLib_1-1.lua
Normal file
3
test/lib/MyLib_1-1.lua
Normal file
@ -0,0 +1,3 @@
|
||||
local MyLib = {}
|
||||
MyLib.v = "1.1"
|
||||
return MyLib
|
3
test/lib/MyLib_2-0-0.lua
Normal file
3
test/lib/MyLib_2-0-0.lua
Normal file
@ -0,0 +1,3 @@
|
||||
local MyLib = {}
|
||||
MyLib.v = "2.0.0"
|
||||
return MyLib
|
3
test/lib/MyLib_2-0-5.lua
Normal file
3
test/lib/MyLib_2-0-5.lua
Normal file
@ -0,0 +1,3 @@
|
||||
local MyLib = {}
|
||||
MyLib.v = "2.0.5"
|
||||
return MyLib
|
Loading…
x
Reference in New Issue
Block a user