vers 1.3.1 → 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.
@@ -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