Skip to content
Newer
Older
100644 174 lines (143 sloc) 6.23 KB
9236b3a @kikito added support for a _VERSION
authored
1 -- semver.lua - v1.1.0 (2012-01)
e1f1b48 @kikito initial version
authored
2 -- Copyright (c) 2012 Enrique García Cota
3 -- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 -- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
5 -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
3e432f5 @kikito add missing test, fixed top comment
authored
6 -- See http://semver.org for details
9236b3a @kikito added support for a _VERSION
authored
7 local version = {}
2b1dc60 @kikito added nextMinor, nextMajor, nextPatch
authored
8
912dd1d @kikito added more input checking
authored
9 local function checkPositiveInteger(number, name)
10 assert(number >= 0, name .. ' must be a valid positive number')
11 assert(math.floor(number) == number, name .. ' must be an integer')
12 end
13
68de15f @kikito started working on the prerelease
authored
14 local function present(value)
15 return value and value ~= ''
16 end
17
8592614 @kikito refactor
authored
18 -- splitByDot("a.bbc.d") == {"a", "bbc", "d"}
19 local function splitByDot(str)
20 local t, count = {}, 0
21 str:gsub("([^%.]+)", function(c)
22 count = count + 1
23 t[count] = c
24 end)
25 return t
26 end
043c181 @kikito small fixes in the way the prereleases are specified and parsed
authored
27
8592614 @kikito refactor
authored
28 local function parsePrereleaseAndBuildWithSign(str)
29 local prereleaseWithSign, buildWithSign = str:match("^(-[^+]+)(+.+)$")
48ecb3b @kikito prereleases and builds now working 100% like the spec
authored
30 if not (prereleaseWithSign and buildWithSign) then
8592614 @kikito refactor
authored
31 prereleaseWithSign = str:match("^(-.+)$")
32 buildWithSign = str:match("^(+.+)$")
48ecb3b @kikito prereleases and builds now working 100% like the spec
authored
33 end
8592614 @kikito refactor
authored
34 assert(prereleaseWithSign or buildWithSign, ("The parameter %q must begin with + or - to denote a prerelease or a build"):format(str))
35 return prereleaseWithSign, buildWithSign
36 end
48ecb3b @kikito prereleases and builds now working 100% like the spec
authored
37
8592614 @kikito refactor
authored
38 local function parsePrerelease(prereleaseWithSign)
48ecb3b @kikito prereleases and builds now working 100% like the spec
authored
39 if prereleaseWithSign then
8592614 @kikito refactor
authored
40 local prerelease = prereleaseWithSign:match("^-(%w[%.%w-]*)$")
48ecb3b @kikito prereleases and builds now working 100% like the spec
authored
41 assert(prerelease, ("The prerelease %q is not a slash followed by alphanumerics, dots and slashes"):format(prereleaseWithSign))
8592614 @kikito refactor
authored
42 return prerelease
48ecb3b @kikito prereleases and builds now working 100% like the spec
authored
43 end
8592614 @kikito refactor
authored
44 end
48ecb3b @kikito prereleases and builds now working 100% like the spec
authored
45
8592614 @kikito refactor
authored
46 local function parseBuild(buildWithSign)
48ecb3b @kikito prereleases and builds now working 100% like the spec
authored
47 if buildWithSign then
48 build = buildWithSign:match("^%+(%w[%.%w-]*)$")
49 assert(build, ("The build %q is not a + sign followed by alphanumerics, dots and slashes"):format(buildWithSign))
8592614 @kikito refactor
authored
50 return build
48ecb3b @kikito prereleases and builds now working 100% like the spec
authored
51 end
8592614 @kikito refactor
authored
52 end
53
54 local function parsePrereleaseAndBuild(str)
55 if not present(str) then return nil, nil end
56
57 local prereleaseWithSign, buildWithSign = parsePrereleaseAndBuildWithSign(str)
58
59 local prerelease = parsePrerelease(prereleaseWithSign)
60 local build = parseBuild(buildWithSign)
48ecb3b @kikito prereleases and builds now working 100% like the spec
authored
61
62 return prerelease, build
68de15f @kikito started working on the prerelease
authored
63 end
64
8592614 @kikito refactor
authored
65 local function parseVersion(str)
66 local sMajor, sMinor, sPatch, sPrereleaseAndBuild = str:match("^(%d+)%.?(%d*)%.?(%d*)(.-)$")
48ecb3b @kikito prereleases and builds now working 100% like the spec
authored
67 assert(type(sMajor) == 'string', ("Could not extract version number(s) from %q"):format(str))
68 local major, minor, patch = tonumber(sMajor), tonumber(sMinor), tonumber(sPatch)
8592614 @kikito refactor
authored
69 local prerelease, build = parsePrereleaseAndBuild(sPrereleaseAndBuild)
70 return major, minor, patch, prerelease, build
48ecb3b @kikito prereleases and builds now working 100% like the spec
authored
71 end
72
73
0f826b8 @kikito finished with prereleases
authored
74 -- return 0 if a == b, -1 if a < b, and 1 if a > b
75 local function compare(a,b)
76 return a == b and 0 or a < b and -1 or 1
77 end
78
79 local function compareIds(selfId, otherId)
80 if not selfId and not otherId then return 0
81 elseif not selfId then return 1
82 elseif not otherId then return -1
83 end
84
85 local selfNumber, otherNumber = tonumber(selfId), tonumber(otherId)
86
87 if selfNumber and otherNumber then -- numerical comparison
88 return compare(selfNumber, otherNumber)
89 elseif selfNumber then -- numericals are always smaller than alphanums
90 return -1
91 else
92 return compare(selfId, otherId) -- alphanumerical comparison
93 end
94 end
95
8592614 @kikito refactor
authored
96 local function smallerPrereleaseOrBuild(mine, his)
97 if mine == his then return false end
0f826b8 @kikito finished with prereleases
authored
98
8592614 @kikito refactor
authored
99 local myIds, hisIds = splitByDot(mine), splitByDot(his)
100 local myLength = #myIds
0f826b8 @kikito finished with prereleases
authored
101 local comparison
102
8592614 @kikito refactor
authored
103 for i = 1, myLength do
104 comparison = compareIds(myIds[i], hisIds[i])
0f826b8 @kikito finished with prereleases
authored
105 if comparison ~= 0 then return comparison == -1 end
106 -- if comparison == 0, continue loop
107 end
108
8592614 @kikito refactor
authored
109 return myLength < #hisIds
0f826b8 @kikito finished with prereleases
authored
110 end
111
2b1dc60 @kikito added nextMinor, nextMajor, nextPatch
authored
112 local methods = {}
113
114 function methods:nextMajor()
115 return version(self.major + 1, 0, 0)
116 end
117 function methods:nextMinor()
118 return version(self.major, self.minor + 1, 0)
119 end
120 function methods:nextPatch()
121 return version(self.major, self.minor, self.patch + 1)
122 end
123
124 local mt = { __index = methods }
125 function mt:__eq(other)
126 return self.major == other.major and
127 self.minor == other.minor and
68de15f @kikito started working on the prerelease
authored
128 self.patch == other.patch and
b1d083e @kikito builds done. I have just realized that builds and prereleases can hap…
authored
129 self.prerelease == other.prerelease and
130 self.build == other.build
2b1dc60 @kikito added nextMinor, nextMajor, nextPatch
authored
131 end
132 function mt:__lt(other)
133 return self.major < other.major or
134 self.minor < other.minor or
0f826b8 @kikito finished with prereleases
authored
135 self.patch < other.patch or
136 (self.prerelease and not other.prerelease) or
8592614 @kikito refactor
authored
137 smallerPrereleaseOrBuild(self.prerelease, other.prerelease) or
b1d083e @kikito builds done. I have just realized that builds and prereleases can hap…
authored
138 (not self.build and other.build) or
8592614 @kikito refactor
authored
139 smallerPrereleaseOrBuild(self.build, other.build)
2b1dc60 @kikito added nextMinor, nextMajor, nextPatch
authored
140 end
d07756e @kikito added pessimistic upgrade operator
authored
141 function mt:__pow(other)
142 return self.major == other.major and
143 self.minor <= other.minor
144 end
2b1dc60 @kikito added nextMinor, nextMajor, nextPatch
authored
145 function mt:__tostring()
68de15f @kikito started working on the prerelease
authored
146 local buffer = { ("%d.%d.%d"):format(self.major, self.minor, self.patch) }
48ecb3b @kikito prereleases and builds now working 100% like the spec
authored
147 if self.prerelease then table.insert(buffer, "-" .. self.prerelease) end
148 if self.build then table.insert(buffer, "+" .. self.build) end
68de15f @kikito started working on the prerelease
authored
149 return table.concat(buffer)
2b1dc60 @kikito added nextMinor, nextMajor, nextPatch
authored
150 end
e1f1b48 @kikito initial version
authored
151
9236b3a @kikito added support for a _VERSION
authored
152 local function new(major, minor, patch, prerelease, build)
e1f1b48 @kikito initial version
authored
153 assert(major, "At least one parameter is needed")
154
155 if type(major) == 'string' then
8592614 @kikito refactor
authored
156 major,minor,patch,prerelease,build = parseVersion(major)
e1f1b48 @kikito initial version
authored
157 end
158 patch = patch or 0
159 minor = minor or 0
160
912dd1d @kikito added more input checking
authored
161 checkPositiveInteger(major, "major")
162 checkPositiveInteger(minor, "minor")
163 checkPositiveInteger(patch, "patch")
164
b1d083e @kikito builds done. I have just realized that builds and prereleases can hap…
authored
165 local result = {major=major, minor=minor, patch=patch, prerelease=prerelease, build=build}
68de15f @kikito started working on the prerelease
authored
166 return setmetatable(result, mt)
e1f1b48 @kikito initial version
authored
167 end
168
9236b3a @kikito added support for a _VERSION
authored
169 setmetatable(version, { __call = function(_, ...) return new(...) end })
170
171 version._VERSION = version('1.1.0')
172
e1f1b48 @kikito initial version
authored
173 return version
Something went wrong with that request. Please try again.