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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d8afd964094235e63624c76231df69d2723cab03721b06c35aac704b2c1284bc
4
- data.tar.gz: a51b11c2b5121fb34dda52d94824ca9713e634691d0aa212cd1c29197b40a8e4
3
+ metadata.gz: 19c2562420fb945c665db99cfeb6ac46186f65b10b4a84ba073aa8e0b443666e
4
+ data.tar.gz: 73d80ecdaedfc906f4e24f1c9f5921f18e126c19d73722991de7f3d61d5b2f24
5
5
  SHA512:
6
- metadata.gz: ce87db5b620f138cd1058c5fb134bc33f7ea136d5dc5f5ba45626e97071d0eba5727e35ecf07cb509d23e08fbbe0838970e0480ba86d17843f416b3b409a6ea7
7
- data.tar.gz: 2194ee64c814f01bdfd51418e3cae3acc451277fab265f5212049dc304ea378bacc6fef76d6786ad9495d23f6a0e2f1fe0eeb809496eb39a0c70725929fad6f3
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.rb'
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 VersionChecker.version_sat?(typ, @version, @constraint)
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