vers 1.3.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.ruby-version +1 -1
- data/CHANGELOG.md +29 -1
- data/CONTRIBUTING.md +2 -2
- data/README.md +27 -24
- data/lib/vers/bazel_version.rb +114 -0
- data/lib/vers/composer_version.rb +117 -0
- data/lib/vers/conan_version.rb +87 -0
- data/lib/vers/constraint.rb +13 -3
- data/lib/vers/distribution_version.rb +482 -0
- data/lib/vers/gem_version.rb +81 -0
- data/lib/vers/interval.rb +49 -33
- data/lib/vers/maven_version.rb +16 -0
- data/lib/vers/nuget_version.rb +14 -0
- data/lib/vers/parser.rb +924 -202
- data/lib/vers/pub_version.rb +59 -0
- data/lib/vers/pypi_version.rb +256 -0
- data/lib/vers/scheme.rb +52 -0
- data/lib/vers/semver_version.rb +294 -0
- data/lib/vers/special_version.rb +336 -0
- data/lib/vers/version.rb +81 -13
- data/lib/vers/version_comparison.rb +26 -0
- data/lib/vers/version_range.rb +201 -48
- data/lib/vers.rb +39 -9
- metadata +15 -4
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "version_comparison"
|
|
4
|
+
|
|
5
|
+
module Vers
|
|
6
|
+
module DebianVersion
|
|
7
|
+
extend self
|
|
8
|
+
|
|
9
|
+
UPSTREAM_PATTERN = /\A[0-9][0-9A-Za-z.+~]*\z/
|
|
10
|
+
REVISION_PATTERN = /\A[0-9A-Za-z.+~]+\z/
|
|
11
|
+
|
|
12
|
+
def compare(left, right)
|
|
13
|
+
left_epoch, left_upstream, left_revision = split(left)
|
|
14
|
+
right_epoch, right_upstream, right_revision = split(right)
|
|
15
|
+
|
|
16
|
+
comparison = VersionComparison.compare_numbers(left_epoch, right_epoch)
|
|
17
|
+
return comparison unless comparison.zero?
|
|
18
|
+
|
|
19
|
+
comparison = compare_part(left_upstream, right_upstream)
|
|
20
|
+
return comparison unless comparison.zero?
|
|
21
|
+
|
|
22
|
+
compare_part(left_revision, right_revision)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def split(value)
|
|
26
|
+
epoch, version = value.to_s.split(":", 2)
|
|
27
|
+
unless version
|
|
28
|
+
version = epoch
|
|
29
|
+
epoch = ""
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
separator = version.rindex("-")
|
|
33
|
+
if separator
|
|
34
|
+
[epoch, version[0...separator], version[(separator + 1)..]]
|
|
35
|
+
else
|
|
36
|
+
[epoch, version, "0"]
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def valid?(value)
|
|
41
|
+
string = value.to_s.strip
|
|
42
|
+
epoch, version = string.split(":", 2)
|
|
43
|
+
if version
|
|
44
|
+
return false unless VersionComparison.numeric?(epoch)
|
|
45
|
+
else
|
|
46
|
+
version = epoch
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
separator = version.rindex("-")
|
|
50
|
+
upstream = separator ? version[0...separator] : version
|
|
51
|
+
revision = separator ? version[(separator + 1)..] : nil
|
|
52
|
+
upstream.match?(UPSTREAM_PATTERN) && (revision.nil? || revision.match?(REVISION_PATTERN))
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def normalize(value)
|
|
56
|
+
value.to_s.strip
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def prerelease?(value)
|
|
60
|
+
_, upstream, revision = split(value.to_s.strip)
|
|
61
|
+
upstream.include?("~") || revision.include?("~")
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def compare_part(left, right)
|
|
65
|
+
left_index = 0
|
|
66
|
+
right_index = 0
|
|
67
|
+
|
|
68
|
+
while left_index < left.length || right_index < right.length
|
|
69
|
+
while non_digit_at?(left, left_index) || non_digit_at?(right, right_index)
|
|
70
|
+
left_byte = non_digit_at?(left, left_index) ? left.getbyte(left_index) : 0
|
|
71
|
+
right_byte = non_digit_at?(right, right_index) ? right.getbyte(right_index) : 0
|
|
72
|
+
comparison = character_order(left_byte) <=> character_order(right_byte)
|
|
73
|
+
return comparison unless comparison.zero?
|
|
74
|
+
|
|
75
|
+
left_index += 1 unless left_byte.zero?
|
|
76
|
+
right_index += 1 unless right_byte.zero?
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
left_zero_end = skip_zeroes(left, left_index)
|
|
80
|
+
right_zero_end = skip_zeroes(right, right_index)
|
|
81
|
+
left_digit_end = scan_digits(left, left_zero_end)
|
|
82
|
+
right_digit_end = scan_digits(right, right_zero_end)
|
|
83
|
+
|
|
84
|
+
comparison = (left_digit_end - left_zero_end) <=> (right_digit_end - right_zero_end)
|
|
85
|
+
return comparison unless comparison.zero?
|
|
86
|
+
|
|
87
|
+
comparison = left[left_zero_end...left_digit_end] <=> right[right_zero_end...right_digit_end]
|
|
88
|
+
return comparison unless comparison.zero?
|
|
89
|
+
|
|
90
|
+
left_index = left_digit_end
|
|
91
|
+
right_index = right_digit_end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
0
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def non_digit_at?(value, index)
|
|
98
|
+
index < value.length && !digit?(value.getbyte(index))
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def skip_zeroes(value, index)
|
|
102
|
+
index += 1 while index < value.length && value.getbyte(index) == 48
|
|
103
|
+
index
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def scan_digits(value, index)
|
|
107
|
+
index += 1 while index < value.length && digit?(value.getbyte(index))
|
|
108
|
+
index
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def character_order(byte)
|
|
112
|
+
return -1 if byte == 126
|
|
113
|
+
return 0 if byte.zero?
|
|
114
|
+
return byte if alpha?(byte)
|
|
115
|
+
|
|
116
|
+
byte + 256
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def digit?(byte)
|
|
120
|
+
byte&.between?(48, 57)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def alpha?(byte)
|
|
124
|
+
byte&.between?(65, 90) || byte&.between?(97, 122)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
module RPMVersion
|
|
129
|
+
extend self
|
|
130
|
+
|
|
131
|
+
PART_PATTERN = /\A[0-9A-Za-z._+~^]+\z/
|
|
132
|
+
|
|
133
|
+
def compare(left, right)
|
|
134
|
+
left_epoch, left_version, left_release = split(left)
|
|
135
|
+
right_epoch, right_version, right_release = split(right)
|
|
136
|
+
|
|
137
|
+
comparison = VersionComparison.compare_numbers(left_epoch, right_epoch)
|
|
138
|
+
return comparison unless comparison.zero?
|
|
139
|
+
|
|
140
|
+
comparison = compare_part(left_version, right_version)
|
|
141
|
+
return comparison unless comparison.zero?
|
|
142
|
+
|
|
143
|
+
compare_part(left_release, right_release)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def split(value)
|
|
147
|
+
epoch, version = value.to_s.split(":", 2)
|
|
148
|
+
unless version
|
|
149
|
+
version = epoch
|
|
150
|
+
epoch = ""
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
separator = version.rindex("-")
|
|
154
|
+
return [epoch, version, ""] unless separator
|
|
155
|
+
|
|
156
|
+
[epoch, version[0...separator], version[(separator + 1)..]]
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def valid?(value)
|
|
160
|
+
string = value.to_s.strip
|
|
161
|
+
return false if string.empty? || string.end_with?("-")
|
|
162
|
+
|
|
163
|
+
epoch, version, release = split(string)
|
|
164
|
+
return false unless epoch.empty? || VersionComparison.numeric?(epoch)
|
|
165
|
+
|
|
166
|
+
version.match?(PART_PATTERN) && (release.empty? || release.match?(PART_PATTERN))
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def normalize(value)
|
|
170
|
+
value.to_s.strip
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def prerelease?(value)
|
|
174
|
+
_, version, release = split(value.to_s.strip)
|
|
175
|
+
version.include?("~") || release.include?("~")
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def compare_part(left, right)
|
|
179
|
+
left_index = 0
|
|
180
|
+
right_index = 0
|
|
181
|
+
|
|
182
|
+
while left_index < left.length || right_index < right.length
|
|
183
|
+
left_index = skip_separators(left, left_index)
|
|
184
|
+
right_index = skip_separators(right, right_index)
|
|
185
|
+
|
|
186
|
+
if marker_at?(left, left_index, 126) || marker_at?(right, right_index, 126)
|
|
187
|
+
return 1 unless marker_at?(left, left_index, 126)
|
|
188
|
+
return -1 unless marker_at?(right, right_index, 126)
|
|
189
|
+
|
|
190
|
+
left_index += 1
|
|
191
|
+
right_index += 1
|
|
192
|
+
next
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
if marker_at?(left, left_index, 94) || marker_at?(right, right_index, 94)
|
|
196
|
+
return -1 if left_index >= left.length
|
|
197
|
+
return 1 if right_index >= right.length
|
|
198
|
+
return 1 unless marker_at?(left, left_index, 94)
|
|
199
|
+
return -1 unless marker_at?(right, right_index, 94)
|
|
200
|
+
|
|
201
|
+
left_index += 1
|
|
202
|
+
right_index += 1
|
|
203
|
+
next
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
return (left.length - left_index) <=> (right.length - right_index) if left_index >= left.length || right_index >= right.length
|
|
207
|
+
|
|
208
|
+
left_numeric = digit?(left.getbyte(left_index))
|
|
209
|
+
right_numeric = digit?(right.getbyte(right_index))
|
|
210
|
+
return left_numeric ? 1 : -1 if left_numeric != right_numeric
|
|
211
|
+
|
|
212
|
+
left_end = scan_segment(left, left_index, left_numeric)
|
|
213
|
+
right_end = scan_segment(right, right_index, right_numeric)
|
|
214
|
+
comparison = if left_numeric
|
|
215
|
+
VersionComparison.compare_numbers(left[left_index...left_end], right[right_index...right_end])
|
|
216
|
+
else
|
|
217
|
+
left[left_index...left_end] <=> right[right_index...right_end]
|
|
218
|
+
end
|
|
219
|
+
return comparison unless comparison.zero?
|
|
220
|
+
|
|
221
|
+
left_index = left_end
|
|
222
|
+
right_index = right_end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
0
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def skip_separators(value, index)
|
|
229
|
+
while index < value.length
|
|
230
|
+
byte = value.getbyte(index)
|
|
231
|
+
break if alphanumeric?(byte) || byte == 126 || byte == 94
|
|
232
|
+
|
|
233
|
+
index += 1
|
|
234
|
+
end
|
|
235
|
+
index
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def marker_at?(value, index, marker)
|
|
239
|
+
index < value.length && value.getbyte(index) == marker
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def scan_segment(value, index, numeric)
|
|
243
|
+
if numeric
|
|
244
|
+
index += 1 while index < value.length && digit?(value.getbyte(index))
|
|
245
|
+
else
|
|
246
|
+
index += 1 while index < value.length && alpha?(value.getbyte(index))
|
|
247
|
+
end
|
|
248
|
+
index
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def digit?(byte)
|
|
252
|
+
byte&.between?(48, 57)
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def alpha?(byte)
|
|
256
|
+
byte&.between?(65, 90) || byte&.between?(97, 122)
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def alphanumeric?(byte)
|
|
260
|
+
digit?(byte) || alpha?(byte)
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
module ALPMVersion
|
|
265
|
+
extend self
|
|
266
|
+
|
|
267
|
+
def compare(left, right)
|
|
268
|
+
left_epoch, left_version, left_release, left_has_release = split(left)
|
|
269
|
+
right_epoch, right_version, right_release, right_has_release = split(right)
|
|
270
|
+
|
|
271
|
+
comparison = compare_part(left_epoch, right_epoch)
|
|
272
|
+
return comparison unless comparison.zero?
|
|
273
|
+
|
|
274
|
+
comparison = compare_part(left_version, right_version)
|
|
275
|
+
return comparison unless comparison.zero?
|
|
276
|
+
|
|
277
|
+
return compare_part(left_release, right_release) if left_has_release && right_has_release
|
|
278
|
+
|
|
279
|
+
0
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
def valid?(value)
|
|
283
|
+
string = value.to_s.strip
|
|
284
|
+
!string.empty? && !string.match?(/[[:space:]]/)
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
def normalize(value)
|
|
288
|
+
value.to_s.strip
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
def prerelease?(_value)
|
|
292
|
+
false
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
def split(value)
|
|
296
|
+
epoch, version = value.to_s.split(":", 2)
|
|
297
|
+
unless version
|
|
298
|
+
version = epoch
|
|
299
|
+
epoch = "0"
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
separator = version.rindex("-")
|
|
303
|
+
return [epoch, version, "", false] unless separator
|
|
304
|
+
|
|
305
|
+
[epoch, version[0...separator], version[(separator + 1)..], true]
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
def compare_part(left, right)
|
|
309
|
+
left_segments = segments(left)
|
|
310
|
+
right_segments = segments(right)
|
|
311
|
+
|
|
312
|
+
[left_segments.length, right_segments.length].max.times do |index|
|
|
313
|
+
left_segment = left_segments[index]
|
|
314
|
+
right_segment = right_segments[index]
|
|
315
|
+
return 0 unless left_segment || right_segment
|
|
316
|
+
return right_segment[0] == :alpha ? 1 : -1 unless left_segment
|
|
317
|
+
return left_segment[0] == :alpha ? -1 : 1 unless right_segment
|
|
318
|
+
|
|
319
|
+
left_kind, left_value = left_segment
|
|
320
|
+
right_kind, right_value = right_segment
|
|
321
|
+
if left_kind != right_kind
|
|
322
|
+
return 1 if left_kind == :digit
|
|
323
|
+
return -1 if right_kind == :digit
|
|
324
|
+
return 1 if left_kind == :other
|
|
325
|
+
return -1
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
comparison = case left_kind
|
|
329
|
+
when :digit
|
|
330
|
+
VersionComparison.compare_numbers(left_value, right_value)
|
|
331
|
+
when :alpha
|
|
332
|
+
left_value <=> right_value
|
|
333
|
+
else
|
|
334
|
+
left_value.length <=> right_value.length
|
|
335
|
+
end
|
|
336
|
+
return comparison unless comparison.zero?
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
0
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
def segments(value)
|
|
343
|
+
result = []
|
|
344
|
+
index = 0
|
|
345
|
+
while index < value.length
|
|
346
|
+
kind = segment_kind(value.getbyte(index))
|
|
347
|
+
ending = index + 1
|
|
348
|
+
ending += 1 while ending < value.length && segment_kind(value.getbyte(ending)) == kind
|
|
349
|
+
result << [kind, value[index...ending]]
|
|
350
|
+
index = ending
|
|
351
|
+
end
|
|
352
|
+
result
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
def segment_kind(byte)
|
|
356
|
+
return :digit if byte.between?(48, 57)
|
|
357
|
+
return :alpha if byte.between?(65, 90) || byte.between?(97, 122)
|
|
358
|
+
|
|
359
|
+
:other
|
|
360
|
+
end
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
module GentooVersion
|
|
364
|
+
extend self
|
|
365
|
+
|
|
366
|
+
PATTERN = /\A\d+(?:\.\d+)*[A-Za-z]?(?:_(?:p(?:re)?|beta|alpha|rc)\d*)*(?:-r\d+)?\z/
|
|
367
|
+
|
|
368
|
+
SUFFIX_RANKS = {
|
|
369
|
+
"alpha" => -4,
|
|
370
|
+
"beta" => -3,
|
|
371
|
+
"pre" => -2,
|
|
372
|
+
"rc" => -1,
|
|
373
|
+
"p" => 1
|
|
374
|
+
}.freeze
|
|
375
|
+
|
|
376
|
+
def compare(left, right)
|
|
377
|
+
left_version, left_revision = split_revision(left)
|
|
378
|
+
right_version, right_revision = split_revision(right)
|
|
379
|
+
return VersionComparison.compare_numbers(left_revision, right_revision) if left_version == right_version
|
|
380
|
+
|
|
381
|
+
left_base, left_suffixes = split_base(left_version)
|
|
382
|
+
right_base, right_suffixes = split_base(right_version)
|
|
383
|
+
comparison = compare_base(left_base, right_base)
|
|
384
|
+
return comparison unless comparison.zero?
|
|
385
|
+
|
|
386
|
+
comparison = compare_suffixes(left_suffixes, right_suffixes)
|
|
387
|
+
return comparison unless comparison.zero?
|
|
388
|
+
|
|
389
|
+
VersionComparison.compare_numbers(left_revision, right_revision)
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
def valid?(value)
|
|
393
|
+
value.to_s.strip.match?(PATTERN)
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
def normalize(value)
|
|
397
|
+
value.to_s.strip
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
def prerelease?(value)
|
|
401
|
+
version, = split_revision(value.to_s.strip)
|
|
402
|
+
_, suffixes = split_base(version)
|
|
403
|
+
suffixes&.any? { |suffix| SUFFIX_RANKS.fetch(parse_suffix(suffix).first, 0).negative? } || false
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
def split_revision(value)
|
|
407
|
+
match = /-r(\d+)\z/.match(value.to_s)
|
|
408
|
+
match ? [value[0...match.begin(0)], match[1]] : [value.to_s, ""]
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
def split_base(value)
|
|
412
|
+
base, suffixes = value.split("_", 2)
|
|
413
|
+
[base, suffixes&.split("_", -1)]
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
def compare_base(left, right)
|
|
417
|
+
left_value, left_letter = split_letter(left)
|
|
418
|
+
right_value, right_letter = split_letter(right)
|
|
419
|
+
left_parts = left_value.split(".", -1)
|
|
420
|
+
right_parts = right_value.split(".", -1)
|
|
421
|
+
|
|
422
|
+
[left_parts.length, right_parts.length].min.times do |index|
|
|
423
|
+
comparison = compare_component(index, left_parts[index], right_parts[index])
|
|
424
|
+
return comparison unless comparison.zero?
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
comparison = left_parts.length <=> right_parts.length
|
|
428
|
+
return comparison unless comparison.zero?
|
|
429
|
+
|
|
430
|
+
left_letter <=> right_letter
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
def split_letter(value)
|
|
434
|
+
return [value[0...-1], value.getbyte(-1)] if value.match?(/[A-Za-z]\z/)
|
|
435
|
+
|
|
436
|
+
[value, -1]
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
def compare_component(index, left, right)
|
|
440
|
+
return 0 if left == right
|
|
441
|
+
return VersionComparison.compare_numbers(left, right) if index.zero? || (!left.start_with?("0") && !right.start_with?("0"))
|
|
442
|
+
|
|
443
|
+
left.sub(/0+\z/, "") <=> right.sub(/0+\z/, "")
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
def compare_suffixes(left, right)
|
|
447
|
+
index = 0
|
|
448
|
+
loop do
|
|
449
|
+
left_suffix = left&.[](index)
|
|
450
|
+
right_suffix = right&.[](index)
|
|
451
|
+
return 0 unless left_suffix || right_suffix
|
|
452
|
+
unless left_suffix
|
|
453
|
+
kind, number = parse_suffix(right_suffix)
|
|
454
|
+
rank = SUFFIX_RANKS.fetch(kind, 0)
|
|
455
|
+
return 0 <=> rank unless rank.zero?
|
|
456
|
+
return VersionComparison.compare_numbers("0", number)
|
|
457
|
+
end
|
|
458
|
+
unless right_suffix
|
|
459
|
+
kind, number = parse_suffix(left_suffix)
|
|
460
|
+
rank = SUFFIX_RANKS.fetch(kind, 0)
|
|
461
|
+
return rank <=> 0 unless rank.zero?
|
|
462
|
+
return VersionComparison.compare_numbers(number, "0")
|
|
463
|
+
end
|
|
464
|
+
|
|
465
|
+
left_kind, left_number = parse_suffix(left_suffix)
|
|
466
|
+
right_kind, right_number = parse_suffix(right_suffix)
|
|
467
|
+
comparison = SUFFIX_RANKS.fetch(left_kind, 0) <=> SUFFIX_RANKS.fetch(right_kind, 0)
|
|
468
|
+
return comparison unless comparison.zero?
|
|
469
|
+
|
|
470
|
+
comparison = VersionComparison.compare_numbers(left_number, right_number)
|
|
471
|
+
return comparison unless comparison.zero?
|
|
472
|
+
|
|
473
|
+
index += 1
|
|
474
|
+
end
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
def parse_suffix(value)
|
|
478
|
+
match = /(\d*)\z/.match(value.to_s)
|
|
479
|
+
[value.to_s[0...match.begin(0)], match[1]]
|
|
480
|
+
end
|
|
481
|
+
end
|
|
482
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "version_comparison"
|
|
4
|
+
|
|
5
|
+
module Vers
|
|
6
|
+
module GemVersion
|
|
7
|
+
extend self
|
|
8
|
+
|
|
9
|
+
Segment = Data.define(:value, :numeric)
|
|
10
|
+
PATTERN = /\A[0-9]+(?:\.[0-9A-Za-z]+)*(?:-[0-9A-Za-z][0-9A-Za-z.-]*)?\z/
|
|
11
|
+
|
|
12
|
+
def compare(left, right)
|
|
13
|
+
compare_segments(parse(left), parse(right))
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def parse(value)
|
|
17
|
+
segments = []
|
|
18
|
+
|
|
19
|
+
value.to_s.strip.scan(/-|\d+|[A-Za-z]+/) do |part|
|
|
20
|
+
if part == "-"
|
|
21
|
+
segments << Segment.new("pre", false)
|
|
22
|
+
else
|
|
23
|
+
segments << Segment.new(part, VersionComparison.numeric?(part))
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
first_text = segments.index { |segment| !segment.numeric }
|
|
28
|
+
if first_text && first_text.positive?
|
|
29
|
+
start = first_text
|
|
30
|
+
start -= 1 while start.positive? && segments[start - 1].numeric && VersionComparison.compare_numbers(segments[start - 1].value, "0").zero?
|
|
31
|
+
segments.slice!(start...first_text) if start < first_text
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
segments.pop while segments.last&.numeric && VersionComparison.compare_numbers(segments.last.value, "0").zero?
|
|
35
|
+
segments
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def valid?(value)
|
|
39
|
+
value.to_s.strip.match?(PATTERN)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def normalize(value)
|
|
43
|
+
value.to_s.strip
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def prerelease?(value)
|
|
47
|
+
parse(value).any? { |segment| !segment.numeric }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def compare_segments(left, right)
|
|
51
|
+
left.zip(right).each do |left_segment, right_segment|
|
|
52
|
+
break unless left_segment && right_segment
|
|
53
|
+
|
|
54
|
+
if left_segment.numeric != right_segment.numeric
|
|
55
|
+
return left_segment.numeric ? 1 : -1
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
comparison = if left_segment.numeric
|
|
59
|
+
VersionComparison.compare_numbers(left_segment.value, right_segment.value)
|
|
60
|
+
else
|
|
61
|
+
left_segment.value <=> right_segment.value
|
|
62
|
+
end
|
|
63
|
+
return comparison unless comparison.zero?
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
return 0 if left.length == right.length
|
|
67
|
+
return compare_missing(right.drop(left.length)) if left.length < right.length
|
|
68
|
+
|
|
69
|
+
-compare_missing(left.drop(right.length))
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def compare_missing(segments)
|
|
73
|
+
segments.each do |segment|
|
|
74
|
+
return 1 unless segment.numeric
|
|
75
|
+
return -1 unless VersionComparison.compare_numbers(segment.value, "0").zero?
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
0
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|