1 2local vers = {} 3 4local util = require("luarocks.core.util") 5local require = nil 6-------------------------------------------------------------------------------- 7 8local deltas = { 9 dev = 120000000, 10 scm = 110000000, 11 cvs = 100000000, 12 rc = -1000, 13 pre = -10000, 14 beta = -100000, 15 alpha = -1000000 16} 17 18local version_mt = { 19 --- Equality comparison for versions. 20 -- All version numbers must be equal. 21 -- If both versions have revision numbers, they must be equal; 22 -- otherwise the revision number is ignored. 23 -- @param v1 table: version table to compare. 24 -- @param v2 table: version table to compare. 25 -- @return boolean: true if they are considered equivalent. 26 __eq = function(v1, v2) 27 if #v1 ~= #v2 then 28 return false 29 end 30 for i = 1, #v1 do 31 if v1[i] ~= v2[i] then 32 return false 33 end 34 end 35 if v1.revision and v2.revision then 36 return (v1.revision == v2.revision) 37 end 38 return true 39 end, 40 --- Size comparison for versions. 41 -- All version numbers are compared. 42 -- If both versions have revision numbers, they are compared; 43 -- otherwise the revision number is ignored. 44 -- @param v1 table: version table to compare. 45 -- @param v2 table: version table to compare. 46 -- @return boolean: true if v1 is considered lower than v2. 47 __lt = function(v1, v2) 48 for i = 1, math.max(#v1, #v2) do 49 local v1i, v2i = v1[i] or 0, v2[i] or 0 50 if v1i ~= v2i then 51 return (v1i < v2i) 52 end 53 end 54 if v1.revision and v2.revision then 55 return (v1.revision < v2.revision) 56 end 57 return false 58 end, 59 -- @param v1 table: version table to compare. 60 -- @param v2 table: version table to compare. 61 -- @return boolean: true if v1 is considered lower than or equal to v2. 62 __le = function(v1, v2) 63 return not (v2 < v1) 64 end, 65 --- Return version as a string. 66 -- @param v The version table. 67 -- @return The string representation. 68 __tostring = function(v) 69 return v.string 70 end, 71} 72 73local version_cache = {} 74setmetatable(version_cache, { 75 __mode = "kv" 76}) 77 78--- Parse a version string, converting to table format. 79-- A version table contains all components of the version string 80-- converted to numeric format, stored in the array part of the table. 81-- If the version contains a revision, it is stored numerically 82-- in the 'revision' field. The original string representation of 83-- the string is preserved in the 'string' field. 84-- Returned version tables use a metatable 85-- allowing later comparison through relational operators. 86-- @param vstring string: A version number in string format. 87-- @return table or nil: A version table or nil 88-- if the input string contains invalid characters. 89function vers.parse_version(vstring) 90 if not vstring then return nil end 91 assert(type(vstring) == "string") 92 93 local cached = version_cache[vstring] 94 if cached then 95 return cached 96 end 97 98 local version = {} 99 local i = 1 100 101 local function add_token(number) 102 version[i] = version[i] and version[i] + number/100000 or number 103 i = i + 1 104 end 105 106 -- trim leading and trailing spaces 107 local v = vstring:match("^%s*(.*)%s*$") 108 version.string = v 109 -- store revision separately if any 110 local main, revision = v:match("(.*)%-(%d+)$") 111 if revision then 112 v = main 113 version.revision = tonumber(revision) 114 end 115 while #v > 0 do 116 -- extract a number 117 local token, rest = v:match("^(%d+)[%.%-%_]*(.*)") 118 if token then 119 add_token(tonumber(token)) 120 else 121 -- extract a word 122 token, rest = v:match("^(%a+)[%.%-%_]*(.*)") 123 if not token then 124 util.warning("version number '"..v.."' could not be parsed.") 125 version[i] = 0 126 break 127 end 128 version[i] = deltas[token] or (token:byte() / 1000) 129 end 130 v = rest 131 end 132 setmetatable(version, version_mt) 133 version_cache[vstring] = version 134 return version 135end 136 137--- Utility function to compare version numbers given as strings. 138-- @param a string: one version. 139-- @param b string: another version. 140-- @return boolean: True if a > b. 141function vers.compare_versions(a, b) 142 if a == b then 143 return false 144 end 145 return vers.parse_version(a) > vers.parse_version(b) 146end 147 148--- A more lenient check for equivalence between versions. 149-- This returns true if the requested components of a version 150-- match and ignore the ones that were not given. For example, 151-- when requesting "2", then "2", "2.1", "2.3.5-9"... all match. 152-- When requesting "2.1", then "2.1", "2.1.3" match, but "2.2" 153-- doesn't. 154-- @param version string or table: Version to be tested; may be 155-- in string format or already parsed into a table. 156-- @param requested string or table: Version requested; may be 157-- in string format or already parsed into a table. 158-- @return boolean: True if the tested version matches the requested 159-- version, false otherwise. 160local function partial_match(version, requested) 161 assert(type(version) == "string" or type(version) == "table") 162 assert(type(requested) == "string" or type(version) == "table") 163 164 if type(version) ~= "table" then version = vers.parse_version(version) end 165 if type(requested) ~= "table" then requested = vers.parse_version(requested) end 166 if not version or not requested then return false end 167 168 for i, ri in ipairs(requested) do 169 local vi = version[i] or 0 170 if ri ~= vi then return false end 171 end 172 if requested.revision then 173 return requested.revision == version.revision 174 end 175 return true 176end 177 178--- Check if a version satisfies a set of constraints. 179-- @param version table: A version in table format 180-- @param constraints table: An array of constraints in table format. 181-- @return boolean: True if version satisfies all constraints, 182-- false otherwise. 183function vers.match_constraints(version, constraints) 184 assert(type(version) == "table") 185 assert(type(constraints) == "table") 186 local ok = true 187 setmetatable(version, version_mt) 188 for _, constr in pairs(constraints) do 189 if type(constr.version) == "string" then 190 constr.version = vers.parse_version(constr.version) 191 end 192 local constr_version, constr_op = constr.version, constr.op 193 setmetatable(constr_version, version_mt) 194 if constr_op == "==" then ok = version == constr_version 195 elseif constr_op == "~=" then ok = version ~= constr_version 196 elseif constr_op == ">" then ok = version > constr_version 197 elseif constr_op == "<" then ok = version < constr_version 198 elseif constr_op == ">=" then ok = version >= constr_version 199 elseif constr_op == "<=" then ok = version <= constr_version 200 elseif constr_op == "~>" then ok = partial_match(version, constr_version) 201 end 202 if not ok then break end 203 end 204 return ok 205end 206 207return vers 208