semver_dialects 2.0.2 → 3.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.
- checksums.yaml +4 -4
- data/lib/semver_dialects/base_version.rb +79 -0
- data/lib/semver_dialects/boundary.rb +142 -0
- data/lib/semver_dialects/commands/check_version.rb +2 -5
- data/lib/semver_dialects/interval.rb +256 -0
- data/lib/semver_dialects/interval_parser.rb +60 -0
- data/lib/semver_dialects/interval_set.rb +121 -0
- data/lib/semver_dialects/interval_set_parser.rb +205 -0
- data/lib/semver_dialects/maven.rb +197 -0
- data/lib/semver_dialects/semantic_version.rb +294 -0
- data/lib/semver_dialects/semver2.rb +159 -0
- data/lib/semver_dialects/version.rb +1 -1
- data/lib/semver_dialects.rb +152 -63
- metadata +57 -12
- data/lib/semver_dialects/semantic_version/semantic_version.rb +0 -337
- data/lib/semver_dialects/semantic_version/version_cut.rb +0 -176
- data/lib/semver_dialects/semantic_version/version_interval.rb +0 -288
- data/lib/semver_dialects/semantic_version/version_parser.rb +0 -40
- data/lib/semver_dialects/semantic_version/version_range.rb +0 -191
- data/lib/semver_dialects/semantic_version/version_translator.rb +0 -172
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 19c2562420fb945c665db99cfeb6ac46186f65b10b4a84ba073aa8e0b443666e
|
4
|
+
data.tar.gz: 73d80ecdaedfc906f4e24f1c9f5921f18e126c19d73722991de7f3d61d5b2f24
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 28e168351b5eda9bf282416c58b52d5fb60ea768f5cb4ecc59c75a16c212b1425878d7a68133538eefea885dd4d430e7fc2d47359b103b31e72970fc69b88643
|
7
|
+
data.tar.gz: b20653d1d36eb4e21de3ae2e5962e1435885dd4a40988279394f733876eb6b4de9d6e6c04badd0222c14cb1af5ce00b9ac0abd747db7eab1908a3d5db3fa1f0b
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Class that describes a version that can be compared to another version in a generic way.
|
4
|
+
#
|
5
|
+
# A version is made of +tokens+ and an optional +addition+.
|
6
|
+
# Tokens and additions must be comparable using the spaceship operator.
|
7
|
+
# An addition behaves like a version and must respond to +tokens+.
|
8
|
+
#
|
9
|
+
# Tokens are used to represent the dot separated list of segments
|
10
|
+
# like 1.2.3 (semantic version) or alpha.1 (pre-release tag).
|
11
|
+
#
|
12
|
+
# Version 1.2.3-alpha.1-2024.03.25 is made of 3 +Version+ objects
|
13
|
+
# whose tokens are represented as 1.2.3, alpha.1, 2024.03.25.
|
14
|
+
# Version alpha.1 is the addition of version 1.2.3,
|
15
|
+
# and version 2024.03.25 is the addition of version alpha.1.
|
16
|
+
#
|
17
|
+
# This class can support of the comparison logic of many syntaxes
|
18
|
+
# by implementing specific token classes.
|
19
|
+
#
|
20
|
+
module SemverDialects
|
21
|
+
class BaseVersion
|
22
|
+
include Comparable
|
23
|
+
|
24
|
+
attr_reader :tokens, :addition
|
25
|
+
|
26
|
+
def initialize(tokens, addition: nil)
|
27
|
+
@tokens = tokens
|
28
|
+
@addition = addition
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_s
|
32
|
+
main = tokens.join('.')
|
33
|
+
main += "-#{addition}" if addition
|
34
|
+
main
|
35
|
+
end
|
36
|
+
|
37
|
+
def <=>(other)
|
38
|
+
cmp = compare_tokens(tokens, other.tokens)
|
39
|
+
return cmp unless cmp == 0
|
40
|
+
|
41
|
+
compare_additions(addition, other.addition)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Returns true if the version tokens are equivalent to zero
|
45
|
+
# and the addition is also equivalent to zero.
|
46
|
+
def is_zero?
|
47
|
+
return false if compare_tokens(tokens, [0]) != 0
|
48
|
+
|
49
|
+
return true if addition.nil?
|
50
|
+
|
51
|
+
addition.is_zero?
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def compare_tokens(a, b) # rubocop:disable Naming/MethodParameterName
|
57
|
+
max_idx = [a.size, b.size].max - 1
|
58
|
+
(0..max_idx).each do |idx|
|
59
|
+
cmp = compare_token_pair(a[idx], b[idx])
|
60
|
+
return cmp unless cmp == 0
|
61
|
+
end
|
62
|
+
0
|
63
|
+
end
|
64
|
+
|
65
|
+
def compare_token_pair(a, b) # rubocop:disable Naming/MethodParameterName
|
66
|
+
(a || 0) <=> (b || 0)
|
67
|
+
end
|
68
|
+
|
69
|
+
def compare_additions(a, b) # rubocop:disable Naming/MethodParameterName
|
70
|
+
return 0 if a.nil? && b.nil?
|
71
|
+
|
72
|
+
(a || empty_addition).<=>(b || empty_addition)
|
73
|
+
end
|
74
|
+
|
75
|
+
def empty_addition
|
76
|
+
self.class.new([])
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Boundary is a boundary used in an interval.
|
4
|
+
# It can either be above all versions (infinity),
|
5
|
+
# below all versions (negative infinity), or any version.
|
6
|
+
module SemverDialects
|
7
|
+
class Boundary
|
8
|
+
attr_accessor :semver
|
9
|
+
|
10
|
+
def initialize(semver)
|
11
|
+
@semver = semver
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_s
|
15
|
+
@semver.to_s
|
16
|
+
end
|
17
|
+
|
18
|
+
def <(other)
|
19
|
+
if other.instance_of?(BelowAll)
|
20
|
+
false
|
21
|
+
else
|
22
|
+
other.instance_of?(AboveAll) ? true : @semver < other.semver
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def >(other)
|
27
|
+
if other.instance_of?(BelowAll)
|
28
|
+
true
|
29
|
+
else
|
30
|
+
other.instance_of?(AboveAll) ? false : @semver > other.semver
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def <=(other)
|
35
|
+
self < other || self == other
|
36
|
+
end
|
37
|
+
|
38
|
+
def >=(other)
|
39
|
+
self > other || self == other
|
40
|
+
end
|
41
|
+
|
42
|
+
def ==(other)
|
43
|
+
# self cannot be BelowAll or AboveAll
|
44
|
+
if other.instance_of?(BelowAll) || other.instance_of?(AboveAll)
|
45
|
+
false
|
46
|
+
else
|
47
|
+
@semver == other.semver
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def !=(other)
|
52
|
+
# self cannot be BelowAll or AboveAll
|
53
|
+
if other.instance_of?(BelowAll) || other.instance_of?(AboveAll)
|
54
|
+
false
|
55
|
+
else
|
56
|
+
@semver != other.semver
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def is_initial_version?
|
61
|
+
@semver.is_zero?
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# BelowAll represents a boundary below all possible versions.
|
66
|
+
# When used as the lower boundary of an interval, any version
|
67
|
+
# that is smaller than the upper boundary is in the interval.
|
68
|
+
class BelowAll < Boundary
|
69
|
+
def initialize; end
|
70
|
+
|
71
|
+
def to_s
|
72
|
+
'-inf'
|
73
|
+
end
|
74
|
+
|
75
|
+
def is_initial_version?
|
76
|
+
false
|
77
|
+
end
|
78
|
+
|
79
|
+
def <(other)
|
80
|
+
other.instance_of?(BelowAll) ? false : true
|
81
|
+
end
|
82
|
+
|
83
|
+
def >(_other)
|
84
|
+
false
|
85
|
+
end
|
86
|
+
|
87
|
+
def <=(other)
|
88
|
+
self < other || self == other
|
89
|
+
end
|
90
|
+
|
91
|
+
def >=(other)
|
92
|
+
self > other || self == other
|
93
|
+
end
|
94
|
+
|
95
|
+
def ==(other)
|
96
|
+
(other.instance_of? BelowAll) ? true : false
|
97
|
+
end
|
98
|
+
|
99
|
+
def !=(other)
|
100
|
+
!(self == other)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# AboveAll represents a boundary above all possible versions.
|
105
|
+
# When used as the upper boundary of an interval, any version
|
106
|
+
# that is greater than the lower boundary is in the interval.
|
107
|
+
class AboveAll < Boundary
|
108
|
+
def initialize; end
|
109
|
+
|
110
|
+
def to_s
|
111
|
+
'+inf'
|
112
|
+
end
|
113
|
+
|
114
|
+
def is_initial_version?
|
115
|
+
false
|
116
|
+
end
|
117
|
+
|
118
|
+
def <(_other)
|
119
|
+
false
|
120
|
+
end
|
121
|
+
|
122
|
+
def >(other)
|
123
|
+
other.instance_of?(AboveAll) ? false : true
|
124
|
+
end
|
125
|
+
|
126
|
+
def <=(other)
|
127
|
+
self < other || self == other
|
128
|
+
end
|
129
|
+
|
130
|
+
def >=(other)
|
131
|
+
self > other || self == other
|
132
|
+
end
|
133
|
+
|
134
|
+
def ==(other)
|
135
|
+
(other.instance_of? AboveAll) ? true : false
|
136
|
+
end
|
137
|
+
|
138
|
+
def !=(other)
|
139
|
+
!(self == other)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
@@ -1,10 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative '../command'
|
4
|
-
require_relative '../../semver_dialects
|
5
|
-
require_relative '../semantic_version/version_translator.rb'
|
6
|
-
require_relative '../semantic_version/version_parser.rb'
|
7
|
-
require_relative '../semantic_version/version_range.rb'
|
4
|
+
require_relative '../../semver_dialects'
|
8
5
|
|
9
6
|
module SemverDialects
|
10
7
|
module Commands
|
@@ -22,7 +19,7 @@ module SemverDialects
|
|
22
19
|
typ = @type.downcase
|
23
20
|
raise SemverDialects::Error, 'wrong package type' unless @avail_types.include?(typ)
|
24
21
|
|
25
|
-
if
|
22
|
+
if SemverDialects.version_satisfies?(typ, @version, @constraint)
|
26
23
|
output.puts "#{@version} matches #{@constraint} for #{@type}"
|
27
24
|
0
|
28
25
|
else
|
@@ -0,0 +1,256 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SemverDialects
|
4
|
+
module IntervalType
|
5
|
+
UNKNOWN = 0
|
6
|
+
LEFT_OPEN = 1
|
7
|
+
LEFT_CLOSED = 2
|
8
|
+
RIGHT_OPEN = 4
|
9
|
+
RIGHT_CLOSED = 8
|
10
|
+
end
|
11
|
+
|
12
|
+
# Interval is an interval that starts with a lower boundary
|
13
|
+
# and ends with an upper boundary. The interval includes the boundaries
|
14
|
+
# or not depending on its type.
|
15
|
+
class Interval
|
16
|
+
# Returns an interval that only includes the given version.
|
17
|
+
def self.from_version(version)
|
18
|
+
boundary = Boundary.new(version)
|
19
|
+
Interval.new(IntervalType::LEFT_CLOSED | IntervalType::RIGHT_CLOSED, boundary, boundary)
|
20
|
+
end
|
21
|
+
|
22
|
+
attr_accessor :type, :start_cut, :end_cut
|
23
|
+
|
24
|
+
def initialize(type, start_cut, end_cut)
|
25
|
+
@type = type
|
26
|
+
@start_cut = start_cut
|
27
|
+
@end_cut = end_cut
|
28
|
+
end
|
29
|
+
|
30
|
+
def intersect(other_interval)
|
31
|
+
return EmptyInterval.new if empty?
|
32
|
+
|
33
|
+
# this look odd -- we have to use it here though, because it may be that placeholders are present inside
|
34
|
+
# the version for which > and < would yield true
|
35
|
+
return EmptyInterval.new if !(@start_cut <= other_interval.end_cut) || !(other_interval.start_cut <= @end_cut)
|
36
|
+
|
37
|
+
start_cut_new = max(@start_cut, other_interval.start_cut)
|
38
|
+
end_cut_new = min(@end_cut, other_interval.end_cut)
|
39
|
+
|
40
|
+
# compute the boundaries for the intersection
|
41
|
+
type = compute_intersection_boundary(self, other_interval, start_cut_new, end_cut_new)
|
42
|
+
interval = Interval.new(type, start_cut_new, end_cut_new)
|
43
|
+
half_open = !(interval.bit_set?(IntervalType::RIGHT_CLOSED) && interval.bit_set?(IntervalType::LEFT_CLOSED))
|
44
|
+
|
45
|
+
interval.singleton? && half_open ? EmptyInterval.new : interval
|
46
|
+
end
|
47
|
+
|
48
|
+
def special(cut)
|
49
|
+
cut.instance_of?(AboveAll) || cut.instance_of?(BelowAll)
|
50
|
+
end
|
51
|
+
|
52
|
+
def to_s
|
53
|
+
s = ''
|
54
|
+
s += bit_set?(IntervalType::LEFT_CLOSED) ? '[' : ''
|
55
|
+
s += bit_set?(IntervalType::LEFT_OPEN) ? '(' : ''
|
56
|
+
s += [@start_cut, @end_cut].join(',')
|
57
|
+
s += bit_set?(IntervalType::RIGHT_CLOSED) ? ']' : ''
|
58
|
+
s += bit_set?(IntervalType::RIGHT_OPEN) ? ')' : ''
|
59
|
+
s
|
60
|
+
end
|
61
|
+
|
62
|
+
# this function returns a human-readable descriptions of the version strings
|
63
|
+
def to_description_s
|
64
|
+
s = ''
|
65
|
+
if distinct?
|
66
|
+
s = "version #{@start_cut}"
|
67
|
+
elsif universal?
|
68
|
+
s = 'all versions '
|
69
|
+
else
|
70
|
+
s = 'all versions '
|
71
|
+
s += if start_cut.instance_of?(BelowAll)
|
72
|
+
''
|
73
|
+
elsif bit_set?(IntervalType::LEFT_OPEN)
|
74
|
+
"after #{@start_cut} "
|
75
|
+
else
|
76
|
+
bit_set?(IntervalType::LEFT_CLOSED) ? "starting from #{@start_cut} " : ''
|
77
|
+
end
|
78
|
+
s += if end_cut.instance_of?(AboveAll)
|
79
|
+
''
|
80
|
+
elsif bit_set?(IntervalType::RIGHT_OPEN)
|
81
|
+
"before #{@end_cut}"
|
82
|
+
else
|
83
|
+
bit_set?(IntervalType::RIGHT_CLOSED) ? "up to #{@end_cut}" : ''
|
84
|
+
end
|
85
|
+
end
|
86
|
+
s.strip
|
87
|
+
end
|
88
|
+
|
89
|
+
def to_nuget_s
|
90
|
+
to_maven_s
|
91
|
+
end
|
92
|
+
|
93
|
+
def to_maven_s
|
94
|
+
s = ''
|
95
|
+
# special case -- distinct version
|
96
|
+
if distinct?
|
97
|
+
s += "[#{@start_cut}]"
|
98
|
+
else
|
99
|
+
s += if start_cut.instance_of?(BelowAll)
|
100
|
+
'(,'
|
101
|
+
elsif bit_set?(IntervalType::LEFT_OPEN)
|
102
|
+
"[#{@start_cut},"
|
103
|
+
else
|
104
|
+
bit_set?(IntervalType::LEFT_CLOSED) ? "[#{@start_cut}," : ''
|
105
|
+
end
|
106
|
+
s += if end_cut.instance_of?(AboveAll)
|
107
|
+
')'
|
108
|
+
elsif bit_set?(IntervalType::RIGHT_OPEN)
|
109
|
+
"#{@end_cut})"
|
110
|
+
else
|
111
|
+
bit_set?(IntervalType::RIGHT_CLOSED) ? "#{@end_cut}]" : ''
|
112
|
+
end
|
113
|
+
end
|
114
|
+
s
|
115
|
+
end
|
116
|
+
|
117
|
+
def distinct?
|
118
|
+
bit_set?(IntervalType::LEFT_CLOSED) && bit_set?(IntervalType::RIGHT_CLOSED) && @start_cut == @end_cut
|
119
|
+
end
|
120
|
+
|
121
|
+
def subsumes?(other)
|
122
|
+
@start_cut <= other.start_cut && @end_cut >= other.end_cut
|
123
|
+
end
|
124
|
+
|
125
|
+
def universal?
|
126
|
+
(bit_set?(IntervalType::LEFT_OPEN) && bit_set?(IntervalType::RIGHT_OPEN) && @start_cut.instance_of?(BelowAll) && @end_cut.instance_of?(AboveAll)) || @start_cut.is_initial_version? && @end_cut.instance_of?(AboveAll)
|
127
|
+
end
|
128
|
+
|
129
|
+
def to_gem_s
|
130
|
+
get_canoncial_s
|
131
|
+
end
|
132
|
+
|
133
|
+
def to_ruby_s
|
134
|
+
get_canoncial_s
|
135
|
+
end
|
136
|
+
|
137
|
+
def to_npm_s
|
138
|
+
get_canoncial_s
|
139
|
+
end
|
140
|
+
|
141
|
+
def to_conan_s
|
142
|
+
get_canoncial_s
|
143
|
+
end
|
144
|
+
|
145
|
+
def to_go_s
|
146
|
+
get_canoncial_s
|
147
|
+
end
|
148
|
+
|
149
|
+
def to_pypi_s
|
150
|
+
get_canoncial_s(',', '==')
|
151
|
+
end
|
152
|
+
|
153
|
+
def to_packagist_s
|
154
|
+
get_canoncial_s(',')
|
155
|
+
end
|
156
|
+
|
157
|
+
def empty?
|
158
|
+
instance_of?(EmptyInterval)
|
159
|
+
end
|
160
|
+
|
161
|
+
def singleton?
|
162
|
+
@start_cut == @end_cut && @start_cut.semver == @end_cut.semver
|
163
|
+
end
|
164
|
+
|
165
|
+
def ==(other)
|
166
|
+
@start_cut == other.start_cut && @end_cut == other.end_cut && @type == other.type
|
167
|
+
end
|
168
|
+
|
169
|
+
def bit_set?(interval_type)
|
170
|
+
@type & interval_type != 0
|
171
|
+
end
|
172
|
+
|
173
|
+
protected
|
174
|
+
|
175
|
+
def compute_intersection_boundary(interval_a, interval_b, start_cut_new, end_cut_new)
|
176
|
+
compute_boundary(interval_a, interval_b, start_cut_new, end_cut_new, IntervalType::LEFT_OPEN,
|
177
|
+
IntervalType::RIGHT_OPEN)
|
178
|
+
end
|
179
|
+
|
180
|
+
def compute_boundary(interval_a, interval_b, start_cut_new, end_cut_new, left_check, right_check)
|
181
|
+
start_cut_a = interval_a.start_cut
|
182
|
+
end_cut_a = interval_a.end_cut
|
183
|
+
type_a = interval_a.type
|
184
|
+
|
185
|
+
start_cut_b = interval_b.start_cut
|
186
|
+
end_cut_b = interval_b.end_cut
|
187
|
+
type_b = interval_b.type
|
188
|
+
|
189
|
+
left_fill = left_check == IntervalType::LEFT_OPEN ? IntervalType::LEFT_CLOSED : IntervalType::LEFT_OPEN
|
190
|
+
right_fill = right_check == IntervalType::RIGHT_OPEN ? IntervalType::RIGHT_CLOSED : IntervalType::RIGHT_OPEN
|
191
|
+
|
192
|
+
# compute the boundaries for the union
|
193
|
+
if start_cut_b == start_cut_a
|
194
|
+
one_left_closed = left_type(type_a) == left_check || left_type(type_b) == left_check
|
195
|
+
left_type = one_left_closed ? left_check : left_fill
|
196
|
+
else
|
197
|
+
left_type = start_cut_new == start_cut_a ? left_type(type_a) : left_type(type_b)
|
198
|
+
end
|
199
|
+
|
200
|
+
if end_cut_b == end_cut_a
|
201
|
+
one_right_closed = right_type(type_a) == right_check || right_type(type_b) == right_check
|
202
|
+
right_type = one_right_closed ? right_check : right_fill
|
203
|
+
else
|
204
|
+
right_type = end_cut_new == end_cut_a ? right_type(type_a) : right_type(type_b)
|
205
|
+
end
|
206
|
+
|
207
|
+
left_type | right_type
|
208
|
+
end
|
209
|
+
|
210
|
+
def get_canoncial_s(delimiter = ' ', eq = '=')
|
211
|
+
if distinct?
|
212
|
+
"#{eq}#{@start_cut}"
|
213
|
+
else
|
214
|
+
first = if start_cut.instance_of?(BelowAll)
|
215
|
+
''
|
216
|
+
elsif bit_set?(IntervalType::LEFT_OPEN)
|
217
|
+
">#{@start_cut}"
|
218
|
+
else
|
219
|
+
bit_set?(IntervalType::LEFT_CLOSED) ? ">=#{@start_cut}" : ''
|
220
|
+
end
|
221
|
+
second = if end_cut.instance_of?(AboveAll)
|
222
|
+
''
|
223
|
+
elsif bit_set?(IntervalType::RIGHT_OPEN)
|
224
|
+
"<#{@end_cut}"
|
225
|
+
else
|
226
|
+
bit_set?(IntervalType::RIGHT_CLOSED) ? "<=#{@end_cut}" : ''
|
227
|
+
end
|
228
|
+
!first.empty? && !second.empty? ? "#{first}#{delimiter}#{second}" : first + second
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
def max(cut_a, cut_b)
|
233
|
+
cut_a > cut_b ? cut_a : cut_b
|
234
|
+
end
|
235
|
+
|
236
|
+
def min(cut_a, cut_b)
|
237
|
+
cut_a < cut_b ? cut_a : cut_b
|
238
|
+
end
|
239
|
+
|
240
|
+
def right_type(type)
|
241
|
+
(IntervalType::RIGHT_OPEN | IntervalType::RIGHT_CLOSED) & type
|
242
|
+
end
|
243
|
+
|
244
|
+
def left_type(type)
|
245
|
+
(IntervalType::LEFT_OPEN | IntervalType::LEFT_CLOSED) & type
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
class EmptyInterval < Interval
|
250
|
+
def initialize; end
|
251
|
+
|
252
|
+
def to_s
|
253
|
+
'empty'
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# IntervalParser parses a simple constraint expressed in the npm syntax
|
4
|
+
# (or equivalent) and returns a Interval that has an upper boundary
|
5
|
+
# or a lower boundary.
|
6
|
+
#
|
7
|
+
# The constraint is a string that can either be:
|
8
|
+
# - an operator (>, <, >=, <=, =) followed by a version
|
9
|
+
# - a version; the interval starts and ends with that version
|
10
|
+
# - "=*"; the interval has no boundaries and includes any version
|
11
|
+
#
|
12
|
+
# Technically IntervalParser returns a Interval such as
|
13
|
+
# start_cut is BelowAll or end_cut is AboveAll.
|
14
|
+
# The type of the Interval matches the operator
|
15
|
+
# that's been detected.
|
16
|
+
#
|
17
|
+
module SemverDialects
|
18
|
+
module IntervalParser
|
19
|
+
# A constraint is made of an operator followed by a version string.
|
20
|
+
# Use the regular expression of the SemanticVersion because this is the most generic one.
|
21
|
+
CONSTRAINT_REGEXP = Regexp.new("(?<op>[><=]+) *(?<version>#{SemanticVersion::VERSION_PATTERN})").freeze
|
22
|
+
|
23
|
+
def self.parse(typ, versionstring)
|
24
|
+
if versionstring == '=*'
|
25
|
+
# special case = All Versions
|
26
|
+
return Interval.new(IntervalType::LEFT_OPEN | IntervalType::RIGHT_OPEN, BelowAll.new, AboveAll.new)
|
27
|
+
end
|
28
|
+
|
29
|
+
version_items = versionstring.split(' ')
|
30
|
+
interval = Interval.new(IntervalType::LEFT_OPEN | IntervalType::RIGHT_OPEN, BelowAll.new, AboveAll.new)
|
31
|
+
version_items.each do |version_item|
|
32
|
+
matches = version_item.match CONSTRAINT_REGEXP
|
33
|
+
raise InvalidConstraintError, versionstring if matches.nil?
|
34
|
+
|
35
|
+
version = SemverDialects.parse_version(typ, matches[:version])
|
36
|
+
boundary = Boundary.new(version)
|
37
|
+
case matches[:op]
|
38
|
+
when '>='
|
39
|
+
new_interval = Interval.new(IntervalType::LEFT_CLOSED | IntervalType::RIGHT_OPEN, boundary, AboveAll.new)
|
40
|
+
interval = interval.intersect(new_interval)
|
41
|
+
when '<='
|
42
|
+
new_interval = Interval.new(IntervalType::LEFT_OPEN | IntervalType::RIGHT_CLOSED, BelowAll.new, boundary)
|
43
|
+
interval = interval.intersect(new_interval)
|
44
|
+
when '<'
|
45
|
+
new_interval = Interval.new(IntervalType::LEFT_OPEN | IntervalType::RIGHT_OPEN, BelowAll.new, boundary)
|
46
|
+
interval = interval.intersect(new_interval)
|
47
|
+
when '>'
|
48
|
+
new_interval = Interval.new(IntervalType::LEFT_OPEN | IntervalType::RIGHT_OPEN, boundary, AboveAll.new)
|
49
|
+
interval = interval.intersect(new_interval)
|
50
|
+
when '=', '=='
|
51
|
+
new_interval = Interval.new(IntervalType::LEFT_CLOSED | IntervalType::RIGHT_CLOSED, boundary, boundary)
|
52
|
+
interval = interval.intersect(new_interval)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
interval
|
56
|
+
rescue InvalidVersionError
|
57
|
+
raise InvalidConstraintError, versionstring
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# IntervalSet is a disjunction of version intervals.
|
4
|
+
# It can express a range like "[1.0,2.0],[3.0,4.0]" (Maven syntax),
|
5
|
+
# that is between 1.0 and 2.0 (included) OR between 3.0 and 4.0 (included).
|
6
|
+
module SemverDialects
|
7
|
+
class IntervalSet
|
8
|
+
attr_reader :intervals
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@intervals = []
|
12
|
+
@interval_set = Set.new
|
13
|
+
end
|
14
|
+
|
15
|
+
def add(interval)
|
16
|
+
@intervals << interval
|
17
|
+
@interval_set.add(interval)
|
18
|
+
end
|
19
|
+
|
20
|
+
def <<(item)
|
21
|
+
add(item)
|
22
|
+
end
|
23
|
+
|
24
|
+
def size
|
25
|
+
@intervals.size
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_s
|
29
|
+
@intervals.map(&:to_s).join(',')
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_description_s
|
33
|
+
@intervals.map(&:to_description_s).join(', ').capitalize
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_npm_s
|
37
|
+
@intervals.map(&:to_npm_s).join('||')
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_conan_s
|
41
|
+
to_npm_s
|
42
|
+
end
|
43
|
+
|
44
|
+
def to_nuget_s
|
45
|
+
to_maven_s
|
46
|
+
end
|
47
|
+
|
48
|
+
def to_maven_s
|
49
|
+
@intervals.map(&:to_maven_s).join(',')
|
50
|
+
end
|
51
|
+
|
52
|
+
def to_gem_s
|
53
|
+
@intervals.map(&:to_gem_s).join('||')
|
54
|
+
end
|
55
|
+
|
56
|
+
def to_pypi_s
|
57
|
+
@intervals.map(&:to_pypi_s).join('||')
|
58
|
+
end
|
59
|
+
|
60
|
+
def to_go_s
|
61
|
+
@intervals.map(&:to_go_s).join('||')
|
62
|
+
end
|
63
|
+
|
64
|
+
def to_packagist_s
|
65
|
+
@intervals.map(&:to_packagist_s).join('||')
|
66
|
+
end
|
67
|
+
|
68
|
+
def to_version_s(package_type)
|
69
|
+
case package_type
|
70
|
+
when 'npm'
|
71
|
+
to_npm_s
|
72
|
+
when 'nuget'
|
73
|
+
to_nuget_s
|
74
|
+
when 'maven'
|
75
|
+
to_maven_s
|
76
|
+
when 'gem'
|
77
|
+
to_gem_s
|
78
|
+
when 'pypi'
|
79
|
+
to_pypi_s
|
80
|
+
when 'packagist'
|
81
|
+
to_packagist_s
|
82
|
+
when 'go'
|
83
|
+
to_go_s
|
84
|
+
when 'conan'
|
85
|
+
to_conan_s
|
86
|
+
else
|
87
|
+
''
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def includes?(other)
|
92
|
+
@interval_set.include?(other)
|
93
|
+
end
|
94
|
+
|
95
|
+
def overlaps_with?(other)
|
96
|
+
@interval_set.each do |interval|
|
97
|
+
return true unless interval.intersect(other).instance_of?(EmptyInterval)
|
98
|
+
end
|
99
|
+
false
|
100
|
+
end
|
101
|
+
|
102
|
+
def first
|
103
|
+
@intervals.first
|
104
|
+
end
|
105
|
+
|
106
|
+
def empty?
|
107
|
+
@intervals.empty?
|
108
|
+
end
|
109
|
+
|
110
|
+
def any?
|
111
|
+
@intervals.any?
|
112
|
+
end
|
113
|
+
|
114
|
+
def universal?
|
115
|
+
@intervals.each do |interval|
|
116
|
+
return true if interval.universal?
|
117
|
+
end
|
118
|
+
false
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|