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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 485026a6d305008958423bb059e470b3d8c5561a6cecc1e0cb33c562df01e255
4
- data.tar.gz: b9ff4d4852dbdc5a7d6356b7d4bc9691c2c3e38ba9bbd8220e1169a53b8100b7
3
+ metadata.gz: 9358b5e27d5e1128e133c3ea3fc04bfdce8d35d8db3479a8edb367eba5d5b766
4
+ data.tar.gz: 334101a02b1af9ab5368ec7003c02d344d3f0e45294c107cda841da3b5e40c86
5
5
  SHA512:
6
- metadata.gz: 29500b37adb16288fbd88e504f7b631a2137b36a0cf5a3b544662bef93f8272fdea9d7a16a637d5c9000e95f860b101149f3885185dfdf26a8e506a8763af4e3
7
- data.tar.gz: 784199112df64433895f31b1a9dc0a9c73b0097d331be26fa3a1d112398e1cdc554d43bc9007b875e3e6d08f687c3e73e8e45323a48404ff59f869a04e5d19a8
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SemverDialects
4
- VERSION = '3.2.1'
4
+ VERSION = '3.3.0'
5
5
  end
@@ -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.2.1
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-02 00:00:00.000000000 Z
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