metadata-json-lint 0.0.4 → 0.0.5

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.
@@ -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