semver_dialects 3.2.1 → 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 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