semver_dialects 3.2.0 → 3.3.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/lib/semver_dialects/apk.rb +242 -0
- data/lib/semver_dialects/rpm.rb +18 -17
- 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/rpm.rb
CHANGED
|
@@ -86,43 +86,43 @@ module SemverDialects
|
|
|
86
86
|
TILDE = /~/
|
|
87
87
|
DIGIT = /([0-9]+)/
|
|
88
88
|
COLON = /:/
|
|
89
|
-
|
|
89
|
+
NON_ALPHANUMERIC_DASH_TILDE_AND_WHITESPACE = /[^a-zA-Z0-9~\s]+/
|
|
90
|
+
WHITE_SPACE = /\s/
|
|
90
91
|
|
|
91
92
|
def self.parse(input)
|
|
92
93
|
new(input).parse
|
|
93
94
|
end
|
|
94
|
-
|
|
95
|
+
|
|
95
96
|
def initialize(input)
|
|
97
|
+
@input = input
|
|
96
98
|
@scanner = StringScanner.new(input)
|
|
97
99
|
end
|
|
98
|
-
|
|
100
|
+
|
|
99
101
|
# parse splits the input string into epoch, version and release tag Eg: <epoch>:<version>-<release_tag>
|
|
100
102
|
# The version and release tag are split at the first `-` character if present
|
|
101
103
|
# With the segment before the first `-` being version while the other being release tag
|
|
102
104
|
# Subsequent `-` are disregarded
|
|
103
105
|
def parse
|
|
104
106
|
epoch = nil
|
|
105
|
-
if s = scanner.scan(/\d+:/)
|
|
107
|
+
if (s = scanner.scan(/\d+:/))
|
|
106
108
|
epoch = s[..-2].to_i
|
|
107
109
|
end
|
|
108
110
|
|
|
109
111
|
# parse tokens until we reach the release tag, if any
|
|
110
112
|
tokens = parse_tokens(false)
|
|
111
|
-
|
|
113
|
+
|
|
112
114
|
# parse release tag
|
|
113
115
|
release_tag = nil
|
|
114
|
-
if scanner.rest?
|
|
115
|
-
release_tag = ReleaseTag.new(parse_tokens(true))
|
|
116
|
-
end
|
|
116
|
+
release_tag = ReleaseTag.new(parse_tokens(true)) if scanner.rest?
|
|
117
117
|
|
|
118
118
|
raise IncompleteScanError, scanner.rest if scanner.rest?
|
|
119
|
-
|
|
119
|
+
|
|
120
120
|
Version.new(tokens, epoch: epoch, release_tag: release_tag)
|
|
121
121
|
end
|
|
122
|
-
|
|
122
|
+
|
|
123
123
|
private
|
|
124
|
-
|
|
125
|
-
attr_reader :scanner
|
|
124
|
+
|
|
125
|
+
attr_reader :scanner, :input
|
|
126
126
|
|
|
127
127
|
def parse_tokens(stop_at_release_tag)
|
|
128
128
|
tokens = []
|
|
@@ -130,9 +130,7 @@ module SemverDialects
|
|
|
130
130
|
until scanner.eos?
|
|
131
131
|
case
|
|
132
132
|
when (s = scanner.scan(DASH))
|
|
133
|
-
|
|
134
|
-
return tokens
|
|
135
|
-
end
|
|
133
|
+
return tokens unless stop_at_release_tag
|
|
136
134
|
# If release tag has been encountered, ignore subsequent dashes
|
|
137
135
|
when (s = scanner.scan(ALPHABET))
|
|
138
136
|
tokens << s
|
|
@@ -140,7 +138,11 @@ module SemverDialects
|
|
|
140
138
|
tokens << s
|
|
141
139
|
when (s = scanner.scan(DIGIT))
|
|
142
140
|
tokens << s.to_i
|
|
143
|
-
when (s = scanner.scan(
|
|
141
|
+
when (s = scanner.scan(WHITE_SPACE))
|
|
142
|
+
# Whitespace is not permitted
|
|
143
|
+
# https://github.com/rpm-software-management/rpm/blob/4d1b7401415003720ea9bef7bda248f7de4fa025/docs/manual/dependencies.md#versioning
|
|
144
|
+
raise SemverDialects::InvalidVersionError, input
|
|
145
|
+
when (s = scanner.scan(NON_ALPHANUMERIC_DASH_TILDE_AND_WHITESPACE))
|
|
144
146
|
# Non-ascii characters are considered equal
|
|
145
147
|
# so they are ignored when parsing versions
|
|
146
148
|
# https://github.com/rpm-software-management/rpm/blob/rpm-4.19.1.1-release/tests/rpmvercmp.at#L143
|
|
@@ -151,6 +153,5 @@ module SemverDialects
|
|
|
151
153
|
tokens
|
|
152
154
|
end
|
|
153
155
|
end
|
|
154
|
-
|
|
155
156
|
end
|
|
156
157
|
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-
|
|
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
|