class SemanticPuppet::VersionRange
A Semantic Version
Range.
@see github.com/npm/node-semver for full specification @api public
Constants
- ALL_RANGE
- EMPTY_RANGE
A range that matches no versions
- HYPHEN
- HYPHEN_EXPR
- LOGICAL_OR
- LOWER_X
- NR
- PART
- PARTIAL
- PARTIAL_EXPR
- PARTS
- QUALIFIER
- QUALIFIER_NC
- RANGE_SPLIT
- SIMPLE
The ~> isn't in the spec but allowed
- SIMPLE_EXPR
- SIMPLE_WITH_EXTRA_WS
- SIMPLE_WITH_EXTRA_WS_EXPR
- STAR
- UPPER_X
- XR
- XR_NC
Attributes
Provides read access to the ranges. For internal use only @api private
Public Class Methods
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_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
Private Class Methods
# 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}\"" 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
Public Instance Methods
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 #{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