semantic_puppet 0.1.3 → 1.0.3
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 +5 -5
- data/.gitignore +1 -0
- data/.rubocop.yml +672 -0
- data/.travis.yml +28 -0
- data/CHANGELOG.md +21 -0
- data/CODEOWNERS +1 -0
- data/README.md +17 -15
- data/appveyor.yml +19 -0
- data/lib/semantic_puppet.rb +0 -2
- data/lib/semantic_puppet/dependency.rb +18 -1
- data/lib/semantic_puppet/dependency/unsatisfiable_graph.rb +9 -3
- data/lib/semantic_puppet/gem_version.rb +3 -0
- data/lib/semantic_puppet/version.rb +122 -114
- data/lib/semantic_puppet/version_range.rb +654 -343
- data/semantic_puppet.gemspec +10 -6
- data/spec/spec_helper.rb +0 -1
- data/spec/unit/semantic_puppet/dependency/unsatisfiable_graph_spec.rb +1 -1
- data/spec/unit/semantic_puppet/dependency_spec.rb +25 -25
- data/spec/unit/semantic_puppet/version_range_spec.rb +468 -79
- data/spec/unit/semantic_puppet/version_spec.rb +218 -80
- metadata +9 -6
@@ -1,422 +1,733 @@
|
|
1
1
|
require 'semantic_puppet'
|
2
2
|
|
3
3
|
module SemanticPuppet
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
4
|
+
# A Semantic Version Range.
|
5
|
+
#
|
6
|
+
# @see https://github.com/npm/node-semver for full specification
|
7
|
+
# @api public
|
8
|
+
class VersionRange
|
9
|
+
UPPER_X = 'X'.freeze
|
10
|
+
LOWER_X = 'x'.freeze
|
11
|
+
STAR = '*'.freeze
|
12
|
+
|
13
|
+
NR = '0|[1-9][0-9]*'.freeze
|
14
|
+
XR = '(x|X|\*|' + NR + ')'.freeze
|
15
|
+
XR_NC = '(?:x|X|\*|' + NR + ')'.freeze
|
16
|
+
|
17
|
+
PART = '(?:[0-9A-Za-z-]+)'.freeze
|
18
|
+
PARTS = PART + '(?:\.' + PART + ')*'.freeze
|
19
|
+
QUALIFIER = '(?:-(' + PARTS + '))?(?:\+(' + PARTS + '))?'.freeze
|
20
|
+
QUALIFIER_NC = '(?:-' + PARTS + ')?(?:\+' + PARTS + ')?'.freeze
|
21
|
+
|
22
|
+
PARTIAL = XR_NC + '(?:\.' + XR_NC + '(?:\.' + XR_NC + QUALIFIER_NC + ')?)?'.freeze
|
23
|
+
|
24
|
+
# The ~> isn't in the spec but allowed
|
25
|
+
SIMPLE = '([<>=~^]|<=|>=|~>|~=)?(' + PARTIAL + ')'.freeze
|
26
|
+
SIMPLE_EXPR = /\A#{SIMPLE}\z/.freeze
|
27
|
+
|
28
|
+
SIMPLE_WITH_EXTRA_WS = '([<>=~^]|<=|>=)?\s+(' + PARTIAL + ')'.freeze
|
29
|
+
SIMPLE_WITH_EXTRA_WS_EXPR = /\A#{SIMPLE_WITH_EXTRA_WS}\z/.freeze
|
30
|
+
|
31
|
+
HYPHEN = '(' + PARTIAL + ')\s+-\s+(' + PARTIAL + ')'.freeze
|
32
|
+
HYPHEN_EXPR = /\A#{HYPHEN}\z/.freeze
|
33
|
+
|
34
|
+
PARTIAL_EXPR = /\A#{XR}(?:\.#{XR}(?:\.#{XR}#{QUALIFIER})?)?\z/.freeze
|
35
|
+
|
36
|
+
LOGICAL_OR = /\s*\|\|\s*/.freeze
|
37
|
+
RANGE_SPLIT = /\s+/.freeze
|
38
|
+
|
39
|
+
# Parses a version range string into a comparable {VersionRange} instance.
|
40
|
+
#
|
41
|
+
# Currently parsed version range string may take any of the following:
|
42
|
+
# forms:
|
43
|
+
#
|
44
|
+
# * Regular Semantic Version strings
|
45
|
+
# * ex. `"1.0.0"`, `"1.2.3-pre"`
|
46
|
+
# * Partial Semantic Version strings
|
47
|
+
# * ex. `"1.0.x"`, `"1"`, `"2.X"`, `"3.*"`,
|
48
|
+
# * Inequalities
|
49
|
+
# * ex. `"> 1.0.0"`, `"<3.2.0"`, `">=4.0.0"`
|
50
|
+
# * Approximate Caret Versions
|
51
|
+
# * ex. `"^1"`, `"^3.2"`, `"^4.1.0"`
|
52
|
+
# * Approximate Tilde Versions
|
53
|
+
# * ex. `"~1.0.0"`, `"~ 3.2.0"`, `"~4.0.0"`
|
54
|
+
# * Inclusive Ranges
|
55
|
+
# * ex. `"1.0.0 - 1.3.9"`
|
56
|
+
# * Range Intersections
|
57
|
+
# * ex. `">1.0.0 <=2.3.0"`
|
58
|
+
# * Combined ranges
|
59
|
+
# * ex, `">=1.0.0 <2.3.0 || >=2.5.0 <3.0.0"`
|
60
|
+
#
|
61
|
+
# @param range_string [String] the version range string to parse
|
62
|
+
# @return [VersionRange] a new {VersionRange} instance
|
63
|
+
# @api public
|
64
|
+
def self.parse(range_string)
|
65
|
+
# Remove extra whitespace after operators. Such whitespace should not cause a split
|
66
|
+
range_set = range_string.gsub(/([><=~^])(?:\s+|\s*v)/, '\1')
|
67
|
+
ranges = range_set.split(LOGICAL_OR)
|
68
|
+
return ALL_RANGE if ranges.empty?
|
69
|
+
|
70
|
+
new(ranges.map do |range|
|
71
|
+
if range =~ HYPHEN_EXPR
|
72
|
+
MinMaxRange.create(GtEqRange.new(parse_version($1)), LtEqRange.new(parse_version($2)))
|
44
73
|
else
|
45
|
-
|
74
|
+
# Split on whitespace
|
75
|
+
simples = range.split(RANGE_SPLIT).map do |simple|
|
76
|
+
match_data = SIMPLE_EXPR.match(simple)
|
77
|
+
raise ArgumentError, "Unparsable version range: \"#{range_string}\"" unless match_data
|
78
|
+
operand = match_data[2]
|
79
|
+
|
80
|
+
# Case based on operator
|
81
|
+
case match_data[1]
|
82
|
+
when '~', '~>', '~='
|
83
|
+
parse_tilde(operand)
|
84
|
+
when '^'
|
85
|
+
parse_caret(operand)
|
86
|
+
when '>'
|
87
|
+
parse_gt_version(operand)
|
88
|
+
when '>='
|
89
|
+
GtEqRange.new(parse_version(operand))
|
90
|
+
when '<'
|
91
|
+
LtRange.new(parse_version(operand))
|
92
|
+
when '<='
|
93
|
+
parse_lteq_version(operand)
|
94
|
+
when '='
|
95
|
+
parse_xrange(operand)
|
96
|
+
else
|
97
|
+
parse_xrange(operand)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
simples.size == 1 ? simples[0] : MinMaxRange.create(*simples)
|
46
101
|
end
|
102
|
+
end.uniq, range_string).freeze
|
103
|
+
end
|
47
104
|
|
48
|
-
|
49
|
-
|
50
|
-
|
105
|
+
def self.parse_partial(expr)
|
106
|
+
match_data = PARTIAL_EXPR.match(expr)
|
107
|
+
raise ArgumentError, "Unparsable version range: \"#{expr}\"" unless match_data
|
108
|
+
match_data
|
109
|
+
end
|
110
|
+
private_class_method :parse_partial
|
51
111
|
|
52
|
-
|
112
|
+
def self.parse_caret(expr)
|
113
|
+
match_data = parse_partial(expr)
|
114
|
+
major = digit(match_data[1])
|
115
|
+
major == 0 ? allow_patch_updates(major, match_data) : allow_minor_updates(major, match_data)
|
116
|
+
end
|
117
|
+
private_class_method :parse_caret
|
53
118
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
expr.split(/[ ]+/).map { |x| parse(x) }.inject { |a,b| a & b }
|
60
|
-
end
|
119
|
+
def self.parse_tilde(expr)
|
120
|
+
match_data = parse_partial(expr)
|
121
|
+
allow_patch_updates(digit(match_data[1]), match_data)
|
122
|
+
end
|
123
|
+
private_class_method :parse_tilde
|
61
124
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
# @param expr [String] a "loose" version expression
|
68
|
-
# @return [VersionRange] a version range representing `expr`
|
69
|
-
def parse_loose_version_expression(expr)
|
70
|
-
start, finish = process_loose_expr(expr)
|
125
|
+
def self.parse_xrange(expr)
|
126
|
+
match_data = parse_partial(expr)
|
127
|
+
allow_patch_updates(digit(match_data[1]), match_data, false)
|
128
|
+
end
|
129
|
+
private_class_method :parse_xrange
|
71
130
|
|
72
|
-
|
73
|
-
|
74
|
-
end
|
131
|
+
def self.allow_patch_updates(major, match_data, tilde_or_caret = true)
|
132
|
+
return AllRange::SINGLETON unless major
|
75
133
|
|
76
|
-
|
77
|
-
|
78
|
-
finish = finish.send(:first_prerelease)
|
79
|
-
end
|
134
|
+
minor = digit(match_data[2])
|
135
|
+
return MinMaxRange.new(GtEqRange.new(Version.new(major, 0, 0)), LtRange.new(Version.new(major + 1, 0, 0))) unless minor
|
80
136
|
|
81
|
-
|
82
|
-
|
137
|
+
patch = digit(match_data[3])
|
138
|
+
return MinMaxRange.new(GtEqRange.new(Version.new(major, minor, 0)), LtRange.new(Version.new(major, minor + 1, 0))) unless patch
|
83
139
|
|
84
|
-
|
85
|
-
|
86
|
-
# @overload parse_inequality_expression('<', expr)
|
87
|
-
# {include:.parse_lt_expression}
|
88
|
-
#
|
89
|
-
# @overload parse_inequality_expression('<=', expr)
|
90
|
-
# {include:.parse_lte_expression}
|
91
|
-
#
|
92
|
-
# @overload parse_inequality_expression('>', expr)
|
93
|
-
# {include:.parse_gt_expression}
|
94
|
-
#
|
95
|
-
# @overload parse_inequality_expression('>=', expr)
|
96
|
-
# {include:.parse_gte_expression}
|
97
|
-
#
|
98
|
-
# @param comp ['<', '<=', '>', '>='] an inequality operator
|
99
|
-
# @param expr [String] a "loose" version expression
|
100
|
-
# @return [VersionRange] a range covering all versions in the inequality
|
101
|
-
def parse_inequality_expression(comp, expr)
|
102
|
-
case comp
|
103
|
-
when '>'
|
104
|
-
parse_gt_expression(expr)
|
105
|
-
when '>='
|
106
|
-
parse_gte_expression(expr)
|
107
|
-
when '<'
|
108
|
-
parse_lt_expression(expr)
|
109
|
-
when '<='
|
110
|
-
parse_lte_expression(expr)
|
111
|
-
end
|
112
|
-
end
|
140
|
+
version = Version.new(major, minor, patch, Version.parse_prerelease(match_data[4]), Version.parse_build(match_data[5]))
|
141
|
+
return EqRange.new(version) unless tilde_or_caret
|
113
142
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
143
|
+
MinMaxRange.new(GtEqRange.new(version), LtRange.new(Version.new(major, minor + 1, 0)))
|
144
|
+
end
|
145
|
+
private_class_method :allow_patch_updates
|
146
|
+
|
147
|
+
def self.allow_minor_updates(major, match_data)
|
148
|
+
return AllRange.SINGLETON unless major
|
149
|
+
minor = digit(match_data[2])
|
150
|
+
if minor
|
151
|
+
patch = digit(match_data[3])
|
152
|
+
if patch.nil?
|
153
|
+
MinMaxRange.new(GtEqRange.new(Version.new(major, minor, 0)), LtRange.new(Version.new(major + 1, 0, 0)))
|
122
154
|
else
|
123
|
-
|
155
|
+
if match_data[4].nil?
|
156
|
+
MinMaxRange.new(GtEqRange.new(Version.new(major, minor, patch)), LtRange.new(Version.new(major + 1, 0, 0)))
|
157
|
+
else
|
158
|
+
MinMaxRange.new(
|
159
|
+
GtEqRange.new(
|
160
|
+
Version.new(major, minor, patch, Version.parse_prerelease(match_data[4]), Version.parse_build(match_data[5]))),
|
161
|
+
LtRange.new(Version.new(major + 1, 0, 0)))
|
162
|
+
end
|
124
163
|
end
|
125
|
-
|
126
|
-
|
164
|
+
else
|
165
|
+
MinMaxRange.new(GtEqRange.new(Version.new(major, 0, 0)), LtRange.new(Version.new(major + 1, 0, 0)))
|
127
166
|
end
|
167
|
+
end
|
168
|
+
private_class_method :allow_minor_updates
|
128
169
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
170
|
+
def self.digit(str)
|
171
|
+
(str.nil? || UPPER_X == str || LOWER_X == str || STAR == str) ? nil : str.to_i
|
172
|
+
end
|
173
|
+
private_class_method :digit
|
174
|
+
|
175
|
+
def self.parse_version(expr)
|
176
|
+
match_data = parse_partial(expr)
|
177
|
+
major = digit(match_data[1]) || 0
|
178
|
+
minor = digit(match_data[2]) || 0
|
179
|
+
patch = digit(match_data[3]) || 0
|
180
|
+
Version.new(major, minor, patch, Version.parse_prerelease(match_data[4]), Version.parse_build(match_data[5]))
|
181
|
+
end
|
182
|
+
private_class_method :parse_version
|
183
|
+
|
184
|
+
def self.parse_gt_version(expr)
|
185
|
+
match_data = parse_partial(expr)
|
186
|
+
major = digit(match_data[1])
|
187
|
+
return LtRange::MATCH_NOTHING unless major
|
188
|
+
minor = digit(match_data[2])
|
189
|
+
return GtEqRange.new(Version.new(major + 1, 0, 0)) unless minor
|
190
|
+
patch = digit(match_data[3])
|
191
|
+
return GtEqRange.new(Version.new(major, minor + 1, 0)) unless patch
|
192
|
+
return GtRange.new(Version.new(major, minor, patch, Version.parse_prerelease(match_data[4]), Version.parse_build(match_data[5])))
|
193
|
+
end
|
194
|
+
private_class_method :parse_gt_version
|
195
|
+
|
196
|
+
def self.parse_lteq_version(expr)
|
197
|
+
match_data = parse_partial(expr)
|
198
|
+
major = digit(match_data[1])
|
199
|
+
return AllRange.SINGLETON unless major
|
200
|
+
minor = digit(match_data[2])
|
201
|
+
return LtRange.new(Version.new(major + 1, 0, 0)) unless minor
|
202
|
+
patch = digit(match_data[3])
|
203
|
+
return LtRange.new(Version.new(major, minor + 1, 0)) unless patch
|
204
|
+
return LtEqRange.new(Version.new(major, minor, patch, Version.parse_prerelease(match_data[4]), Version.parse_build(match_data[5])))
|
205
|
+
end
|
206
|
+
private_class_method :parse_lteq_version
|
207
|
+
|
208
|
+
# Provides read access to the ranges. For internal use only
|
209
|
+
# @api private
|
210
|
+
attr_reader :ranges
|
211
|
+
|
212
|
+
# Creates a new version range
|
213
|
+
# @overload initialize(from, to, exclude_end = false)
|
214
|
+
# Creates a new instance using ruby `Range` semantics
|
215
|
+
# @param begin [String,Version] the version denoting the start of the range (always inclusive)
|
216
|
+
# @param end [String,Version] the version denoting the end of the range
|
217
|
+
# @param exclude_end [Boolean] `true` if the `end` version should be excluded from the range
|
218
|
+
# @overload initialize(ranges, string)
|
219
|
+
# Creates a new instance based on parsed content. For internal use only
|
220
|
+
# @param ranges [Array<AbstractRange>] the ranges to include in this range
|
221
|
+
# @param string [String] the original string representation that was parsed to produce the ranges
|
222
|
+
#
|
223
|
+
# @api private
|
224
|
+
def initialize(ranges, string, exclude_end = false)
|
225
|
+
unless ranges.is_a?(Array)
|
226
|
+
lb = GtEqRange.new(ranges)
|
227
|
+
if exclude_end
|
228
|
+
ub = LtRange.new(string)
|
229
|
+
string = ">=#{string} <#{ranges}"
|
138
230
|
else
|
139
|
-
|
231
|
+
ub = LtEqRange.new(string)
|
232
|
+
string = "#{string} - #{ranges}"
|
140
233
|
end
|
234
|
+
ranges = [MinMaxRange.create(lb, ub)]
|
235
|
+
end
|
236
|
+
ranges.compact!
|
237
|
+
|
238
|
+
merge_happened = true
|
239
|
+
while ranges.size > 1 && merge_happened
|
240
|
+
# merge ranges if possible
|
241
|
+
merge_happened = false
|
242
|
+
result = []
|
243
|
+
until ranges.empty?
|
244
|
+
unmerged = []
|
245
|
+
x = ranges.pop
|
246
|
+
result << ranges.reduce(x) do |memo, y|
|
247
|
+
merged = memo.merge(y)
|
248
|
+
if merged.nil?
|
249
|
+
unmerged << y
|
250
|
+
else
|
251
|
+
merge_happened = true
|
252
|
+
memo = merged
|
253
|
+
end
|
254
|
+
memo
|
255
|
+
end
|
256
|
+
ranges = unmerged
|
257
|
+
end
|
258
|
+
ranges = result.reverse!
|
259
|
+
end
|
260
|
+
|
261
|
+
ranges = [LtRange::MATCH_NOTHING] if ranges.empty?
|
262
|
+
@ranges = ranges
|
263
|
+
@string = string.nil? ? ranges.join(' || ') : string
|
264
|
+
end
|
265
|
+
|
266
|
+
def eql?(range)
|
267
|
+
range.is_a?(VersionRange) && @ranges.eql?(range.ranges)
|
268
|
+
end
|
269
|
+
alias == eql?
|
270
|
+
|
271
|
+
def hash
|
272
|
+
@ranges.hash
|
273
|
+
end
|
274
|
+
|
275
|
+
# Returns the version that denotes the beginning of this range.
|
276
|
+
#
|
277
|
+
# Since this really is an OR between disparate ranges, it may have multiple beginnings. This method
|
278
|
+
# returns `nil` if that is the case.
|
279
|
+
#
|
280
|
+
# @return [Version] the beginning of the range, or `nil` if there are multiple beginnings
|
281
|
+
# @api public
|
282
|
+
def begin
|
283
|
+
@ranges.size == 1 ? @ranges[0].begin : nil
|
284
|
+
end
|
141
285
|
|
142
|
-
|
286
|
+
# Returns the version that denotes the end of this range.
|
287
|
+
#
|
288
|
+
# Since this really is an OR between disparate ranges, it may have multiple ends. This method
|
289
|
+
# returns `nil` if that is the case.
|
290
|
+
#
|
291
|
+
# @return [Version] the end of the range, or `nil` if there are multiple ends
|
292
|
+
# @api public
|
293
|
+
def end
|
294
|
+
@ranges.size == 1 ? @ranges[0].end : nil
|
295
|
+
end
|
296
|
+
|
297
|
+
# Returns `true` if the beginning is excluded from the range.
|
298
|
+
#
|
299
|
+
# Since this really is an OR between disparate ranges, it may have multiple beginnings. This method
|
300
|
+
# returns `nil` if that is the case.
|
301
|
+
#
|
302
|
+
# @return [Boolean] `true` if the beginning is excluded from the range, `false` if included, or `nil` if there are multiple beginnings
|
303
|
+
# @api public
|
304
|
+
def exclude_begin?
|
305
|
+
@ranges.size == 1 ? @ranges[0].exclude_begin? : nil
|
306
|
+
end
|
307
|
+
|
308
|
+
# Returns `true` if the end is excluded from the range.
|
309
|
+
#
|
310
|
+
# Since this really is an OR between disparate ranges, it may have multiple ends. This method
|
311
|
+
# returns `nil` if that is the case.
|
312
|
+
#
|
313
|
+
# @return [Boolean] `true` if the end is excluded from the range, `false` if not, or `nil` if there are multiple ends
|
314
|
+
# @api public
|
315
|
+
def exclude_end?
|
316
|
+
@ranges.size == 1 ? @ranges[0].exclude_end? : nil
|
317
|
+
end
|
318
|
+
|
319
|
+
# @return [Boolean] `true` if the given version is included in the range
|
320
|
+
# @api public
|
321
|
+
def include?(version)
|
322
|
+
@ranges.any? { |range| range.include?(version) && (version.stable? || range.test_prerelease?(version)) }
|
323
|
+
end
|
324
|
+
alias member? include?
|
325
|
+
alias cover? include?
|
326
|
+
alias === include?
|
327
|
+
|
328
|
+
# Computes the intersection of a pair of ranges. If the ranges have no
|
329
|
+
# useful intersection, an empty range is returned.
|
330
|
+
#
|
331
|
+
# @param other [VersionRange] the range to intersect with
|
332
|
+
# @return [VersionRange] the common subset
|
333
|
+
# @api public
|
334
|
+
def intersection(other)
|
335
|
+
raise ArgumentError, "value must be a #{self.class.name}" unless other.is_a?(VersionRange)
|
336
|
+
result = @ranges.map { |range| other.ranges.map { |o_range| range.intersection(o_range) } }.flatten
|
337
|
+
result.compact!
|
338
|
+
result.uniq!
|
339
|
+
result.empty? ? EMPTY_RANGE : VersionRange.new(result, nil)
|
340
|
+
end
|
341
|
+
alias :& :intersection
|
342
|
+
|
343
|
+
# Returns a string representation of this range. This will be the string that was used
|
344
|
+
# when the range was parsed.
|
345
|
+
#
|
346
|
+
# @return [String] a range expression representing this VersionRange
|
347
|
+
# @api public
|
348
|
+
def to_s
|
349
|
+
@string
|
350
|
+
end
|
351
|
+
|
352
|
+
# Returns a canonical string representation of this range, assembled from the internal
|
353
|
+
# matchers.
|
354
|
+
#
|
355
|
+
# @return [String] a range expression representing this VersionRange
|
356
|
+
# @api public
|
357
|
+
def inspect
|
358
|
+
@ranges.join(' || ')
|
359
|
+
end
|
360
|
+
|
361
|
+
# @api private
|
362
|
+
class AbstractRange
|
363
|
+
def include?(_)
|
364
|
+
true
|
143
365
|
end
|
144
366
|
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
# @return [VersionRange] a range covering all versions less than the
|
149
|
-
# given `expr`
|
150
|
-
def parse_lt_expression(expr)
|
151
|
-
if expr =~ /^[^+]*-/
|
152
|
-
finish = Version.parse(expr)
|
153
|
-
else
|
154
|
-
finish = process_loose_expr(expr).first.send(:first_prerelease)
|
155
|
-
end
|
367
|
+
def begin
|
368
|
+
Version::MIN
|
369
|
+
end
|
156
370
|
|
157
|
-
|
371
|
+
def end
|
372
|
+
Version::MAX
|
158
373
|
end
|
159
374
|
|
160
|
-
|
161
|
-
|
162
|
-
#
|
163
|
-
# @param expr [String] the version to be less than or equal to
|
164
|
-
# @return [VersionRange] a range covering all versions less than or equal
|
165
|
-
# to the given `expr`
|
166
|
-
def parse_lte_expression(expr)
|
167
|
-
if expr =~ /^[^+]*-/
|
168
|
-
finish = Version.parse(expr)
|
169
|
-
self.new(SemanticPuppet::Version::MIN, finish)
|
170
|
-
else
|
171
|
-
finish = process_loose_expr(expr).last.send(:first_prerelease)
|
172
|
-
self.new(SemanticPuppet::Version::MIN, finish, true)
|
173
|
-
end
|
375
|
+
def exclude_begin?
|
376
|
+
false
|
174
377
|
end
|
175
378
|
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
#
|
180
|
-
# ~[Version]
|
181
|
-
#
|
182
|
-
# The general semantics of these expressions are that the given version
|
183
|
-
# forms a lower bound for the range, and the upper bound is either the
|
184
|
-
# next version number increment (at whatever precision the expression
|
185
|
-
# provides) or the next stable version (in the case of a prerelease
|
186
|
-
# version).
|
187
|
-
#
|
188
|
-
# @example "Reasonably close" major version
|
189
|
-
# "~1" # => (>=1.0.0 <2.0.0)
|
190
|
-
# @example "Reasonably close" minor version
|
191
|
-
# "~1.2" # => (>=1.2.0 <1.3.0)
|
192
|
-
# @example "Reasonably close" patch version
|
193
|
-
# "~1.2.3" # => (>=1.2.3 <1.3.0)
|
194
|
-
# @example "Reasonably close" prerelease version
|
195
|
-
# "~1.2.3-alpha" # => (>=1.2.3-alpha <1.2.4)
|
196
|
-
#
|
197
|
-
# @param expr [String] a "loose" expression to build the range around
|
198
|
-
# @return [VersionRange] a "reasonably close" version range
|
199
|
-
def parse_reasonably_close_expression(expr)
|
200
|
-
parsed, succ = process_loose_expr(expr)
|
379
|
+
def exclude_end?
|
380
|
+
false
|
381
|
+
end
|
201
382
|
|
202
|
-
|
203
|
-
|
383
|
+
def eql?(other)
|
384
|
+
other.class.eql?(self.class)
|
385
|
+
end
|
204
386
|
|
205
|
-
|
206
|
-
|
387
|
+
def ==(other)
|
388
|
+
eql?(other)
|
389
|
+
end
|
207
390
|
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
391
|
+
def lower_bound?
|
392
|
+
false
|
393
|
+
end
|
394
|
+
|
395
|
+
def upper_bound?
|
396
|
+
false
|
213
397
|
end
|
214
398
|
|
215
|
-
#
|
216
|
-
# version numbers) and creates a range that covers all versions between
|
217
|
-
# them. These take the form:
|
399
|
+
# Merge two ranges so that the result matches the intersection of all matching versions.
|
218
400
|
#
|
219
|
-
#
|
401
|
+
# @param range [AbastractRange] the range to intersect with
|
402
|
+
# @return [AbastractRange,nil] the intersection between the ranges
|
220
403
|
#
|
221
|
-
# @
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
404
|
+
# @api private
|
405
|
+
def intersection(range)
|
406
|
+
cmp = self.begin <=> range.end
|
407
|
+
if cmp > 0
|
408
|
+
nil
|
409
|
+
elsif cmp == 0
|
410
|
+
exclude_begin? || range.exclude_end? ? nil : EqRange.new(self.begin)
|
411
|
+
else
|
412
|
+
cmp = range.begin <=> self.end
|
413
|
+
if cmp > 0
|
414
|
+
nil
|
415
|
+
elsif cmp == 0
|
416
|
+
range.exclude_begin? || exclude_end? ? nil : EqRange.new(range.begin)
|
417
|
+
else
|
418
|
+
cmp = self.begin <=> range.begin
|
419
|
+
min = if cmp < 0
|
420
|
+
range
|
421
|
+
elsif cmp > 0
|
422
|
+
self
|
423
|
+
else
|
424
|
+
self.exclude_begin? ? self : range
|
425
|
+
end
|
426
|
+
|
427
|
+
cmp = self.end <=> range.end
|
428
|
+
max = if cmp > 0
|
429
|
+
range
|
430
|
+
elsif cmp < 0
|
431
|
+
self
|
432
|
+
else
|
433
|
+
self.exclude_end? ? self : range
|
434
|
+
end
|
435
|
+
|
436
|
+
if !max.upper_bound?
|
437
|
+
min
|
438
|
+
elsif !min.lower_bound?
|
439
|
+
max
|
440
|
+
else
|
441
|
+
MinMaxRange.new(min, max)
|
442
|
+
end
|
443
|
+
end
|
232
444
|
end
|
233
|
-
|
234
|
-
self.new(start, finish, exclude)
|
235
445
|
end
|
236
446
|
|
237
|
-
#
|
238
|
-
#
|
239
|
-
#
|
240
|
-
# * [Major].[Minor].[Patch]-[Prerelease]
|
241
|
-
# * [Major].[Minor].[Patch]
|
242
|
-
# * [Major].[Minor]
|
243
|
-
# * [Major]
|
447
|
+
# Merge two ranges so that the result matches the sum of all matching versions. A merge
|
448
|
+
# is only possible when the ranges are either adjacent or have an overlap.
|
244
449
|
#
|
245
|
-
#
|
246
|
-
#
|
450
|
+
# @param other [AbastractRange] the range to merge with
|
451
|
+
# @return [AbastractRange,nil] the result of the merge
|
247
452
|
#
|
248
|
-
#
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
453
|
+
# @api private
|
454
|
+
def merge(other)
|
455
|
+
if include?(other.begin) || other.include?(self.begin)
|
456
|
+
cmp = self.begin <=> other.begin
|
457
|
+
if cmp < 0
|
458
|
+
min = self.begin
|
459
|
+
excl_begin = exclude_begin?
|
460
|
+
elsif cmp > 0
|
461
|
+
min = other.begin
|
462
|
+
excl_begin = other.exclude_begin?
|
463
|
+
else
|
464
|
+
min = self.begin
|
465
|
+
excl_begin = exclude_begin? && other.exclude_begin?
|
466
|
+
end
|
467
|
+
|
468
|
+
cmp = self.end <=> other.end
|
469
|
+
if cmp > 0
|
470
|
+
max = self.end
|
471
|
+
excl_end = self.exclude_end?
|
472
|
+
elsif cmp < 0
|
473
|
+
max = other.end
|
474
|
+
excl_end = other.exclude_end?
|
475
|
+
else
|
476
|
+
max = self.end
|
477
|
+
excl_end = exclude_end && other.exclude_end?
|
478
|
+
end
|
479
|
+
|
480
|
+
MinMaxRange.create(excl_begin ? GtRange.new(min) : GtEqRange.new(min), excl_end ? LtRange.new(max) : LtEqRange.new(max))
|
481
|
+
elsif exclude_end? && !other.exclude_begin? && self.end == other.begin
|
482
|
+
# Adjacent, self before other
|
483
|
+
from_to(self, other)
|
484
|
+
elsif other.exclude_end? && !exclude_begin? && other.end == self.begin
|
485
|
+
# Adjacent, other before self
|
486
|
+
from_to(other, self)
|
487
|
+
elsif !exclude_end? && !other.exclude_begin? && self.end.next(:patch) == other.begin
|
488
|
+
# Adjacent, self before other
|
489
|
+
from_to(self, other)
|
490
|
+
elsif !other.exclude_end? && !exclude_begin? && other.end.next(:patch) == self.begin
|
491
|
+
# Adjacent, other before self
|
492
|
+
from_to(other, self)
|
493
|
+
else
|
494
|
+
# No overlap
|
495
|
+
nil
|
266
496
|
end
|
497
|
+
end
|
267
498
|
|
268
|
-
|
499
|
+
# Checks if this matcher accepts a prerelease with the same major, minor, patch triple as the given version. Only matchers
|
500
|
+
# where this has been explicitly stated will respond `true` to this method
|
501
|
+
#
|
502
|
+
# @return [Boolean] `true` if this matcher accepts a prerelase with the tuple from the given version
|
503
|
+
def test_prerelease?(_)
|
504
|
+
false
|
505
|
+
end
|
269
506
|
|
270
|
-
|
271
|
-
next_version = version.next(arity)
|
272
|
-
end
|
507
|
+
private
|
273
508
|
|
274
|
-
|
509
|
+
def from_to(a, b)
|
510
|
+
MinMaxRange.create(a.exclude_begin? ? GtRange.new(a.begin) : GtEqRange.new(a.begin), b.exclude_end? ? LtRange.new(b.end) : LtEqRange.new(b.end))
|
275
511
|
end
|
276
512
|
end
|
277
513
|
|
278
|
-
#
|
279
|
-
|
280
|
-
|
281
|
-
# @param other [VersionRange] the range to intersect with
|
282
|
-
# @return [VersionRange] the common subset
|
283
|
-
def intersection(other)
|
284
|
-
raise NOT_A_VERSION_RANGE unless other.kind_of?(VersionRange)
|
514
|
+
# @api private
|
515
|
+
class AllRange < AbstractRange
|
516
|
+
SINGLETON = AllRange.new
|
285
517
|
|
286
|
-
|
287
|
-
|
518
|
+
def intersection(range)
|
519
|
+
range
|
288
520
|
end
|
289
521
|
|
290
|
-
|
291
|
-
|
522
|
+
def merge(range)
|
523
|
+
self
|
292
524
|
end
|
293
525
|
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
alias :& :intersection
|
526
|
+
def test_prerelease?(_)
|
527
|
+
true
|
528
|
+
end
|
298
529
|
|
299
|
-
|
300
|
-
|
301
|
-
#
|
302
|
-
# @return [String] a range expression representing this VersionRange
|
303
|
-
def to_s
|
304
|
-
start, finish = self.begin, self.end
|
305
|
-
inclusive = exclude_end? ? '' : '='
|
306
|
-
|
307
|
-
case
|
308
|
-
when EMPTY_RANGE == self
|
309
|
-
"<0.0.0"
|
310
|
-
when exact_version?, patch_version?
|
311
|
-
"#{ start }"
|
312
|
-
when minor_version?
|
313
|
-
"#{ start }".sub(/.0$/, '.x')
|
314
|
-
when major_version?
|
315
|
-
"#{ start }".sub(/.0.0$/, '.x')
|
316
|
-
when open_end? && start.to_s =~ /-.*[.]0$/
|
317
|
-
">#{ start }".sub(/.0$/, '')
|
318
|
-
when open_end?
|
319
|
-
">=#{ start }"
|
320
|
-
when open_begin?
|
321
|
-
"<#{ inclusive }#{ finish }"
|
322
|
-
else
|
323
|
-
">=#{ start } <#{ inclusive }#{ finish }"
|
530
|
+
def to_s
|
531
|
+
'*'
|
324
532
|
end
|
325
533
|
end
|
326
|
-
alias :inspect :to_s
|
327
534
|
|
328
|
-
private
|
535
|
+
# @api private
|
536
|
+
class MinMaxRange < AbstractRange
|
537
|
+
attr_reader :min, :max
|
329
538
|
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
# @param other [VersionRange] the range to compare against
|
334
|
-
# @return [Boolean] true if the endpoint for this range is less than or
|
335
|
-
# equal to the endpoint of the `other` range.
|
336
|
-
def ends_before?(other)
|
337
|
-
self.end < other.end || (self.end == other.end && self.exclude_end?)
|
338
|
-
end
|
539
|
+
def self.create(*ranges)
|
540
|
+
ranges.reduce { |memo, range| memo.intersection(range) }
|
541
|
+
end
|
339
542
|
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
end
|
543
|
+
def initialize(min, max)
|
544
|
+
@min = min.is_a?(MinMaxRange) ? min.min : min
|
545
|
+
@max = max.is_a?(MinMaxRange) ? max.max : max
|
546
|
+
end
|
345
547
|
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
self.begin == SemanticPuppet::Version::MIN
|
350
|
-
end
|
548
|
+
def begin
|
549
|
+
@min.begin
|
550
|
+
end
|
351
551
|
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
552
|
+
def end
|
553
|
+
@max.end
|
554
|
+
end
|
555
|
+
|
556
|
+
def exclude_begin?
|
557
|
+
@min.exclude_begin?
|
558
|
+
end
|
559
|
+
|
560
|
+
def exclude_end?
|
561
|
+
@max.exclude_end?
|
562
|
+
end
|
563
|
+
|
564
|
+
def eql?(other)
|
565
|
+
super && @min.eql?(other.min) && @max.eql?(other.max)
|
566
|
+
end
|
567
|
+
|
568
|
+
def hash
|
569
|
+
@min.hash ^ @max.hash
|
570
|
+
end
|
571
|
+
|
572
|
+
def include?(version)
|
573
|
+
@min.include?(version) && @max.include?(version)
|
574
|
+
end
|
575
|
+
|
576
|
+
def lower_bound?
|
577
|
+
@min.lower_bound?
|
578
|
+
end
|
579
|
+
|
580
|
+
def upper_bound?
|
581
|
+
@max.upper_bound?
|
582
|
+
end
|
583
|
+
|
584
|
+
def test_prerelease?(version)
|
585
|
+
@min.test_prerelease?(version) || @max.test_prerelease?(version)
|
586
|
+
end
|
358
587
|
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
start, finish = self.begin, self.end
|
364
|
-
|
365
|
-
exclude_end? &&
|
366
|
-
start.major.next == finish.major &&
|
367
|
-
same_minor? && start.minor == 0 &&
|
368
|
-
same_patch? && start.patch == 0 &&
|
369
|
-
[start.prerelease, finish.prerelease] == ['', '']
|
588
|
+
def to_s
|
589
|
+
"#{@min} #{@max}"
|
590
|
+
end
|
591
|
+
alias inspect to_s
|
370
592
|
end
|
371
593
|
|
372
|
-
#
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
594
|
+
# @api private
|
595
|
+
class ComparatorRange < AbstractRange
|
596
|
+
attr_reader :version
|
597
|
+
|
598
|
+
def initialize(version)
|
599
|
+
@version = version
|
600
|
+
end
|
601
|
+
|
602
|
+
def eql?(other)
|
603
|
+
super && @version.eql?(other.version)
|
604
|
+
end
|
605
|
+
|
606
|
+
def hash
|
607
|
+
@class.hash ^ @version.hash
|
608
|
+
end
|
609
|
+
|
610
|
+
# Checks if this matcher accepts a prerelease with the same major, minor, patch triple as the given version
|
611
|
+
def test_prerelease?(version)
|
612
|
+
!@version.stable? && @version.major == version.major && @version.minor == version.minor && @version.patch == version.patch
|
613
|
+
end
|
383
614
|
end
|
384
615
|
|
385
|
-
#
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
616
|
+
# @api private
|
617
|
+
class GtRange < ComparatorRange
|
618
|
+
def include?(version)
|
619
|
+
version > @version
|
620
|
+
end
|
621
|
+
|
622
|
+
def exclude_begin?
|
623
|
+
true
|
624
|
+
end
|
625
|
+
|
626
|
+
def begin
|
627
|
+
@version
|
628
|
+
end
|
629
|
+
|
630
|
+
def lower_bound?
|
631
|
+
true
|
632
|
+
end
|
633
|
+
|
634
|
+
def to_s
|
635
|
+
">#{@version}"
|
636
|
+
end
|
396
637
|
end
|
397
638
|
|
398
|
-
# @
|
399
|
-
|
400
|
-
|
639
|
+
# @api private
|
640
|
+
class GtEqRange < ComparatorRange
|
641
|
+
def include?(version)
|
642
|
+
version >= @version
|
643
|
+
end
|
644
|
+
|
645
|
+
def begin
|
646
|
+
@version
|
647
|
+
end
|
648
|
+
|
649
|
+
def lower_bound?
|
650
|
+
@version != Version::MIN
|
651
|
+
end
|
652
|
+
|
653
|
+
def to_s
|
654
|
+
">=#{@version}"
|
655
|
+
end
|
401
656
|
end
|
402
657
|
|
403
|
-
# @
|
404
|
-
|
405
|
-
|
658
|
+
# @api private
|
659
|
+
class LtRange < ComparatorRange
|
660
|
+
MATCH_NOTHING = LtRange.new(Version::MIN)
|
661
|
+
|
662
|
+
def include?(version)
|
663
|
+
version < @version
|
664
|
+
end
|
665
|
+
|
666
|
+
def exclude_end?
|
667
|
+
true
|
668
|
+
end
|
669
|
+
|
670
|
+
def end
|
671
|
+
@version
|
672
|
+
end
|
673
|
+
|
674
|
+
def upper_bound?
|
675
|
+
true
|
676
|
+
end
|
677
|
+
|
678
|
+
def to_s
|
679
|
+
self.equal?(MATCH_NOTHING) ? '<0.0.0' : "<#{@version}"
|
680
|
+
end
|
406
681
|
end
|
407
682
|
|
408
|
-
# @
|
409
|
-
|
410
|
-
|
683
|
+
# @api private
|
684
|
+
class LtEqRange < ComparatorRange
|
685
|
+
def include?(version)
|
686
|
+
version <= @version
|
687
|
+
end
|
688
|
+
|
689
|
+
def end
|
690
|
+
@version
|
691
|
+
end
|
692
|
+
|
693
|
+
def upper_bound?
|
694
|
+
@version != Version::MAX
|
695
|
+
end
|
696
|
+
|
697
|
+
def to_s
|
698
|
+
"<=#{@version}"
|
699
|
+
end
|
411
700
|
end
|
412
701
|
|
413
|
-
|
702
|
+
# @api private
|
703
|
+
class EqRange < ComparatorRange
|
704
|
+
def include?(version)
|
705
|
+
version == @version
|
706
|
+
end
|
414
707
|
|
415
|
-
|
708
|
+
def begin
|
709
|
+
@version
|
710
|
+
end
|
711
|
+
|
712
|
+
def lower_bound?
|
713
|
+
@version != Version::MIN
|
714
|
+
end
|
416
715
|
|
417
|
-
|
716
|
+
def upper_bound?
|
717
|
+
@version != Version::MAX
|
718
|
+
end
|
719
|
+
|
720
|
+
def end
|
721
|
+
@version
|
722
|
+
end
|
723
|
+
|
724
|
+
def to_s
|
725
|
+
@version.to_s
|
726
|
+
end
|
727
|
+
end
|
418
728
|
|
419
729
|
# A range that matches no versions
|
420
|
-
EMPTY_RANGE = VersionRange.
|
730
|
+
EMPTY_RANGE = VersionRange.new([], nil).freeze
|
731
|
+
ALL_RANGE = VersionRange.new([AllRange::SINGLETON], '*')
|
421
732
|
end
|
422
733
|
end
|