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