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