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.
- checksums.yaml +7 -0
- data/.gitignore +4 -0
- data/Gemfile +3 -0
- data/LICENSE +13 -0
- data/README.md +62 -0
- data/lib/metadata-json-lint/semantic/version.rb +170 -0
- data/lib/metadata-json-lint/semantic/version_range.rb +423 -0
- data/lib/metadata_json_lint.rb +18 -2
- data/metadata-json-lint.gemspec +17 -0
- data/tests/metadata-bad_license.json +87 -0
- data/tests/metadata-broken.json +87 -0
- data/tests/metadata-duplicate-dep.json +91 -0
- data/tests/metadata-multiple_problems.json +86 -0
- data/tests/metadata-noname.json +86 -0
- data/tests/metadata-openenddep.json +87 -0
- data/tests/metadata-perfect.json +87 -0
- data/tests/metadata-types.json +88 -0
- data/tests/test.sh +83 -0
- metadata +42 -19
checksums.yaml
ADDED
@@ -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
|
data/.gitignore
ADDED
data/Gemfile
ADDED
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.
|
data/README.md
ADDED
@@ -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
|