Class: SemanticPuppet::VersionRange

Inherits:
Object
  • Object
show all
Defined in:
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



224
225
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
# 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

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



210
211
212
# File 'lib/semantic_puppet/version_range.rb', line 210

def ranges
  @ranges
end

Class Method Details

.parse(range_string) ⇒ 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

Returns:



64
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
# 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

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



282
283
284
# File 'lib/semantic_puppet/version_range.rb', line 282

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



293
294
295
# File 'lib/semantic_puppet/version_range.rb', line 293

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

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

Returns:

  • (Boolean)


266
267
268
# File 'lib/semantic_puppet/version_range.rb', line 266

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



304
305
306
# File 'lib/semantic_puppet/version_range.rb', line 304

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



315
316
317
# File 'lib/semantic_puppet/version_range.rb', line 315

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

#hashObject



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

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



321
322
323
# 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

#inspectString

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

Returns:

  • (String)

    a range expression representing this VersionRange



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

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)


334
335
336
337
338
339
340
# 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

#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



348
349
350
# File 'lib/semantic_puppet/version_range.rb', line 348

def to_s
  @string
end