semver_dialects 3.2.1 → 3.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/semver_dialects/apk.rb +242 -0
- data/lib/semver_dialects/version.rb +1 -1
- data/lib/semver_dialects.rb +17 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9358b5e27d5e1128e133c3ea3fc04bfdce8d35d8db3479a8edb367eba5d5b766
|
4
|
+
data.tar.gz: 334101a02b1af9ab5368ec7003c02d344d3f0e45294c107cda841da3b5e40c86
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 94af1e53527c2b56ab22c0792bc085d9c594abd1a5285aa73d8b968cfee761566967d7b093aaa10353d79c684fec71f6ed3edf83f55e37cf02a054f7dcaa4c41
|
7
|
+
data.tar.gz: c4aefce6856543bae0aca7cc07f706b023ee9d345f3a18c2d68b3178be4f781963a32de3d75cdf179f2d9549985d217a8dc00c4588a0e0c35b4391eae3efaea6
|
@@ -0,0 +1,242 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'strscan'
|
4
|
+
|
5
|
+
module SemverDialects
|
6
|
+
module Apk
|
7
|
+
# This implementation references the version.c apk-tools implementation
|
8
|
+
# https://gitlab.alpinelinux.org/alpine/apk-tools/-/blob/6052bfef57a81d82451b4cad86f78a2d01959767/src/version.c
|
9
|
+
# apk version spec can be found here https://wiki.alpinelinux.org/wiki/APKBUILD_Reference#pkgver
|
10
|
+
class Version < BaseVersion
|
11
|
+
PRE_RELEASE_ORDER = { 'alpha' => 0, 'beta' => 1, 'pre' => 2, 'rc' => 3 }.freeze
|
12
|
+
POST_RELEASE_ORDER = { 'cvs' => 0, 'svn' => 1, 'git' => 2, 'hg' => 3, 'p' => 4 }.freeze
|
13
|
+
|
14
|
+
attr_reader :tokens, :pre_release, :post_release, :revision
|
15
|
+
|
16
|
+
def initialize(tokens, pre_release: [], post_release: [], revision: [])
|
17
|
+
@tokens = tokens
|
18
|
+
@pre_release = pre_release
|
19
|
+
@post_release = post_release
|
20
|
+
@revision = revision
|
21
|
+
end
|
22
|
+
|
23
|
+
def <=>(other)
|
24
|
+
cmp = compare_tokens(tokens, other.tokens)
|
25
|
+
return cmp unless cmp.zero?
|
26
|
+
|
27
|
+
cmp = compare_pre_release(pre_release, other.pre_release)
|
28
|
+
return cmp unless cmp.zero?
|
29
|
+
|
30
|
+
cmp = compare_post_release(post_release, other.post_release)
|
31
|
+
return cmp unless cmp.zero?
|
32
|
+
|
33
|
+
compare_revisions(revision, other.revision)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Note that to_s does not accurately recreate the version string
|
37
|
+
# if alphabets are present in the version segment.
|
38
|
+
# For instance 1.2.a or 1.2a would both be returned as 1.2.a with to_s
|
39
|
+
# More details in https://gitlab.com/gitlab-org/ruby/gems/semver_dialects/-/merge_requests/97#note_1989192447
|
40
|
+
def to_s
|
41
|
+
@to_s ||= begin
|
42
|
+
main = tokens.join('.')
|
43
|
+
main += "_#{pre_release.join('')}" unless pre_release.empty?
|
44
|
+
main += "_#{post_release.join('')}" unless post_release.empty?
|
45
|
+
main += "-#{revision.join('')}" unless revision.empty?
|
46
|
+
main
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
# Token can be either integer or string
|
53
|
+
# Precedence: numeric token > string token > no token
|
54
|
+
def compare_token_pair(a, b)
|
55
|
+
return 1 if !a.nil? && b.nil?
|
56
|
+
return -1 if a.nil? && !b.nil?
|
57
|
+
|
58
|
+
return 1 if a.is_a?(Integer) && b.is_a?(String)
|
59
|
+
return -1 if a.is_a?(String) && b.is_a?(Integer)
|
60
|
+
|
61
|
+
# Remaining scenario are tokens of the same type ie Integer or String. Use <=> to compare
|
62
|
+
a <=> b
|
63
|
+
end
|
64
|
+
|
65
|
+
# Precedence: post-release > no release > pre-release
|
66
|
+
# https://wiki.alpinelinux.org/wiki/APKBUILD_Reference#pkgver
|
67
|
+
def compare_pre_release(a, b)
|
68
|
+
return 0 if a.empty? && b.empty?
|
69
|
+
return -1 if !a.empty? && b.empty?
|
70
|
+
return 1 if a.empty? && !b.empty?
|
71
|
+
|
72
|
+
compare_suffix(a, b, PRE_RELEASE_ORDER)
|
73
|
+
end
|
74
|
+
|
75
|
+
# Precedence: post-release > no release > pre-release
|
76
|
+
# https://wiki.alpinelinux.org/wiki/APKBUILD_Reference#pkgver
|
77
|
+
def compare_post_release(a, b)
|
78
|
+
return 0 if a.empty? && b.empty?
|
79
|
+
return 1 if !a.empty? && b.empty?
|
80
|
+
return -1 if a.empty? && !b.empty?
|
81
|
+
|
82
|
+
compare_suffix(a, b, POST_RELEASE_ORDER)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Pre-release precedence: alpha < beta < pre < rc
|
86
|
+
# Post-release precedence: cvs < svn < git < hg < p
|
87
|
+
# Precedence for releases with number eg alpha1:
|
88
|
+
# release without number < release with number
|
89
|
+
def compare_suffix(a, b, order)
|
90
|
+
a_suffix = order[a[0]]
|
91
|
+
b_suffix = order[b[0]]
|
92
|
+
|
93
|
+
return 1 if a_suffix > b_suffix
|
94
|
+
return -1 if a_suffix < b_suffix
|
95
|
+
|
96
|
+
a_value = a[1]
|
97
|
+
b_value = b[1]
|
98
|
+
|
99
|
+
return 1 if !a_value.nil? && b_value.nil?
|
100
|
+
return -1 if a_value.nil? && !b_value.nil?
|
101
|
+
|
102
|
+
(a_value || 0) <=> (b_value || 0)
|
103
|
+
end
|
104
|
+
|
105
|
+
def compare_revisions(a, b)
|
106
|
+
return 0 if a.empty? && b.empty?
|
107
|
+
return 1 if !a.empty? && b.empty?
|
108
|
+
return -1 if a.empty? && !b.empty?
|
109
|
+
|
110
|
+
a_value = a[1]
|
111
|
+
b_value = b[1]
|
112
|
+
|
113
|
+
return 1 if !a_value.nil? && b_value.nil?
|
114
|
+
return -1 if a_value.nil? && !b_value.nil?
|
115
|
+
|
116
|
+
(a_value || 0) <=> (b_value || 0)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
class VersionParser
|
121
|
+
DASH = /-/
|
122
|
+
ALPHABETS = /([a-zA-Z]+)/
|
123
|
+
DIGITS = /([0-9]+)/
|
124
|
+
DIGIT = /[0-9]/
|
125
|
+
DOT = '.'
|
126
|
+
UNDERSCORE = '_'
|
127
|
+
PRE_RELEASE_SUFFIXES = %w[alpha beta pre rc].freeze
|
128
|
+
POST_RELEASE_SUFFIXES = %w[cvs svn git hg p].freeze
|
129
|
+
WHITE_SPACE = /\s/
|
130
|
+
|
131
|
+
def self.parse(input)
|
132
|
+
new(input).parse
|
133
|
+
end
|
134
|
+
|
135
|
+
attr_reader :scanner, :input
|
136
|
+
|
137
|
+
def initialize(input)
|
138
|
+
@input = input
|
139
|
+
@pre_release = []
|
140
|
+
@post_release = []
|
141
|
+
@revision = []
|
142
|
+
@scanner = StringScanner.new(input)
|
143
|
+
end
|
144
|
+
|
145
|
+
# Parse splits the raw version string into:
|
146
|
+
# version, pre_release, post_release and revision
|
147
|
+
# Format: <version>_<release>-<revision>
|
148
|
+
# Note that version segment can contain alphabets
|
149
|
+
# Release is always preceded with `_`
|
150
|
+
# Revision is always preceded with `-`
|
151
|
+
def parse
|
152
|
+
tokens = parse_tokens
|
153
|
+
|
154
|
+
Version.new(tokens, pre_release: @pre_release, post_release: @post_release, revision: @revision)
|
155
|
+
end
|
156
|
+
|
157
|
+
private
|
158
|
+
|
159
|
+
def parse_tokens
|
160
|
+
tokens = []
|
161
|
+
|
162
|
+
until scanner.eos?
|
163
|
+
case
|
164
|
+
when (s = scanner.scan(ALPHABETS))
|
165
|
+
tokens << s
|
166
|
+
when (s = scanner.scan(DIGITS))
|
167
|
+
# TODO: add support to parse numbers with leading zero https://gitlab.com/gitlab-org/gitlab/-/issues/471509
|
168
|
+
raise SemverDialects::UnsupportedVersionError, input if s.start_with?('0') && s.length > 1
|
169
|
+
|
170
|
+
tokens << s.to_i
|
171
|
+
when (s = scanner.scan(UNDERSCORE))
|
172
|
+
parse_release
|
173
|
+
# Continue parsing if there's remaining tokens since revision which comes after release is optional
|
174
|
+
return tokens if scanner.eos?
|
175
|
+
when (s = scanner.scan(DASH))
|
176
|
+
parse_revision
|
177
|
+
return tokens
|
178
|
+
when (s = scanner.scan(WHITE_SPACE))
|
179
|
+
# Raise error if there's whitespace
|
180
|
+
raise SemverDialects::InvalidVersionError, input
|
181
|
+
when (s = scanner.scan(DOT))
|
182
|
+
# Skip parsing dot
|
183
|
+
else
|
184
|
+
raise SemverDialects::IncompleteScanError, scanner.rest
|
185
|
+
end
|
186
|
+
end
|
187
|
+
tokens
|
188
|
+
end
|
189
|
+
|
190
|
+
# PRE_RELEASE_SUFFIXES: alpha, beta, pre, rc
|
191
|
+
# POST_RELEASE_SUFFIXES: cvs, svn, git, hg, p
|
192
|
+
# No other suffixes are allowed
|
193
|
+
# Release can be either `<suffix>` or `<suffix><number>` with the number being optional
|
194
|
+
def parse_release
|
195
|
+
# TODO: Add support to parse version with multiple releases
|
196
|
+
raise SemverDialects::UnsupportedVersionError, input if !@pre_release.empty? || !@post_release.empty?
|
197
|
+
|
198
|
+
suffix_type = nil
|
199
|
+
until scanner.eos?
|
200
|
+
case
|
201
|
+
when (s = scanner.scan(ALPHABETS))
|
202
|
+
if PRE_RELEASE_SUFFIXES.include?(s)
|
203
|
+
suffix_type = :pre
|
204
|
+
@pre_release << s
|
205
|
+
elsif POST_RELEASE_SUFFIXES.include?(s)
|
206
|
+
suffix_type = :post
|
207
|
+
@post_release << s
|
208
|
+
else
|
209
|
+
raise SemverDialects::InvalidVersionError, input
|
210
|
+
end
|
211
|
+
return unless scanner.peek(1) =~ DIGIT
|
212
|
+
when (s = scanner.scan(DIGITS))
|
213
|
+
if suffix_type == :pre
|
214
|
+
@pre_release << s.to_i
|
215
|
+
return
|
216
|
+
elsif suffix_type == :post
|
217
|
+
@post_release << s.to_i
|
218
|
+
return
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
# Revision can be either `r` or `r<number>` with the number being optional
|
225
|
+
def parse_revision
|
226
|
+
until scanner.eos?
|
227
|
+
case
|
228
|
+
when (s = scanner.scan(ALPHABETS))
|
229
|
+
raise SemverDialects::InvalidVersionError, input unless s == 'r'
|
230
|
+
|
231
|
+
@revision << s
|
232
|
+
|
233
|
+
return unless scanner.peek(1) =~ DIGIT
|
234
|
+
when (s = scanner.scan(DIGITS))
|
235
|
+
@revision << s.to_i
|
236
|
+
return
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
data/lib/semver_dialects.rb
CHANGED
@@ -4,6 +4,7 @@ require 'semver_dialects/version'
|
|
4
4
|
require 'semver_dialects/base_version'
|
5
5
|
require 'semver_dialects/maven'
|
6
6
|
require 'semver_dialects/rpm'
|
7
|
+
require 'semver_dialects/apk'
|
7
8
|
require 'semver_dialects/semver2'
|
8
9
|
require 'semver_dialects/semantic_version'
|
9
10
|
require 'semver_dialects/boundary'
|
@@ -28,6 +29,16 @@ module SemverDialects
|
|
28
29
|
end
|
29
30
|
end
|
30
31
|
|
32
|
+
class UnsupportedVersionError < Error
|
33
|
+
def initialize(raw_version)
|
34
|
+
@raw_version = raw_version
|
35
|
+
end
|
36
|
+
|
37
|
+
def message
|
38
|
+
"unsupported version '#{@raw_version}'"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
31
42
|
class InvalidVersionError < Error
|
32
43
|
def initialize(raw_version)
|
33
44
|
@raw_version = raw_version
|
@@ -86,7 +97,7 @@ module SemverDialects
|
|
86
97
|
end
|
87
98
|
|
88
99
|
def self.os_pkg_version_satisfies?(typ, raw_ver, raw_constraint)
|
89
|
-
return unless %w[deb rpm].include?(typ)
|
100
|
+
return unless %w[deb rpm apk].include?(typ)
|
90
101
|
# we only support the less than operator, because that's the only one currently output
|
91
102
|
# by the advisory exporter for operating system packages.
|
92
103
|
raise SemverDialects::InvalidConstraintError, raw_constraint unless raw_constraint[0] == '<'
|
@@ -101,6 +112,11 @@ module SemverDialects
|
|
101
112
|
v1 = Rpm::VersionParser.parse(raw_ver)
|
102
113
|
v2 = Rpm::VersionParser.parse(raw_constraint[1..])
|
103
114
|
|
115
|
+
v1 < v2
|
116
|
+
when 'apk'
|
117
|
+
v1 = Apk::VersionParser.parse(raw_ver)
|
118
|
+
v2 = Apk::VersionParser.parse(raw_constraint[1..])
|
119
|
+
|
104
120
|
v1 < v2
|
105
121
|
end
|
106
122
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: semver_dialects
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Julian Thome
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: exe
|
12
12
|
cert_chain: []
|
13
|
-
date: 2024-07-
|
13
|
+
date: 2024-07-10 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: pastel
|
@@ -177,6 +177,7 @@ extensions: []
|
|
177
177
|
extra_rdoc_files: []
|
178
178
|
files:
|
179
179
|
- lib/semver_dialects.rb
|
180
|
+
- lib/semver_dialects/apk.rb
|
180
181
|
- lib/semver_dialects/base_version.rb
|
181
182
|
- lib/semver_dialects/boundary.rb
|
182
183
|
- lib/semver_dialects/cli.rb
|