Class: SemanticPuppet::VersionRange

Inherits:
Object
  • Object
show all
Defined in:
lib/puppet/vendor/semantic_puppet/lib/semantic_puppet/version_range.rb

Overview

A Semantic Version Range.

Defined Under Namespace

Classes: AbstractRange, AllRange, ComparatorRange, EqRange, GtEqRange, GtRange, LtEqRange, LtRange, MinMaxRange

Constant Summary collapse

UPPER_X =
'X'.freeze
LOWER_X =
'x'.freeze
STAR =
'*'.freeze
NR =
'0|[1-9][0-9]*'.freeze
XR =
'(x|X|\*|' + NR + ')'.freeze
XR_NC =
'(?:x|X|\*|' + NR + ')'.freeze
PART =
'(?:[0-9A-Za-z-]+)'.freeze
PARTS =
PART + '(?:\.' + PART + ')*'.freeze
QUALIFIER =
'(?:-(' + PARTS + '))?(?:\+(' + PARTS + '))?'.freeze
QUALIFIER_NC =
'(?:-' + PARTS + ')?(?:\+' + PARTS + ')?'.freeze
PARTIAL =
XR_NC + '(?:\.' + XR_NC + '(?:\.' + XR_NC + QUALIFIER_NC + ')?)?'.freeze
SIMPLE =

The ~> isn’t in the spec but allowed

'([<>=~^]|<=|>=|~>|~=)?(' + PARTIAL + ')'.freeze
SIMPLE_EXPR =
/\A#{SIMPLE}\z/.freeze
SIMPLE_WITH_EXTRA_WS =
'([<>=~^]|<=|>=)?\s+(' + PARTIAL + ')'.freeze
SIMPLE_WITH_EXTRA_WS_EXPR =
/\A#{SIMPLE_WITH_EXTRA_WS}\z/.freeze
HYPHEN =
'(' + PARTIAL + ')\s+-\s+(' + PARTIAL + ')'.freeze
HYPHEN_EXPR =
/\A#{HYPHEN}\z/.freeze
PARTIAL_EXPR =
/\A#{XR}(?:\.#{XR}(?:\.#{XR}#{QUALIFIER})?)?\z/.freeze
LOGICAL_OR =
/\s*\|\|\s*/.freeze
RANGE_SPLIT =
/\s+/.freeze
EMPTY_RANGE =

A range that matches no versions

VersionRange.new([], nil).freeze
ALL_RANGE =
VersionRange.new([AllRange::SINGLETON], '*')

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(from, to, exclude_end = false) ⇒ VersionRange #initialize(ranges, string) ⇒ VersionRange

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Creates a new version range

Overloads:

  • #initialize(from, to, exclude_end = false) ⇒ VersionRange

    Creates a new instance using ruby ‘Range` semantics

    Parameters:

    • begin (String, Version)

      the version denoting the start of the range (always inclusive)

    • end (String, Version)

      the version denoting the end of the range

    • exclude_end (Boolean) (defaults to: false)

      ‘true` if the `end` version should be excluded from the range

  • #initialize(ranges, string) ⇒ VersionRange

    Creates a new instance based on parsed content. For internal use only

    Parameters:

    • ranges (Array<AbstractRange>)

      the ranges to include in this range

    • string (String)

      the original string representation that was parsed to produce the ranges

    • strict_semver (Boolean)

      ‘false` if pre-releases should be included even when not explicitly appointed



226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
# File 'lib/puppet/vendor/semantic_puppet/lib/semantic_puppet/version_range.rb', line 226

def initialize(ranges, string, exclude_end = nil)
  if ranges.is_a?(Array)
    @strict_semver = exclude_end.nil? ? true : exclude_end
  else
    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)]
    @strict_semver = true
  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

Instance Attribute Details

#rangesObject (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Provides read access to the ranges. For internal use only



211
212
213
# File 'lib/puppet/vendor/semantic_puppet/lib/semantic_puppet/version_range.rb', line 211

def ranges
  @ranges
end

Class Method Details

.parse(range_string, strict_semver = true) ⇒ VersionRange

Parses a version range string into a comparable SemanticPuppet::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”`

Parameters:

  • range_string (String)

    the version range string to parse

  • strict_semver (Boolean) (defaults to: true)

    ‘false` if pre-releases should be included even when not explicitly appointed

Returns:



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/puppet/vendor/semantic_puppet/lib/semantic_puppet/version_range.rb', line 65

def self.parse(range_string, strict_semver = true)
  # 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, strict_semver).freeze
end

Instance Method Details

#beginVersion

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.

Returns:

  • (Version)

    the beginning of the range, or ‘nil` if there are multiple beginnings



287
288
289
# File 'lib/puppet/vendor/semantic_puppet/lib/semantic_puppet/version_range.rb', line 287

def begin
  @ranges.size == 1 ? @ranges[0].begin : nil
end

#endVersion

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.

Returns:

  • (Version)

    the end of the range, or ‘nil` if there are multiple ends



298
299
300
# File 'lib/puppet/vendor/semantic_puppet/lib/semantic_puppet/version_range.rb', line 298

def end
  @ranges.size == 1 ? @ranges[0].end : nil
end

#eql?(range) ⇒ Boolean Also known as: ==

Returns:

  • (Boolean)


271
272
273
# File 'lib/puppet/vendor/semantic_puppet/lib/semantic_puppet/version_range.rb', line 271

def eql?(range)
  range.is_a?(VersionRange) && @ranges.eql?(range.ranges)
end

#exclude_begin?Boolean

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.

Returns:

  • (Boolean)

    ‘true` if the beginning is excluded from the range, `false` if included, or `nil` if there are multiple beginnings



309
310
311
# File 'lib/puppet/vendor/semantic_puppet/lib/semantic_puppet/version_range.rb', line 309

def exclude_begin?
  @ranges.size == 1 ? @ranges[0].exclude_begin? : nil
end

#exclude_end?Boolean

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.

Returns:

  • (Boolean)

    ‘true` if the end is excluded from the range, `false` if not, or `nil` if there are multiple ends



320
321
322
# File 'lib/puppet/vendor/semantic_puppet/lib/semantic_puppet/version_range.rb', line 320

def exclude_end?
  @ranges.size == 1 ? @ranges[0].exclude_end? : nil
end

#hashObject



276
277
278
# File 'lib/puppet/vendor/semantic_puppet/lib/semantic_puppet/version_range.rb', line 276

def hash
  @ranges.hash
end

#include?(version) ⇒ Boolean Also known as: member?, cover?, ===

Returns ‘true` if the given version is included in the range.

Returns:

  • (Boolean)

    ‘true` if the given version is included in the range



326
327
328
329
330
331
332
# File 'lib/puppet/vendor/semantic_puppet/lib/semantic_puppet/version_range.rb', line 326

def include?(version)
  if @strict_semver
    @ranges.any? { |range| range.include?(version) && (version.stable? || range.test_prerelease?(version)) }
  else
    @ranges.any? { |range| range.include?(version) || !version.stable? && range.stable? &&  range.include?(version.to_stable) }
  end
end

#inspectString

Returns a canonical string representation of this range, assembled from the internal matchers.

Returns:

  • (String)

    a range expression representing this VersionRange



366
367
368
# File 'lib/puppet/vendor/semantic_puppet/lib/semantic_puppet/version_range.rb', line 366

def inspect
  @ranges.join(' || ')
end

#intersection(other) ⇒ VersionRange Also known as: &

Computes the intersection of a pair of ranges. If the ranges have no useful intersection, an empty range is returned.

Parameters:

Returns:

Raises:

  • (ArgumentError)


343
344
345
346
347
348
349
# File 'lib/puppet/vendor/semantic_puppet/lib/semantic_puppet/version_range.rb', line 343

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

#to_sString

Returns a string representation of this range. This will be the string that was used when the range was parsed.

Returns:

  • (String)

    a range expression representing this VersionRange



357
358
359
# File 'lib/puppet/vendor/semantic_puppet/lib/semantic_puppet/version_range.rb', line 357

def to_s
  @string
end