mirror of
				https://github.com/folke/lazy.nvim.git
				synced 2025-10-31 14:31:04 +00:00 
			
		
		
		
	feat: added full semver and range parsing
This commit is contained in:
		
					parent
					
						
							
								f51eb0d957
							
						
					
				
			
			
				commit
				
					
						f54c24a4fa
					
				
			
		
					 3 changed files with 303 additions and 0 deletions
				
			
		
							
								
								
									
										15
									
								
								.neoconf.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								.neoconf.json
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,15 @@ | |||
| { | ||||
|   "neodev": { | ||||
|     "library": { | ||||
|       "plugins": [ | ||||
|         "plenary.nvim" | ||||
|       ] | ||||
|     } | ||||
|   }, | ||||
|   "lspconfig": { | ||||
|     "sumneko_lua": { | ||||
|       "Lua.runtime.version": "LuaJIT", | ||||
|       "Lua.workspace.checkThirdParty": false | ||||
|     } | ||||
|   } | ||||
| } | ||||
							
								
								
									
										191
									
								
								lua/lazy/manage/semver.lua
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										191
									
								
								lua/lazy/manage/semver.lua
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,191 @@ | |||
| local M = {} | ||||
| 
 | ||||
| ---@class Semver | ||||
| ---@field [1] number | ||||
| ---@field [2] number | ||||
| ---@field [3] number | ||||
| ---@field major number | ||||
| ---@field minor number | ||||
| ---@field patch number | ||||
| ---@field prerelease? string | ||||
| ---@field build? string | ||||
| local Semver = {} | ||||
| Semver.__index = Semver | ||||
| 
 | ||||
| function Semver:__index(key) | ||||
|   return type(key) == "number" and ({ self.major, self.minor, self.patch })[key] or Semver[key] | ||||
| end | ||||
| 
 | ||||
| function Semver:__newindex(key, value) | ||||
|   if key == 1 then | ||||
|     self.major = value | ||||
|   elseif key == 2 then | ||||
|     self.minor = value | ||||
|   elseif key == 3 then | ||||
|     self.patch = value | ||||
|   else | ||||
|     rawset(self, key, value) | ||||
|   end | ||||
| end | ||||
| 
 | ||||
| ---@param other Semver | ||||
| function Semver:__eq(other) | ||||
|   for i = 1, 3 do | ||||
|     if self[i] ~= other[i] then | ||||
|       return false | ||||
|     end | ||||
|   end | ||||
|   return self.prerelease == other.prerelease | ||||
| end | ||||
| 
 | ||||
| function Semver:__tostring() | ||||
|   local ret = table.concat({ self.major, self.minor, self.patch }, ".") | ||||
|   if self.prerelease then | ||||
|     ret = ret .. "-" .. self.prerelease | ||||
|   end | ||||
|   if self.build then | ||||
|     ret = ret .. "+" .. self.build | ||||
|   end | ||||
|   return ret | ||||
| end | ||||
| 
 | ||||
| ---@param other Semver | ||||
| function Semver:__lt(other) | ||||
|   for i = 1, 3 do | ||||
|     if self[i] > other[i] then | ||||
|       return false | ||||
|     elseif self[i] < other[i] then | ||||
|       return true | ||||
|     end | ||||
|   end | ||||
|   if self.prerelease and not other.prerelease then | ||||
|     return true | ||||
|   end | ||||
|   if other.prerelease and not self.prerelease then | ||||
|     return false | ||||
|   end | ||||
|   return (self.prerelease or "") < (other.prerelease or "") | ||||
| end | ||||
| 
 | ||||
| ---@param other Semver | ||||
| function Semver:__le(other) | ||||
|   return self < other or self == other | ||||
| end | ||||
| 
 | ||||
| ---@param version string|number[] | ||||
| ---@return Semver? | ||||
| function M.version(version) | ||||
|   if type(version) == "table" then | ||||
|     return setmetatable({ | ||||
|       major = version[1] or 0, | ||||
|       minor = version[2] or 0, | ||||
|       patch = version[3] or 0, | ||||
|     }, Semver) | ||||
|   end | ||||
|   local major, minor, patch, prerelease, build = version:match("^v?(%d+)%.?(%d*)%.?(%d*)%-?([^+]*)+?(.*)$") | ||||
|   if major then | ||||
|     return setmetatable({ | ||||
|       major = tonumber(major), | ||||
|       minor = minor == "" and 0 or tonumber(minor), | ||||
|       patch = patch == "" and 0 or tonumber(patch), | ||||
|       prerelease = prerelease ~= "" and prerelease or nil, | ||||
|       build = build ~= "" and build or nil, | ||||
|     }, Semver) | ||||
|   end | ||||
| end | ||||
| 
 | ||||
| ---@generic T: Semver | ||||
| ---@param versions T[] | ||||
| ---@return T? | ||||
| function M.last(versions) | ||||
|   local last = versions[1] | ||||
|   for i = 2, #versions do | ||||
|     if versions[i] > last then | ||||
|       last = versions[i] | ||||
|     end | ||||
|   end | ||||
|   return last | ||||
| end | ||||
| 
 | ||||
| ---@class SemverRange | ||||
| ---@field from Semver | ||||
| ---@field to? Semver | ||||
| local Range = {} | ||||
| 
 | ||||
| ---@param version string|Semver | ||||
| function Range:matches(version) | ||||
|   if type(version) == "string" then | ||||
|     ---@diagnostic disable-next-line: cast-local-type | ||||
|     version = M.version(version) | ||||
|   end | ||||
|   if version then | ||||
|     if version.prerelease ~= self.from.prerelease then | ||||
|       return false | ||||
|     end | ||||
|     return version >= self.from and (self.to == nil or version < self.to) | ||||
|   end | ||||
| end | ||||
| 
 | ||||
| ---@param spec string | ||||
| function M.range(spec) | ||||
|   if spec == "*" or spec == "" then | ||||
|     return setmetatable({ from = M.version("0.0.0") }, { __index = Range }) | ||||
|   end | ||||
| 
 | ||||
|   ---@type number? | ||||
|   local hyphen = spec:find(" - ", 1, true) | ||||
|   if hyphen then | ||||
|     local a = spec:sub(1, hyphen - 1) | ||||
|     local b = spec:sub(hyphen + 3) | ||||
|     local parts = vim.split(b, ".", { plain = true }) | ||||
|     local ra = M.range(a) | ||||
|     local rb = M.range(b) | ||||
|     return setmetatable({ | ||||
|       from = ra and ra.from, | ||||
|       to = rb and (#parts == 3 and rb.from or rb.to), | ||||
|     }, { __index = Range }) | ||||
|   end | ||||
|   ---@type string, string | ||||
|   local mods, version = spec:lower():match("^([%^=>~]*)(.*)$") | ||||
|   version = version:gsub("%.[%*x]", "") | ||||
|   local parts = vim.split(version:gsub("%-.*", ""), ".", { plain = true }) | ||||
|   if #parts < 3 and mods == "" then | ||||
|     mods = "~" | ||||
|   end | ||||
| 
 | ||||
|   local semver = M.version(version) | ||||
|   if semver then | ||||
|     local from = semver | ||||
|     local to = vim.deepcopy(semver) | ||||
|     if mods == "" or mods == "=" then | ||||
|       to.patch = to.patch + 1 | ||||
|     elseif mods == ">" then | ||||
|       from.patch = from.patch + 1 | ||||
|       to = nil | ||||
|     elseif mods == ">=" then | ||||
|       to = nil | ||||
|     elseif mods == "~" then | ||||
|       if #parts >= 2 then | ||||
|         to[2] = to[2] + 1 | ||||
|         to[3] = 0 | ||||
|       else | ||||
|         to[1] = to[1] + 1 | ||||
|         to[2] = 0 | ||||
|         to[3] = 0 | ||||
|       end | ||||
|     elseif mods == "^" then | ||||
|       for i = 1, 3 do | ||||
|         if to[i] ~= 0 then | ||||
|           to[i] = to[i] + 1 | ||||
|           for j = i + 1, 3 do | ||||
|             to[j] = 0 | ||||
|           end | ||||
|           break | ||||
|         end | ||||
|       end | ||||
|     end | ||||
|     return setmetatable({ from = from, to = to }, { __index = Range }) | ||||
|   end | ||||
| end | ||||
| 
 | ||||
| return M | ||||
							
								
								
									
										97
									
								
								tests/manage/semver_spec.lua
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								tests/manage/semver_spec.lua
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,97 @@ | |||
| local Semver = require("lazy.manage.semver") | ||||
| 
 | ||||
| local function v(version) | ||||
|   return Semver.version(version) | ||||
| end | ||||
| 
 | ||||
| describe("semver version", function() | ||||
|   local tests = { | ||||
|     ["v1.2.3"] = { major = 1, minor = 2, patch = 3 }, | ||||
|     ["v1.2"] = { major = 1, minor = 2, patch = 0 }, | ||||
|     ["v1.2.3-prerelease"] = { major = 1, minor = 2, patch = 3, prerelease = "prerelease" }, | ||||
|     ["v1.2-prerelease"] = { major = 1, minor = 2, patch = 0, prerelease = "prerelease" }, | ||||
|     ["v1.2.3-prerelease+build"] = { major = 1, minor = 2, patch = 3, prerelease = "prerelease", build = "build" }, | ||||
|     ["1.2.3+build"] = { major = 1, minor = 2, patch = 3, build = "build" }, | ||||
|   } | ||||
|   for input, output in pairs(tests) do | ||||
|     it("correctly parses " .. input, function() | ||||
|       assert.same(output, v(input)) | ||||
|     end) | ||||
|   end | ||||
| end) | ||||
| 
 | ||||
| describe("semver range", function() | ||||
|   local tests = { | ||||
|     ["1.2.3"] = { from = { 1, 2, 3 }, to = { 1, 2, 4 } }, | ||||
|     ["1.2"] = { from = { 1, 2, 0 }, to = { 1, 3, 0 } }, | ||||
|     ["=1.2.3"] = { from = { 1, 2, 3 }, to = { 1, 2, 4 } }, | ||||
|     [">1.2.3"] = { from = { 1, 2, 4 } }, | ||||
|     [">=1.2.3"] = { from = { 1, 2, 3 } }, | ||||
|     ["~1.2.3"] = { from = { 1, 2, 3 }, to = { 1, 3, 0 } }, | ||||
|     ["^1.2.3"] = { from = { 1, 2, 3 }, to = { 2, 0, 0 } }, | ||||
|     ["^0.2.3"] = { from = { 0, 2, 3 }, to = { 0, 3, 0 } }, | ||||
|     ["^0.0.1"] = { from = { 0, 0, 1 }, to = { 0, 0, 2 } }, | ||||
|     ["^1.2"] = { from = { 1, 2, 0 }, to = { 2, 0, 0 } }, | ||||
|     ["~1.2"] = { from = { 1, 2, 0 }, to = { 1, 3, 0 } }, | ||||
|     ["~1"] = { from = { 1, 0, 0 }, to = { 2, 0, 0 } }, | ||||
|     ["^1"] = { from = { 1, 0, 0 }, to = { 2, 0, 0 } }, | ||||
|     ["1.*"] = { from = { 1, 0, 0 }, to = { 2, 0, 0 } }, | ||||
|     ["1"] = { from = { 1, 0, 0 }, to = { 2, 0, 0 } }, | ||||
|     ["1.x"] = { from = { 1, 0, 0 }, to = { 2, 0, 0 } }, | ||||
|     ["1.2.x"] = { from = { 1, 2, 0 }, to = { 1, 3, 0 } }, | ||||
|     ["1.2.*"] = { from = { 1, 2, 0 }, to = { 1, 3, 0 } }, | ||||
|     ["*"] = { from = { 0, 0, 0 } }, | ||||
|     ["1.2 - 2.3.0"] = { from = { 1, 2, 0 }, to = { 2, 3, 0 } }, | ||||
|     ["1.2.3 - 2.3.4"] = { from = { 1, 2, 3 }, to = { 2, 3, 4 } }, | ||||
|     ["1.2.3 - 2"] = { from = { 1, 2, 3 }, to = { 3, 0, 0 } }, | ||||
|   } | ||||
|   for input, output in pairs(tests) do | ||||
|     output.from = v(output.from) | ||||
|     output.to = output.to and v(output.to) | ||||
| 
 | ||||
|     local range = Semver.range(input) | ||||
|     it("correctly parses " .. input, function() | ||||
|       assert.same(output, range) | ||||
|     end) | ||||
| 
 | ||||
|     it("from in range " .. input, function() | ||||
|       assert(range:matches(output.from)) | ||||
|     end) | ||||
| 
 | ||||
|     it("from - 1 not in range " .. input, function() | ||||
|       local lower = vim.deepcopy(range.from) | ||||
|       lower.major = lower.major - 1 | ||||
|       assert(not range:matches(lower)) | ||||
|     end) | ||||
| 
 | ||||
|     it("to not in range " .. input .. " to:" .. tostring(range.to), function() | ||||
|       if range.to then | ||||
|         assert(not (range.to < range.to)) | ||||
|         assert(not range:matches(range.to)) | ||||
|       end | ||||
|     end) | ||||
|   end | ||||
| 
 | ||||
|   it("handles prereleass", function() | ||||
|     assert(not Semver.range("1.2.3"):matches("1.2.3-alpha")) | ||||
|     assert(Semver.range("1.2.3-alpha"):matches("1.2.3-alpha")) | ||||
|     assert(not Semver.range("1.2.3-alpha"):matches("1.2.3-beta")) | ||||
|   end) | ||||
| end) | ||||
| 
 | ||||
| describe("semver order", function() | ||||
|   it("is correct", function() | ||||
|     assert(v("v1.2.3") == v("1.2.3")) | ||||
|     assert(not (v("v1.2.3") < v("1.2.3"))) | ||||
|     assert(v("v1.2.3") > v("1.2.3-prerelease")) | ||||
|     assert(v("v1.2.3-alpha") < v("1.2.3-beta")) | ||||
|     assert(v("v1.2.3-prerelease") < v("1.2.3")) | ||||
|     assert(v("v1.2.3") >= v("1.2.3")) | ||||
|     assert(v("v1.2.3") >= v("1.0.3")) | ||||
|     assert(v("v1.2.3") >= v("1.2.2")) | ||||
|     assert(v("v1.2.3") > v("1.2.2")) | ||||
|     assert(v("v1.2.3") > v("1.0.3")) | ||||
|     assert.same(Semver.last({ v("1.2.3"), v("2.0.0") }), v("2.0.0")) | ||||
|     assert.same(Semver.last({ v("2.0.0"), v("1.2.3") }), v("2.0.0")) | ||||
|   end) | ||||
| end) | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue