A Semantic Version Range.
@see github.com/npm/node-semver for full specification @api public
A range that matches no versions
The ~> isn't in the spec but allowed
Provides read access to the ranges. For internal use only @api private
Creates a new version range @overload initialize(from, to, exclude_end = false)
Creates a new instance using ruby `Range` semantics @param begin [String,Version] the version denoting the start of the range (always inclusive) @param end [String,Version] the version denoting the end of the range @param exclude_end [Boolean] `true` if the `end` version should be excluded from the range
@overload initialize(ranges, string)
Creates a new instance based on parsed content. For internal use only @param ranges [Array<AbstractRange>] the ranges to include in this range @param string [String] the original string representation that was parsed to produce the ranges
@api private
# File lib/semantic_puppet/version_range.rb, line 224 def initialize(ranges, string, exclude_end = false) unless ranges.is_a?(Array) lb = GtEqRange.new(ranges) if exclude_end ub = LtRange.new(string) string = ">=#{string} <#{ranges}" else ub = LtEqRange.new(string) string = "#{string} - #{ranges}" end ranges = [MinMaxRange.create(lb, ub)] end ranges.compact! merge_happened = true while ranges.size > 1 && merge_happened # merge ranges if possible merge_happened = false result = [] until ranges.empty? unmerged = [] x = ranges.pop result << ranges.reduce(x) do |memo, y| merged = memo.merge(y) if merged.nil? unmerged << y else merge_happened = true memo = merged end memo end ranges = unmerged end ranges = result.reverse! end ranges = [LtRange::MATCH_NOTHING] if ranges.empty? @ranges = ranges @string = string.nil? ? ranges.join(' || ') : string end
Parses a version range string into a comparable {VersionRange} instance.
Currently parsed version range string may take any of the following: forms:
Regular Semantic Version strings
ex. `“1.0.0”`, `“1.2.3-pre”`
Partial Semantic Version strings
ex. `“1.0.x”`, `“1”`, `“2.X”`, `“3.*”`,
Inequalities
ex. `“> 1.0.0”`, `“<3.2.0”`, `“>=4.0.0”`
Approximate Caret Versions
ex. `“^1”`, `“^3.2”`, `“^4.1.0”`
Approximate Tilde Versions
ex. `“~1.0.0”`, `“~ 3.2.0”`, `“~4.0.0”`
Inclusive Ranges
ex. `“1.0.0 - 1.3.9”`
Range Intersections
ex. `“>1.0.0 <=2.3.0”`
Combined ranges
ex, `“>=1.0.0 <2.3.0 || >=2.5.0 <3.0.0”`
@param range_string [String] the version range string to parse @return [VersionRange] a new {VersionRange} instance @api public
# File lib/semantic_puppet/version_range.rb, line 64 def self.parse(range_string) # Remove extra whitespace after operators. Such whitespace should not cause a split range_set = range_string.gsub(/([><=~^])(?:\s+|\s*v)/, '\1') ranges = range_set.split(LOGICAL_OR) return ALL_RANGE if ranges.empty? new(ranges.map do |range| if range =~ HYPHEN_EXPR MinMaxRange.create(GtEqRange.new(parse_version($1)), LtEqRange.new(parse_version($2))) else # Split on whitespace simples = range.split(RANGE_SPLIT).map do |simple| match_data = SIMPLE_EXPR.match(simple) raise ArgumentError, _("Unparsable version range: \"%{range}\"") % { range: range_string } unless match_data operand = match_data[2] # Case based on operator case match_data[1] when '~', '~>', '~=' parse_tilde(operand) when '^' parse_caret(operand) when '>' parse_gt_version(operand) when '>=' GtEqRange.new(parse_version(operand)) when '<' LtRange.new(parse_version(operand)) when '<=' parse_lteq_version(operand) when '=' parse_xrange(operand) else parse_xrange(operand) end end simples.size == 1 ? simples[0] : MinMaxRange.create(*simples) end end.uniq, range_string).freeze end
# File lib/semantic_puppet/version_range.rb, line 147 def self.allow_minor_updates(major, match_data) return AllRange.SINGLETON unless major minor = digit(match_data[2]) if minor patch = digit(match_data[3]) if patch.nil? MinMaxRange.new(GtEqRange.new(Version.new(major, minor, 0)), LtRange.new(Version.new(major + 1, 0, 0))) else if match_data[4].nil? MinMaxRange.new(GtEqRange.new(Version.new(major, minor, patch)), LtRange.new(Version.new(major + 1, 0, 0))) else MinMaxRange.new( GtEqRange.new( Version.new(major, minor, patch, Version.parse_prerelease(match_data[4]), Version.parse_build(match_data[5]))), LtRange.new(Version.new(major + 1, 0, 0))) end end else MinMaxRange.new(GtEqRange.new(Version.new(major, 0, 0)), LtRange.new(Version.new(major + 1, 0, 0))) end end
# File lib/semantic_puppet/version_range.rb, line 131 def self.allow_patch_updates(major, match_data, tilde_or_caret = true) return AllRange::SINGLETON unless major minor = digit(match_data[2]) return MinMaxRange.new(GtEqRange.new(Version.new(major, 0, 0)), LtRange.new(Version.new(major + 1, 0, 0))) unless minor patch = digit(match_data[3]) return MinMaxRange.new(GtEqRange.new(Version.new(major, minor, 0)), LtRange.new(Version.new(major, minor + 1, 0))) unless patch version = Version.new(major, minor, patch, Version.parse_prerelease(match_data[4]), Version.parse_build(match_data[5])) return EqRange.new(version) unless tilde_or_caret MinMaxRange.new(GtEqRange.new(version), LtRange.new(Version.new(major, minor + 1, 0))) end
# File lib/semantic_puppet/version_range.rb, line 170 def self.digit(str) (str.nil? || UPPER_X == str || LOWER_X == str || STAR == str) ? nil : str.to_i end
# File lib/semantic_puppet/version_range.rb, line 112 def self.parse_caret(expr) match_data = parse_partial(expr) major = digit(match_data[1]) major == 0 ? allow_patch_updates(major, match_data) : allow_minor_updates(major, match_data) end
# File lib/semantic_puppet/version_range.rb, line 184 def self.parse_gt_version(expr) match_data = parse_partial(expr) major = digit(match_data[1]) return LtRange::MATCH_NOTHING unless major minor = digit(match_data[2]) return GtEqRange.new(Version.new(major + 1, 0, 0)) unless minor patch = digit(match_data[3]) return GtEqRange.new(Version.new(major, minor + 1, 0)) unless patch return GtRange.new(Version.new(major, minor, patch, Version.parse_prerelease(match_data[4]), Version.parse_build(match_data[5]))) end
# File lib/semantic_puppet/version_range.rb, line 196 def self.parse_lteq_version(expr) match_data = parse_partial(expr) major = digit(match_data[1]) return AllRange.SINGLETON unless major minor = digit(match_data[2]) return LtRange.new(Version.new(major + 1, 0, 0)) unless minor patch = digit(match_data[3]) return LtRange.new(Version.new(major, minor + 1, 0)) unless patch return LtEqRange.new(Version.new(major, minor, patch, Version.parse_prerelease(match_data[4]), Version.parse_build(match_data[5]))) end
# File lib/semantic_puppet/version_range.rb, line 105 def self.parse_partial(expr) match_data = PARTIAL_EXPR.match(expr) raise ArgumentError, _("Unparsable version range: \"%{expr}\"") % { expr: expr } unless match_data match_data end
# File lib/semantic_puppet/version_range.rb, line 119 def self.parse_tilde(expr) match_data = parse_partial(expr) allow_patch_updates(digit(match_data[1]), match_data) end
# File lib/semantic_puppet/version_range.rb, line 175 def self.parse_version(expr) match_data = parse_partial(expr) major = digit(match_data[1]) || 0 minor = digit(match_data[2]) || 0 patch = digit(match_data[3]) || 0 Version.new(major, minor, patch, Version.parse_prerelease(match_data[4]), Version.parse_build(match_data[5])) end
# File lib/semantic_puppet/version_range.rb, line 125 def self.parse_xrange(expr) match_data = parse_partial(expr) allow_patch_updates(digit(match_data[1]), match_data, false) end
Returns the version that denotes the beginning of this range.
Since this really is an OR between disparate ranges, it may have multiple beginnings. This method returns `nil` if that is the case.
@return [Version] the beginning of the range, or `nil` if there are multiple beginnings @api public
# File lib/semantic_puppet/version_range.rb, line 282 def begin @ranges.size == 1 ? @ranges[0].begin : nil end
Returns the version that denotes the end of this range.
Since this really is an OR between disparate ranges, it may have multiple ends. This method returns `nil` if that is the case.
@return [Version] the end of the range, or `nil` if there are multiple ends @api public
# File lib/semantic_puppet/version_range.rb, line 293 def end @ranges.size == 1 ? @ranges[0].end : nil end
# File lib/semantic_puppet/version_range.rb, line 266 def eql?(range) range.is_a?(VersionRange) && @ranges.eql?(range.ranges) end
Returns `true` if the beginning is excluded from the range.
Since this really is an OR between disparate ranges, it may have multiple beginnings. This method returns `nil` if that is the case.
@return [Boolean] `true` if the beginning is excluded from the range, `false` if included, or `nil` if there are multiple beginnings @api public
# File lib/semantic_puppet/version_range.rb, line 304 def exclude_begin? @ranges.size == 1 ? @ranges[0].exclude_begin? : nil end
Returns `true` if the end is excluded from the range.
Since this really is an OR between disparate ranges, it may have multiple ends. This method returns `nil` if that is the case.
@return [Boolean] `true` if the end is excluded from the range, `false` if not, or `nil` if there are multiple ends @api public
# File lib/semantic_puppet/version_range.rb, line 315 def exclude_end? @ranges.size == 1 ? @ranges[0].exclude_end? : nil end
# File lib/semantic_puppet/version_range.rb, line 271 def hash @ranges.hash end
@return [Boolean] `true` if the given version is included in the range @api public
# File lib/semantic_puppet/version_range.rb, line 321 def include?(version) @ranges.any? { |range| range.include?(version) && (version.stable? || range.test_prerelease?(version)) } end
Returns a canonical string representation of this range, assembled from the internal matchers.
@return [String] a range expression representing this VersionRange @api public
# File lib/semantic_puppet/version_range.rb, line 357 def inspect @ranges.join(' || ') end
Computes the intersection of a pair of ranges. If the ranges have no useful intersection, an empty range is returned.
@param other [VersionRange] the range to intersect with @return [VersionRange] the common subset @api public
# File lib/semantic_puppet/version_range.rb, line 334 def intersection(other) raise ArgumentError, _("value must be a %{type}") % { :type => self.class.name } unless other.is_a?(VersionRange) result = @ranges.map { |range| other.ranges.map { |o_range| range.intersection(o_range) } }.flatten result.compact! result.uniq! result.empty? ? EMPTY_RANGE : VersionRange.new(result, nil) end
Returns a string representation of this range. This will be the string that was used when the range was parsed.
@return [String] a range expression representing this VersionRange @api public
# File lib/semantic_puppet/version_range.rb, line 348 def to_s @string end