semver_dialects 2.0.2 → 3.0.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/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
|