diff --git a/semver.lua b/semver.lua index 67a6046..446edee 100644 --- a/semver.lua +++ b/semver.lua @@ -99,23 +99,13 @@ local function compare(a,b) return a == b and 0 or a < b and -1 or 1 end -local function compareNilPrereleases(mine, other) - if mine == other then return 0 - elseif not mine then return 1 - elseif not other then return -1 - end -- else return nil -end +local function compareIds(myId, otherId) + if myId == otherId then return 0 + elseif not myId then return -1 + elseif not otherId then return 1 + end --- notice that builds compare nils inversely than prereleases (the -1 and 1 are switched) -local function compareNilBuilds(mine, other) - if mine == other then return 0 - elseif not mine then return -1 - elseif not other then return 1 - end -- else return nil -end - -local function compareIds(selfId, otherId) - local selfNumber, otherNumber = tonumber(selfId), tonumber(otherId) + local selfNumber, otherNumber = tonumber(myId), tonumber(otherId) if selfNumber and otherNumber then -- numerical comparison return compare(selfNumber, otherNumber) @@ -125,18 +115,16 @@ local function compareIds(selfId, otherId) elseif otherNumber then return 1 else - return compare(selfId, otherId) -- alphanumerical comparison + return compare(myId, otherId) -- alphanumerical comparison end end -local function smallerIds(mine, other, compareNil) - local myIds, otherIds = splitByDot(mine), splitByDot(other) +local function smallerIdList(myIds, otherIds) local myLength = #myIds - local comparison, myId, otherId + local comparison - for i = 1, myLength do - myId, otherId = myIds[i], otherIds[i] - comparison = compareNil(myId, otherId) or compareIds(myId, otherId) + for i=1, myLength do + comparison = compareIds(myIds[i], otherIds[i]) if comparison ~= 0 then return comparison == -1 end @@ -147,19 +135,11 @@ local function smallerIds(mine, other, compareNil) end local function smallerPrerelease(mine, other) - if mine == other or not mine then return false + if mine == other or not mine then return false elseif not other then return true end - return smallerIds(mine, other, compareNilPrereleases) -end - -local function smallerBuild(mine, other) - if mine == other or not other then return false - elseif not mine then return true - end - - return smallerIds(mine, other, compareNilBuilds) + return smallerIdList(splitByDot(mine), splitByDot(other)) end local methods = {} @@ -179,16 +159,19 @@ function mt:__eq(other) return self.major == other.major and self.minor == other.minor and self.patch == other.patch and - self.prerelease == other.prerelease and - self.build == other.build + self.prerelease == other.prerelease + -- notice that build is ignored for precedence in semver 2.0.0 end function mt:__lt(other) - return self.major < other.major or - self.minor < other.minor or - self.patch < other.patch or - smallerPrerelease(self.prerelease, other.prerelease) or - smallerBuild(self.build, other.build) + if self.major ~= other.major then return self.major < other.major end + if self.minor ~= other.minor then return self.minor < other.minor end + if self.patch ~= other.patch then return self.patch < other.patch end + return smallerPrerelease(self.prerelease, other.prerelease) + -- notice that build is ignored for precedence in semver 2.0.0 end +-- This works like the "pessimisstic operator" in Rubygems. +-- if a and b are versions, a ^ b means "b is backwards-compatible with a" +-- in other words, "it's safe to upgrade from a to b" function mt:__pow(other) return self.major == other.major and self.minor <= other.minor diff --git a/spec/semver_spec.lua b/spec/semver_spec.lua index c356af5..93263fd 100644 --- a/spec/semver_spec.lua +++ b/spec/semver_spec.lua @@ -39,59 +39,59 @@ describe('semver', function() end) describe("from strings", function() - test("1.2.3", function() + it("1.2.3", function() checkVersion( v'1.2.3', 1,2,3) end) - test("10.20.123", function() + it("10.20.123", function() checkVersion( v'10.20.123', 10,20,123) end) - test("2.0", function() + it("2.0", function() checkVersion( v'2.0', 2,0,0) end) - test("5", function() + it("5", function() checkVersion( v'5', 5,0,0) end) - test("1.2.3-alpha", function() + it("1.2.3-alpha", function() checkVersion( v'1.2.3-alpha', 1,2,3,'alpha' ) end) - test("1.2.3+build.15", function() + it("1.2.3+build.15", function() checkVersion( v'1.2.3+build.15', 1,2,3,nil,'build.15' ) end) - test("1.2.3-rc1+build.15", function() + it("1.2.3-rc1+build.15", function() checkVersion( v'1.2.3-rc1+build.15', 1,2,3,'rc1','build.15' ) end) end) describe('errors', function() - test('no parameters are passed', function() + it('no parameters are passed', function() assert.error(function() v() end) end) - test('negative numbers', function() + it('negative numbers', function() assert.error(function() v(-1, 0, 0) end) assert.error(function() v( 0,-1, 0) end) assert.error(function() v( 0, 0,-1) end) end) - test('floats', function() + it('floats', function() assert.error(function() v(.1, 0, 0) end) assert.error(function() v( 0,.1, 0) end) assert.error(function() v( 0, 0,.1) end) end) - test('empty string', function() + it('empty string', function() assert.error(function() v("") end) end) - test('garbage at the beginning of the string', function() + it('garbage at the beginning of the string', function() assert.error(function() v("foobar1.2.3") end) end) - test('garbage at the end of the string', function() + it('garbage at the end of the string', function() assert.error(function() v("1.2.3foobar") end) end) - test('a non-string or number is passed', function() + it('a non-string or number is passed', function() assert.error(function() v({}) end) end) - test('an invalid prerelease', function() + it('an invalid prerelease', function() assert.error(function() v'1.2.3-%?' end) end) - test('an invalid build', function() + it('an invalid build', function() assert.error(function() v'1.2.3+%?' end) end) end) @@ -115,115 +115,112 @@ describe('semver', function() describe("==", function() it("is true when major, minor and patch are the same", function() - assert.equal(v(1,2,3), v'1.2.3') + assert.equal(v'1.0.0', v'1.0.0') end) - it("is false when major, minor and patch are not the same", function() - assert.not_equal(v(1,2,3), v(4,5,6)) + it("is false when major, minor, patch or prerelease are not the same", function() + assert.not_equal(v'1.0.0', v'1.0.1') + assert.not_equal(v'1.0.0', v'1.1.0') + assert.not_equal(v'1.0.0', v'2.0.0') + assert.not_equal(v'1.0.0', v'1.0.0-alpha') end) - it("false if all is the same except the prerelease", function() - assert.not_equal(v(1,2,3), v'1.2.3-alpha') - end) - it("false if all is the same except the build", function() - assert.not_equal(v(1,2,3), v'1.2.3+peter.1') + it("ignores builds", function() + assert.equal(v'1.2.3', v'1.2.3+1') + assert.equal(v'1.2.3+1', v'1.2.3+2') end) end) describe("<", function() - test("true if major < minor", function() - assert.less(v'1.100.10', v'2.0.0') + it("compares correctly when major, minor and patch are equal", function() + assert.not_less(v'1.0.0', v'1.0.0') + assert.not_greater(v'1.0.0', v'1.0.0') + assert.equal(v'1.0.0', v'1.0.0') end) - test("false if major > minor", function() - assert.greater(v'2', v'1') + + it("#focus prioritizes major over minor", function() + --assert.less(v'1.100.10', v'2.0.0') + assert.not_greater(v'1.100.10', v'2.0.0') + --assert.greater(v'2', v'1') + --assert.not_less(v'2', v'1') end) - test("true if major = major but minor < minor", function() + it("when equal major, compares minor", function() assert.less(v'1.2.0', v'1.3.0') - end) - test("false if minor < minor", function() + assert.not_greater(v'1.2.0', v'1.3.0') assert.greater(v'1.1', v'1.0') + assert.not_less(v'1.1', v'1.0') end) - test("true if major =, minor =, but patch <", function() + it("when equal major and minor, compares patch", function() assert.less(v'0.0.1', v'0.0.10') - end) - test("false if major =, minor =, but patch >", function() + assert.not_greater(v'0.0.1', v'0.0.10') assert.greater(v'0.0.2', v'0.0.1') + assert.not_less(v'0.0.2', v'0.0.1') end) describe("prereleases", function() - test("false if exact same prerelease", function() - assert.not_less(v'1.0.0-beta', v'1.0.0-beta') + it("compares correctly when major, minor, patch and prerelease are equal", function() + assert.not_less(v'1.0.0-1', v'1.0.0-1') + assert.not_greater(v'1.0.0-1', v'1.0.0-1') + assert.equal(v'1.0.0-1', v'1.0.0-1') end) - test("#focus a prerelease version is less than the official version", function() + it("prioritizes non-prereleases over prereleases", function() assert.less(v'1.0.0-rc1', v'1.0.0') assert.not_greater(v'1.0.0-rc1', v'1.0.0') assert.greater(v'1.2.3', v'1.2.3-alpha') assert.not_less(v'1.2.3', v'1.2.3-alpha') end) - test("identifiers with only digits are compared numerically", function() + it("compares identifiers with only digits numerically", function() assert.less(v'1.0.0-1', v'1.0.0-2') assert.not_greater(v'1.0.0-1', v'1.0.0-2') + assert.greater(v'1.0.0-2', v'1.0.0-1') + assert.not_less(v'1.0.0-2', v'1.0.0-1') end) - test("idendifiers with letters or dashes are compared lexiconumerically", function() + it("compares idendifiers with letters or dashes lexiconumerically", function() assert.less(v'1.0.0-alpha', v'1.0.0-beta') - assert.less(v'1.0.0-alpha-10', v'1.0.0-alpha-2') assert.not_greater(v'1.0.0-alpha', v'1.0.0-beta') + assert.less(v'1.0.0-alpha-10', v'1.0.0-alpha-2') assert.not_greater(v'1.0.0-alpha-10', v'1.0.0-alpha-2') + assert.greater(v'1.0.0-beta', v'1.0.0-alpha') + assert.not_less(v'1.0.0-beta', v'1.0.0-alpha') + assert.greater(v'1.0.0-alpha-2', v'1.0.0-alpha-10') + assert.not_less(v'1.0.0-alpha-2', v'1.0.0-alpha-10') end) - test("numerical ids always have less priority than lexiconumericals", function() + it("prioritizes lexiconumericals over numbers", function() assert.less(v'1.0.0-1', v'1.0.0-alpha') - assert.less(v'1.0.0-2', v'1.0.0-1asdf') assert.not_greater(v'1.0.0-1', v'1.0.0-alpha') + assert.less(v'1.0.0-2', v'1.0.0-1asdf') assert.not_greater(v'1.0.0-2', v'1.0.0-1asdf') + assert.greater(v'1.0.0-alpha', v'1.0.0-1') + assert.not_less(v'1.0.0-alpha', v'1.0.0-1') + assert.greater(v'1.0.0-1asdf', v'1.0.0-2') + assert.not_less(v'1.0.0-1asdf', v'1.0.0-2') end) - test("identifiers can be separated by colons; they must be compared individually", function() - --assert.less(v'1.0.0-alpha' , v'1.0.0-alpha.1') - --assert.less(v'1.0.0-alpha.1' , v'1.0.0-beta.2') - --assert.less(v'1.0.0-beta.2' , v'1.0.0-beta.11') - --assert.less(v'1.0.0-beta.11' , v'1.0.0-rc.1') + it("splits identifiers by colons, comparing every pair individually", function() + assert.less(v'1.0.0-alpha', v'1.0.0-alpha.1') assert.not_greater(v'1.0.0-alpha', v'1.0.0-alpha.1') - --assert.not_greater(v'1.0.0-alpha.1', v'1.0.0-beta.2') - --assert.not_greater(v'1.0.0-beta.2' , v'1.0.0-beta.11') - --assert.not_greater(v'1.0.0-beta.11', v'1.0.0-rc.1') + assert.less(v'1.0.0-alpha.1', v'1.0.0-beta.2') + assert.not_greater(v'1.0.0-alpha.1', v'1.0.0-beta.2') + assert.less(v'1.0.0-beta.2', v'1.0.0-beta.11') + assert.not_greater(v'1.0.0-beta.2', v'1.0.0-beta.11') + assert.less(v'1.0.0-beta.11', v'1.0.0-rc.1') + assert.not_greater(v'1.0.0-beta.11', v'1.0.0-rc.1') + + assert.greater(v'1.0.0-alpha.1', v'1.0.0-alpha') + assert.not_less(v'1.0.0-alpha.1', v'1.0.0-alpha') + assert.greater(v'1.0.0-beta.2', v'1.0.0-alpha.1') + assert.not_less(v'1.0.0-beta.2', v'1.0.0-alpha.1') + assert.greater(v'1.0.0-beta.11', v'1.0.0-beta.2') + assert.not_less(v'1.0.0-beta.11', v'1.0.0-beta.2') + assert.greater(v'1.0.0-rc.1', v'1.0.0-beta.11') + assert.not_less(v'1.0.0-rc.1', v'1.0.0-beta.11') end) end) describe("builds", function() - test("false if exact same build", function() - assert.not_less(v'1.0.0+build1', v'1.0.0+build1') + it("false independently of the build", function() + assert.not_less(v'1.0.0+build1', v'1.0.0') + assert.not_less(v'1.0.0+build1', v'1.0.0+build3') + assert.not_less(v'1.0.0-beta+build1', v'1.0.0-beta+build2') + assert.not_greater(v'1.0.0+build1', v'1.0.0') + assert.not_greater(v'1.0.0+build1', v'1.0.0+build3') + assert.not_greater(v'1.0.0-beta+build1', v'1.0.0-beta+build2') end) - test("a regular (not-build) version is always less than a build version", function() - assert.less(v'1.0.0', v'1.0.0+12') - assert.less(v'1.0.0', v'1.0.0+12') - end) - test("identifiers with only digits are compared numerically", function() - assert.less(v'1.0.0+1', v'1.0.0+2') - assert.less(v'1.0.0+2', v'1.0.0+10') - assert.not_greater(v'1.0.0+1', v'1.0.0+2') - assert.not_greater(v'1.0.0+2', v'1.0.0+10') - end) - test("idendifiers with letters or dashes are compared lexiconumerically", function() - assert.less(v'1.0.0+build1' , v'1.0.0+build2') - assert.less(v'1.0.0+build10', v'1.0.0+build2') - assert.not_greater(v'1.0.0+build1' , v'1.0.0+build2') - assert.not_greater(v'1.0.0+build10' , v'1.0.0+build2') - end) - test("numerical ids always have less priority than lexiconumericals", function() - assert.less(v'1.0.0+1', v'1.0.0+build1') - assert.less(v'1.0.0+2', v'1.0.0+1build') - assert.not_greater(v'1.0.0+1', v'1.0.0+build1') - assert.not_greater(v'1.0.0+2', v'1.0.0+1build') - end) - test("identifiers can be separated by colons; they must be compared individually", function() - assert.less(v'1.0.0+0.3.7', v'1.3.7+build') - assert.less(v'1.3.7+build', v'1.3.7+build.2.b8f12d7') - assert.less(v'1.3.7+build.2.b8f12d7', v'1.3.7+build.11.e0f985a') - assert.not_greater(v'1.0.0+0.3.7', v'1.3.7+build') - assert.not_greater(v'1.3.7+build', v'1.3.7+build.2.b8f12d7') - assert.not_greater(v'1.3.7+build.2.b8f12d7', v'1.3.7+build.11.e0f985a') - end) - end) - test("#focus prereleases + builds", function() - --assert.less(v'1.0.0-rc.1', v'1.0.0-rc.1+build.1') - --assert.less(v'1.0.0-rc.1+build.1', v'1.0.0') - --assert.not_greater(v'1.0.0-rc.1', v'1.0.0-rc.1+build.1') - assert.not_greater(v'1.0.0-rc.1+build.1', v'1.0.0') end) end) @@ -254,30 +251,26 @@ describe('semver', function() end) end) - - -- This works like the "pessimisstic operator" in Rubygems. - -- if a and b are versions, a ^ b means "b is backwards-compatible with a" - -- in other words, "it's safe to upgrade from a to b" describe("^", function() - test("true for self", function() + it("true for self", function() assert.is_true(v(1,2,3) ^ v(1,2,3)) end) - test("different major versions mean it's always unsafe", function() + it("different major versions mean it's always unsafe", function() assert.is_false(v(2,0,0) ^ v(3,0,0)) assert.is_false(v(2,0,0) ^ v(1,0,0)) end) - test("patches, prereleases and builds are ignored", function() + it("patches, prereleases and builds are ignored", function() assert.is_true(v(1,2,3) ^ v(1,2,0)) assert.is_true(v(1,2,3) ^ v(1,2,5)) assert.is_true(v(1,2,3,'foo') ^ v(1,2,3)) assert.is_true(v(1,2,3,nil,'bar') ^ v(1,2,3)) end) - test("it's safe to upgrade to a newer minor version", function() + it("is safe to upgrade to a newer minor version", function() assert.is_true(v(1,2,0) ^ v(1,5,0)) end) - test("it's unsafe to downgrade to an earlier minor version", function() + it("is unsafe to downgrade to an earlier minor version", function() assert.is_false(v(1,5,0) ^ v(1,2,0)) end) end)