local vers = {} local util = require("luarocks.core.util") local require = nil -------------------------------------------------------------------------------- local deltas = { dev = 120000000, scm = 110000000, cvs = 100000000, rc = -1000, pre = -10000, beta = -100000, alpha = -1000000 } local version_mt = { --- Equality comparison for versions. -- All version numbers must be equal. -- If both versions have revision numbers, they must be equal; -- otherwise the revision number is ignored. -- @param v1 table: version table to compare. -- @param v2 table: version table to compare. -- @return boolean: true if they are considered equivalent. __eq = function(v1, v2) if #v1 ~= #v2 then return false end for i = 1, #v1 do if v1[i] ~= v2[i] then return false end end if v1.revision and v2.revision then return (v1.revision == v2.revision) end return true end, --- Size comparison for versions. -- All version numbers are compared. -- If both versions have revision numbers, they are compared; -- otherwise the revision number is ignored. -- @param v1 table: version table to compare. -- @param v2 table: version table to compare. -- @return boolean: true if v1 is considered lower than v2. __lt = function(v1, v2) for i = 1, math.max(#v1, #v2) do local v1i, v2i = v1[i] or 0, v2[i] or 0 if v1i ~= v2i then return (v1i < v2i) end end if v1.revision and v2.revision then return (v1.revision < v2.revision) end return false end, -- @param v1 table: version table to compare. -- @param v2 table: version table to compare. -- @return boolean: true if v1 is considered lower than or equal to v2. __le = function(v1, v2) return not (v2 < v1) -- luacheck: ignore end, --- Return version as a string. -- @param v The version table. -- @return The string representation. __tostring = function(v) return v.string end, } local version_cache = {} setmetatable(version_cache, { __mode = "kv" }) --- Parse a version string, converting to table format. -- A version table contains all components of the version string -- converted to numeric format, stored in the array part of the table. -- If the version contains a revision, it is stored numerically -- in the 'revision' field. The original string representation of -- the string is preserved in the 'string' field. -- Returned version tables use a metatable -- allowing later comparison through relational operators. -- @param vstring string: A version number in string format. -- @return table or nil: A version table or nil -- if the input string contains invalid characters. function vers.parse_version(vstring) if not vstring then return nil end assert(type(vstring) == "string") local cached = version_cache[vstring] if cached then return cached end local version = {} local i = 1 local function add_token(number) version[i] = version[i] and version[i] + number/100000 or number i = i + 1 end -- trim leading and trailing spaces local v = vstring:match("^%s*(.*)%s*$") version.string = v -- store revision separately if any local main, revision = v:match("(.*)%-(%d+)$") if revision then v = main version.revision = tonumber(revision) end while #v > 0 do -- extract a number local token, rest = v:match("^(%d+)[%.%-%_]*(.*)") if token then add_token(tonumber(token)) else -- extract a word token, rest = v:match("^(%a+)[%.%-%_]*(.*)") if not token then util.warning("version number '"..v.."' could not be parsed.") version[i] = 0 break end version[i] = deltas[token] or (token:byte() / 1000) end v = rest end setmetatable(version, version_mt) version_cache[vstring] = version return version end --- Utility function to compare version numbers given as strings. -- @param a string: one version. -- @param b string: another version. -- @return boolean: True if a > b. function vers.compare_versions(a, b) if a == b then return false end return vers.parse_version(a) > vers.parse_version(b) end --- A more lenient check for equivalence between versions. -- This returns true if the requested components of a version -- match and ignore the ones that were not given. For example, -- when requesting "2", then "2", "2.1", "2.3.5-9"... all match. -- When requesting "2.1", then "2.1", "2.1.3" match, but "2.2" -- doesn't. -- @param version string or table: Version to be tested; may be -- in string format or already parsed into a table. -- @param requested string or table: Version requested; may be -- in string format or already parsed into a table. -- @return boolean: True if the tested version matches the requested -- version, false otherwise. local function partial_match(version, requested) assert(type(version) == "string" or type(version) == "table") assert(type(requested) == "string" or type(version) == "table") if type(version) ~= "table" then version = vers.parse_version(version) end if type(requested) ~= "table" then requested = vers.parse_version(requested) end if not version or not requested then return false end for i, ri in ipairs(requested) do local vi = version[i] or 0 if ri ~= vi then return false end end if requested.revision then return requested.revision == version.revision end return true end --- Check if a version satisfies a set of constraints. -- @param version table: A version in table format -- @param constraints table: An array of constraints in table format. -- @return boolean: True if version satisfies all constraints, -- false otherwise. function vers.match_constraints(version, constraints) assert(type(version) == "table") assert(type(constraints) == "table") local ok = true setmetatable(version, version_mt) for _, constr in pairs(constraints) do if type(constr.version) == "string" then constr.version = vers.parse_version(constr.version) end local constr_version, constr_op = constr.version, constr.op setmetatable(constr_version, version_mt) if constr_op == "==" then ok = version == constr_version elseif constr_op == "~=" then ok = version ~= constr_version elseif constr_op == ">" then ok = version > constr_version elseif constr_op == "<" then ok = version < constr_version elseif constr_op == ">=" then ok = version >= constr_version elseif constr_op == "<=" then ok = version <= constr_version elseif constr_op == "~>" then ok = partial_match(version, constr_version) end if not ok then break end end return ok end return vers