metadata-json-lint 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a8b581daaa67e2771ff6140c639af6f1329e8d73
4
+ data.tar.gz: 8328a6afa75a9c140d4caea7645d1cac11c81678
5
+ SHA512:
6
+ metadata.gz: 36fea1589e860d39f2142bdf2ccca9789faba173d2e576d8566e03f29e01b092c5748c110b76049af65d5a269e33d5c5928d330c082e742260b59145e710cbbc
7
+ data.tar.gz: 226c601914ce93265675c2473589892581a804f5c5d56c7cfa725db89b139b3bc46bf018365794907f79d83579b9990c9b03a340c3eb96eec77130a7c35d3d88
@@ -0,0 +1,4 @@
1
+ *.swp
2
+ *.swo
3
+ *.gem
4
+ Gemfile.lock
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright 2014 HP Development Corporation L.P.
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this software except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
@@ -0,0 +1,62 @@
1
+ metadata-json-linter
2
+ --------------------
3
+
4
+ Simple tool to validate and lint metadata.json files in Puppet modules.
5
+
6
+
7
+
8
+ install
9
+ -------
10
+
11
+ ```shell
12
+ gem install metadata-json-lint
13
+ ```
14
+
15
+
16
+
17
+ usage
18
+ -----
19
+
20
+ ```shell
21
+ metadata-json-lint /path/too/metadata.json
22
+ ```
23
+
24
+
25
+
26
+ rake
27
+ ----
28
+
29
+
30
+ ```ruby
31
+ task :metadata do
32
+ sh "metadata-json-lint metadata.json"
33
+ end
34
+ ```
35
+
36
+
37
+
38
+ options
39
+ -------
40
+
41
+
42
+ ```
43
+ --no-fail-on-warnings
44
+ --strict-dependency
45
+ --no-strict-license
46
+ ```
47
+
48
+
49
+
50
+ contributors
51
+ ------------
52
+
53
+ A Big thank you to the code contributors:
54
+
55
+ Richard Pijnenburg
56
+ Dominic Cleal
57
+ Igor Galić
58
+ Mike Arnold
59
+
60
+
61
+
62
+
@@ -0,0 +1,170 @@
1
+ require 'semantic'
2
+ module MetadataJsonLint
3
+ module Semantic
4
+
5
+ # @note Semantic::Version subclasses Numeric so that it has sane Range
6
+ # semantics in Ruby 1.9+.
7
+ class Version < Numeric
8
+ include Comparable
9
+
10
+ class ValidationFailure < ArgumentError; end
11
+
12
+ class << self
13
+ LOOSE_REGEX = /
14
+ \A
15
+ (\d+)[.](\d+)[.](\d+) # Major . Minor . Patch
16
+ (?: [-](.*?))? # Prerelease
17
+ (?: [+](.*?))? # Build
18
+ \Z
19
+ /x
20
+
21
+ # Parse a Semantic Version string.
22
+ #
23
+ # @param ver [String] the version string to parse
24
+ # @return [Version] a comparable {Version} object
25
+ def parse(ver)
26
+ match, major, minor, patch, prerelease, build = *ver.match(LOOSE_REGEX)
27
+
28
+ if match.nil?
29
+ raise 'Version numbers MUST begin with three dot-separated numbers'
30
+ elsif [major, minor, patch].any? { |x| x =~ /^0\d+/ }
31
+ raise 'Version numbers MUST NOT contain leading zeroes'
32
+ end
33
+
34
+ prerelease = parse_prerelease(prerelease) if prerelease
35
+ build = parse_build_metadata(build) if build
36
+
37
+ self.new(major.to_i, minor.to_i, patch.to_i, prerelease, build)
38
+ end
39
+
40
+ private
41
+ def parse_prerelease(prerelease)
42
+ subject = 'Prerelease identifiers'
43
+ prerelease = prerelease.split('.', -1)
44
+
45
+ if prerelease.empty? or prerelease.any? { |x| x.empty? }
46
+ raise "#{subject} MUST NOT be empty"
47
+ elsif prerelease.any? { |x| x =~ /[^0-9a-zA-Z-]/ }
48
+ raise "#{subject} MUST use only ASCII alphanumerics and hyphens"
49
+ elsif prerelease.any? { |x| x =~ /^0\d+$/ }
50
+ raise "#{subject} MUST NOT contain leading zeroes"
51
+ end
52
+
53
+ return prerelease.map { |x| x =~ /^\d+$/ ? x.to_i : x }
54
+ end
55
+
56
+ def parse_build_metadata(build)
57
+ subject = 'Build identifiers'
58
+ build = build.split('.', -1)
59
+
60
+ if build.empty? or build.any? { |x| x.empty? }
61
+ raise "#{subject} MUST NOT be empty"
62
+ elsif build.any? { |x| x =~ /[^0-9a-zA-Z-]/ }
63
+ raise "#{subject} MUST use only ASCII alphanumerics and hyphens"
64
+ end
65
+
66
+ return build
67
+ end
68
+
69
+ def raise(msg)
70
+ super ValidationFailure, msg, caller.drop_while { |x| x !~ /\bparse\b/ }
71
+ end
72
+ end
73
+
74
+ attr_reader :major, :minor, :patch
75
+
76
+ def initialize(major, minor, patch, prerelease = nil, build = nil)
77
+ @major = major
78
+ @minor = minor
79
+ @patch = patch
80
+ @prerelease = prerelease
81
+ @build = build
82
+ end
83
+
84
+ def next(part)
85
+ case part
86
+ when :major
87
+ self.class.new(@major.next, 0, 0)
88
+ when :minor
89
+ self.class.new(@major, @minor.next, 0)
90
+ when :patch
91
+ self.class.new(@major, @minor, @patch.next)
92
+ end
93
+ end
94
+
95
+ def prerelease
96
+ @prerelease && @prerelease.join('.')
97
+ end
98
+
99
+ # @return [Boolean] true if this is a stable release
100
+ def stable?
101
+ @prerelease.nil?
102
+ end
103
+
104
+ def build
105
+ @build && @build.join('.')
106
+ end
107
+
108
+ def <=>(other)
109
+ return self.major <=> other.major unless self.major == other.major
110
+ return self.minor <=> other.minor unless self.minor == other.minor
111
+ return self.patch <=> other.patch unless self.patch == other.patch
112
+ return compare_prerelease(other)
113
+ end
114
+
115
+ def to_s
116
+ "#{major}.#{minor}.#{patch}" +
117
+ (@prerelease.nil? || prerelease.empty? ? '' : "-" + prerelease) +
118
+ (@build.nil? || build.empty? ? '' : "+" + build )
119
+ end
120
+
121
+ def hash
122
+ self.to_s.hash
123
+ end
124
+
125
+ private
126
+ # This is a hack; tildes sort later than any valid identifier. The
127
+ # advantage is that we don't need to handle stable vs. prerelease
128
+ # comparisons separately.
129
+ @@STABLE_RELEASE = [ '~' ].freeze
130
+
131
+ def compare_prerelease(other)
132
+ all_mine = @prerelease || @@STABLE_RELEASE
133
+ all_yours = other.instance_variable_get(:@prerelease) || @@STABLE_RELEASE
134
+
135
+ # Precedence is determined by comparing each dot separated identifier from
136
+ # left to right...
137
+ size = [ all_mine.size, all_yours.size ].max
138
+ Array.new(size).zip(all_mine, all_yours) do |_, mine, yours|
139
+
140
+ # ...until a difference is found.
141
+ next if mine == yours
142
+
143
+ # Numbers are compared numerically, strings are compared ASCIIbetically.
144
+ if mine.class == yours.class
145
+ return mine <=> yours
146
+
147
+ # A larger set of pre-release fields has a higher precedence.
148
+ elsif mine.nil?
149
+ return -1
150
+ elsif yours.nil?
151
+ return 1
152
+
153
+ # Numeric identifiers always have lower precedence than non-numeric.
154
+ elsif mine.is_a? Numeric
155
+ return -1
156
+ elsif yours.is_a? Numeric
157
+ return 1
158
+ end
159
+ end
160
+
161
+ return 0
162
+ end
163
+
164
+ def first_prerelease
165
+ self.class.new(@major, @minor, @patch, [])
166
+ end
167
+ end
168
+ end
169
+
170
+ end
@@ -0,0 +1,423 @@
1
+ require 'semantic'
2
+ module MetadataJsonLint
3
+ module Semantic
4
+ class VersionRange < Range
5
+ class << self
6
+ # Parses a version range string into a comparable {VersionRange} instance.
7
+ #
8
+ # Currently parsed version range string may take any of the following:
9
+ # forms:
10
+ #
11
+ # * Regular Semantic Version strings
12
+ # * ex. `"1.0.0"`, `"1.2.3-pre"`
13
+ # * Partial Semantic Version strings
14
+ # * ex. `"1.0.x"`, `"1"`, `"2.X"`
15
+ # * Inequalities
16
+ # * ex. `"> 1.0.0"`, `"<3.2.0"`, `">=4.0.0"`
17
+ # * Approximate Versions
18
+ # * ex. `"~1.0.0"`, `"~ 3.2.0"`, `"~4.0.0"`
19
+ # * Inclusive Ranges
20
+ # * ex. `"1.0.0 - 1.3.9"`
21
+ # * Range Intersections
22
+ # * ex. `">1.0.0 <=2.3.0"`
23
+ #
24
+ # @param range_str [String] the version range string to parse
25
+ # @return [VersionRange] a new {VersionRange} instance
26
+ def parse(range_str)
27
+ partial = '\d+(?:[.]\d+)?(?:[.][x]|[.]\d+(?:[-][0-9a-z.-]*)?)?'
28
+ exact = '\d+[.]\d+[.]\d+(?:[-][0-9a-z.-]*)?'
29
+
30
+ range = range_str.gsub(/([(><=~])[ ]+/, '\1')
31
+ range = range.gsub(/ - /, '#').strip
32
+
33
+ return case range
34
+ when /\A(#{partial})\Z/i
35
+ parse_loose_version_expression($1)
36
+ when /\A([><][=]?)(#{exact})\Z/i
37
+ parse_inequality_expression($1, $2)
38
+ when /\A~(#{partial})\Z/i
39
+ parse_reasonably_close_expression($1)
40
+ when /\A(#{exact})#(#{exact})\Z/i
41
+ parse_inclusive_range_expression($1, $2)
42
+ when /[ ]+/
43
+ parse_intersection_expression(range)
44
+ else
45
+ raise ArgumentError
46
+ end
47
+
48
+ rescue ArgumentError
49
+ raise ArgumentError, "Unparsable version range: #{range_str.inspect}"
50
+ end
51
+
52
+ private
53
+
54
+ # Creates a new {VersionRange} from a range intersection expression.
55
+ #
56
+ # @param expr [String] a range intersection expression
57
+ # @return [VersionRange] a version range representing `expr`
58
+ def parse_intersection_expression(expr)
59
+ expr.split(/[ ]+/).map { |x| parse(x) }.inject { |a,b| a & b }
60
+ end
61
+
62
+ # Creates a new {VersionRange} from a "loose" description of a Semantic
63
+ # Version number.
64
+ #
65
+ # @see .process_loose_expr
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)
71
+
72
+ if start.stable?
73
+ start = start.send(:first_prerelease)
74
+ end
75
+
76
+ if finish.stable?
77
+ exclude = true
78
+ finish = finish.send(:first_prerelease)
79
+ end
80
+
81
+ self.new(start, finish, exclude)
82
+ end
83
+
84
+ # Creates an open-ended version range from an inequality expression.
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
113
+
114
+ # Returns a range covering all versions greater than the given `expr`.
115
+ #
116
+ # @param expr [String] the version to be greater than
117
+ # @return [VersionRange] a range covering all versions greater than the
118
+ # given `expr`
119
+ def parse_gt_expression(expr)
120
+ if expr =~ /^[^+]*-/
121
+ start = Version.parse("#{expr}.0")
122
+ else
123
+ start = process_loose_expr(expr).last.send(:first_prerelease)
124
+ end
125
+
126
+ self.new(start, MAX_VERSION)
127
+ end
128
+
129
+ # Returns a range covering all versions greater than or equal to the given
130
+ # `expr`.
131
+ #
132
+ # @param expr [String] the version to be greater than or equal to
133
+ # @return [VersionRange] a range covering all versions greater than or
134
+ # equal to the given `expr`
135
+ def parse_gte_expression(expr)
136
+ if expr =~ /^[^+]*-/
137
+ start = Version.parse(expr)
138
+ else
139
+ start = process_loose_expr(expr).first.send(:first_prerelease)
140
+ end
141
+
142
+ self.new(start, MAX_VERSION)
143
+ end
144
+
145
+ # Returns a range covering all versions less than the given `expr`.
146
+ #
147
+ # @param expr [String] the version to be less than
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
156
+
157
+ self.new(MIN_VERSION, finish, true)
158
+ end
159
+
160
+ # Returns a range covering all versions less than or equal to the given
161
+ # `expr`.
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(MIN_VERSION, finish)
170
+ else
171
+ finish = process_loose_expr(expr).last.send(:first_prerelease)
172
+ self.new(MIN_VERSION, finish, true)
173
+ end
174
+ end
175
+
176
+ # The "reasonably close" expression is used to designate ranges that have
177
+ # a reasonable proximity to the given "loose" version number. These take
178
+ # the form:
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)
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)
201
+
202
+ if parsed.stable?
203
+ parsed = parsed.send(:first_prerelease)
204
+ succ = succ.send(:first_prerelease)
205
+ self.new(parsed, succ, true)
206
+ else
207
+ self.new(parsed, succ.next(:patch).send(:first_prerelease), true)
208
+ end
209
+ end
210
+
211
+ # An "inclusive range" expression takes two version numbers (or partial
212
+ # version numbers) and creates a range that covers all versions between
213
+ # them. These take the form:
214
+ #
215
+ # [Version] - [Version]
216
+ #
217
+ # @param start [String] a "loose" expresssion for the start of the range
218
+ # @param finish [String] a "loose" expression for the end of the range
219
+ # @return [VersionRange] a {VersionRange} covering `start` to `finish`
220
+ def parse_inclusive_range_expression(start, finish)
221
+ start, _ = process_loose_expr(start)
222
+ _, finish = process_loose_expr(finish)
223
+
224
+ start = start.send(:first_prerelease) if start.stable?
225
+ if finish.stable?
226
+ exclude = true
227
+ finish = finish.send(:first_prerelease)
228
+ end
229
+
230
+ self.new(start, finish, exclude)
231
+ end
232
+
233
+ # A "loose expression" is one that takes the form of all or part of a
234
+ # valid Semantic Version number. Particularly:
235
+ #
236
+ # * [Major].[Minor].[Patch]-[Prerelease]
237
+ # * [Major].[Minor].[Patch]
238
+ # * [Major].[Minor]
239
+ # * [Major]
240
+ #
241
+ # Various placeholders are also permitted in "loose expressions"
242
+ # (typically an 'x' or an asterisk).
243
+ #
244
+ # This method parses these expressions into a minimal and maximal version
245
+ # number pair.
246
+ #
247
+ # @todo Stabilize whether the second value is inclusive or exclusive
248
+ #
249
+ # @param expr [String] a string containing a "loose" version expression
250
+ # @return [(VersionNumber, VersionNumber)] a minimal and maximal
251
+ # version pair for the given expression
252
+ def process_loose_expr(expr)
253
+ case expr
254
+ when /^(\d+)(?:[.][xX*])?$/
255
+ expr = "#{$1}.0.0"
256
+ arity = :major
257
+ when /^(\d+[.]\d+)(?:[.][xX*])?$/
258
+ expr = "#{$1}.0"
259
+ arity = :minor
260
+ when /^\d+[.]\d+[.]\d+$/
261
+ arity = :patch
262
+ end
263
+
264
+ version = next_version = Version.parse(expr)
265
+
266
+ if arity
267
+ next_version = version.next(arity)
268
+ end
269
+
270
+ [ version, next_version ]
271
+ end
272
+ end
273
+
274
+ # Computes the intersection of a pair of ranges. If the ranges have no
275
+ # useful intersection, an empty range is returned.
276
+ #
277
+ # @param other [VersionRange] the range to intersect with
278
+ # @return [VersionRange] the common subset
279
+ def intersection(other)
280
+ raise NOT_A_VERSION_RANGE unless other.kind_of?(VersionRange)
281
+
282
+ if self.begin < other.begin
283
+ return other.intersection(self)
284
+ end
285
+
286
+ unless include?(other.begin) || other.include?(self.begin)
287
+ return EMPTY_RANGE
288
+ end
289
+
290
+ endpoint = ends_before?(other) ? self : other
291
+ VersionRange.new(self.begin, endpoint.end, endpoint.exclude_end?)
292
+ end
293
+ alias :& :intersection
294
+
295
+ # Returns a string representation of this range, prefering simple common
296
+ # expressions for comprehension.
297
+ #
298
+ # @return [String] a range expression representing this VersionRange
299
+ def to_s
300
+ start, finish = self.begin, self.end
301
+ inclusive = exclude_end? ? '' : '='
302
+
303
+ case
304
+ when EMPTY_RANGE == self
305
+ "<0.0.0"
306
+ when exact_version?, patch_version?
307
+ "#{ start }"
308
+ when minor_version?
309
+ "#{ start }".sub(/.0$/, '.x')
310
+ when major_version?
311
+ "#{ start }".sub(/.0.0$/, '.x')
312
+ when open_end? && start.to_s =~ /-.*[.]0$/
313
+ ">#{ start }".sub(/.0$/, '')
314
+ when open_end?
315
+ ">=#{ start }"
316
+ when open_begin?
317
+ "<#{ inclusive }#{ finish }"
318
+ else
319
+ ">=#{ start } <#{ inclusive }#{ finish }"
320
+ end
321
+ end
322
+ alias :inspect :to_s
323
+
324
+ # The lowest precedence Version possible
325
+ MIN_VERSION = Version.new(0, 0, 0, []).freeze
326
+
327
+ # The highest precedence Version possible
328
+ MAX_VERSION = Version.new((1.0/0.0), 0, 0).freeze
329
+
330
+ # Determines whether this {VersionRange} has an earlier endpoint than the
331
+ # give `other` range.
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
339
+
340
+ # Describes whether this range has an upper limit.
341
+ # @return [Boolean] true if this range has no upper limit
342
+ def open_end?
343
+ self.end == MAX_VERSION
344
+ end
345
+
346
+ # Describes whether this range has a lower limit.
347
+ # @return [Boolean] true if this range has no lower limit
348
+ def open_begin?
349
+ self.begin == MIN_VERSION
350
+ end
351
+
352
+ # Describes whether this range follows the patterns for matching all
353
+ # releases with the same exact version.
354
+ # @return [Boolean] true if this range matches only a single exact version
355
+ def exact_version?
356
+ self.begin == self.end
357
+ end
358
+
359
+ # Describes whether this range follows the patterns for matching all
360
+ # releases with the same major version.
361
+ # @return [Boolean] true if this range matches only a single major version
362
+ def major_version?
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] == ['', '']
370
+ end
371
+
372
+ # Describes whether this range follows the patterns for matching all
373
+ # releases with the same minor version.
374
+ # @return [Boolean] true if this range matches only a single minor version
375
+ def minor_version?
376
+ start, finish = self.begin, self.end
377
+
378
+ exclude_end? &&
379
+ same_major? &&
380
+ start.minor.next == finish.minor &&
381
+ same_patch? && start.patch == 0 &&
382
+ [start.prerelease, finish.prerelease] == ['', '']
383
+ end
384
+
385
+ # Describes whether this range follows the patterns for matching all
386
+ # releases with the same patch version.
387
+ # @return [Boolean] true if this range matches only a single patch version
388
+ def patch_version?
389
+ start, finish = self.begin, self.end
390
+
391
+ exclude_end? &&
392
+ same_major? &&
393
+ same_minor? &&
394
+ start.patch.next == finish.patch &&
395
+ [start.prerelease, finish.prerelease] == ['', '']
396
+ end
397
+
398
+ # @return [Boolean] true if `begin` and `end` share the same major verion
399
+ def same_major?
400
+ self.begin.major == self.end.major
401
+ end
402
+
403
+ # @return [Boolean] true if `begin` and `end` share the same minor verion
404
+ def same_minor?
405
+ self.begin.minor == self.end.minor
406
+ end
407
+
408
+ # @return [Boolean] true if `begin` and `end` share the same patch verion
409
+ def same_patch?
410
+ self.begin.patch == self.end.patch
411
+ end
412
+
413
+ undef :to_a
414
+
415
+ NOT_A_VERSION_RANGE = ArgumentError.new("value must be a #{VersionRange}")
416
+
417
+ public
418
+
419
+ # A range that matches no versions
420
+ EMPTY_RANGE = VersionRange.parse('< 0.0.0').freeze
421
+ end
422
+ end
423
+ end