-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
/
semantic_version.cr
208 lines (184 loc) · 5.81 KB
/
semantic_version.cr
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
# Conforms to Semantic Versioning 2.0.0
#
# See [https://semver.org/](https://semver.org/) for more information.
struct SemanticVersion
include Comparable(self)
# The major version of this semantic version
getter major : Int32
# The minor version of this semantic version
getter minor : Int32
# The patch version of this semantic version
getter patch : Int32
# The build metadata of this semantic version
getter build : String?
# The pre-release version of this semantic version
getter prerelease : Prerelease
# Parses a `SemanticVersion` from the given semantic version string
#
# ```
# require "semantic_version"
#
# semver = SemanticVersion.parse("2.61.4")
# semver # => #<SemanticVersion:0x55b3667c9e70 @major=2, @minor=61, @patch=4, ... >
# ```
#
# Raises `ArgumentError` if *str* is not a semantic version.
def self.parse(str : String) : self
if m = str.match /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)
(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?
(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/x
major = m[1].to_i
minor = m[2].to_i
patch = m[3].to_i
prerelease = m[4]?
build = m[5]?
new major, minor, patch, prerelease, build
else
raise ArgumentError.new("Not a semantic version: #{str.inspect}")
end
end
# Creates a new `SemanticVersion` instance with the given major, minor, and patch versions
# and optionally build and pre-release version
#
# Raises `ArgumentError` if *prerelease* is invalid pre-release version
def initialize(@major : Int, @minor : Int, @patch : Int, prerelease : String | Prerelease | Nil = nil, @build : String? = nil)
@prerelease = case prerelease
when Prerelease
prerelease
when String
Prerelease.parse prerelease
when nil
Prerelease.new
else
raise ArgumentError.new("Invalid prerelease #{prerelease.inspect}")
end
end
def_equals_and_hash major, minor, patch, prerelease, build
# Returns the string representation of this semantic version
#
# ```
# require "semantic_version"
#
# semver = SemanticVersion.parse("0.27.1")
# semver.to_s # => "0.27.1"
# ```
def to_s(io : IO) : Nil
io << major << '.' << minor << '.' << patch
unless prerelease.identifiers.empty?
io << '-'
prerelease.to_s io
end
if build
io << '+' << build
end
end
# The comparison operator.
#
# Returns `-1`, `0` or `1` depending on whether `self`'s version is lower than *other*'s,
# equal to *other*'s version or greater than *other*'s version.
#
# ```
# require "semantic_version"
#
# semver1 = SemanticVersion.new(1, 0, 0)
# semver2 = SemanticVersion.new(2, 0, 0)
#
# semver1 <=> semver2 # => -1
# semver2 <=> semver2 # => 0
# semver2 <=> semver1 # => 1
# ```
def <=>(other : self) : Int32
r = major <=> other.major
return r unless r.zero?
r = minor <=> other.minor
return r unless r.zero?
r = patch <=> other.patch
return r unless r.zero?
prerelease <=> other.prerelease
end
# Contains the pre-release version related to this semantic version
struct Prerelease
include Comparable(self)
# Parses a `Prerelease` from the given pre-release version string
#
# ```
# require "semantic_version"
#
# prerelease = SemanticVersion::Prerelease.parse("rc.1.3")
# prerelease # => SemanticVersion::Prerelease(@identifiers=["rc", 1, 3])
# ```
def self.parse(str : String) : self
identifiers = [] of String | Int32
str.split('.').each do |val|
if number = val.to_i32?
identifiers << number
else
identifiers << val
end
end
Prerelease.new identifiers
end
# Array of identifiers that make up the pre-release metadata
getter identifiers : Array(String | Int32)
# Creates a new `Prerelease` instance with supplied array of identifiers
def initialize(@identifiers : Array(String | Int32) = [] of String | Int32)
end
# Returns the string representation of this semantic version's pre-release metadata
#
# ```
# require "semantic_version"
#
# semver = SemanticVersion.parse("0.27.1-rc.1")
# semver.prerelease.to_s # => "rc.1"
# ```
def to_s(io : IO) : Nil
identifiers.join(io, '.')
end
# The comparison operator.
#
# Returns `-1`, `0` or `1` depending on whether `self`'s pre-release is lower than *other*'s,
# equal to *other*'s pre-release or greater than *other*'s pre-release.
#
# ```
# require "semantic_version"
#
# prerelease1 = SemanticVersion::Prerelease.new(["rc", 1])
# prerelease2 = SemanticVersion::Prerelease.new(["rc", 1, 2])
#
# prerelease1 <=> prerelease2 # => -1
# prerelease1 <=> prerelease1 # => 0
# prerelease2 <=> prerelease1 # => 1
# ```
def <=>(other : self) : Int32
if identifiers.empty?
if other.identifiers.empty?
return 0
else
return 1
end
elsif other.identifiers.empty?
return -1
end
identifiers.each_with_index do |item, i|
return 1 if i >= other.identifiers.size # larger = higher precedence
oitem = other.identifiers[i]
r = compare item, oitem
return r if r != 0
end
return -1 if identifiers.size != other.identifiers.size # larger = higher precedence
0
end
private def compare(x : Int32, y : String)
-1
end
private def compare(x : String, y : Int32)
1
end
private def compare(x : Int32, y : Int32)
x <=> y
end
private def compare(x : String, y : String)
x <=> y
end
end
end