class SemanticPuppet::VersionRange

Constants

EMPTY_RANGE

A range that matches no versions

NOT_A_VERSION_RANGE

Public Class Methods

parse(range_str) click to toggle source

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”`

  • Inequalities

    • ex. `“> 1.0.0”`, `“<3.2.0”`, `“>=4.0.0”`

  • Approximate 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”`

@param range_str [String] the version range string to parse @return [VersionRange] a new {VersionRange} instance

# File lib/semantic_puppet/version_range.rb, line 26
def parse(range_str)
  partial = '\d+(?:[.]\d+)?(?:[.][x]|[.]\d+(?:[-][0-9a-z.-]*)?)?'
  exact   = '\d+[.]\d+[.]\d+(?:[-][0-9a-z.-]*)?'

  range = range_str.gsub(/([(><=~])[ ]+/, '\1')
  range = range.gsub(/ - /, '#').strip

  return case range
  when /\A(#{partial})\Z/i
    parse_loose_version_expression($1)
  when /\A([><][=]?)(#{exact})\Z/i
    parse_inequality_expression($1, $2)
  when /\A~(#{partial})\Z/i
    parse_reasonably_close_expression($1)
  when /\A(#{exact})#(#{exact})\Z/i
    parse_inclusive_range_expression($1, $2)
  when /[ ]+/
    parse_intersection_expression(range)
  else
    raise ArgumentError
  end

rescue ArgumentError
  raise ArgumentError, "Unparsable version range: #{range_str.inspect}"
end

Private Class Methods

parse_gt_expression(expr) click to toggle source

Returns a range covering all versions greater than the given `expr`.

@param expr [String] the version to be greater than @return [VersionRange] a range covering all versions greater than the

given %x`expr`
# File lib/semantic_puppet/version_range.rb, line 119
def parse_gt_expression(expr)
  if expr =~ /^[^+]*-/
    start = Version.parse("#{expr}.0")
  else
    start = process_loose_expr(expr).last.send(:first_prerelease)
  end

  self.new(start, SemanticPuppet::Version::MAX)
end
parse_gte_expression(expr) click to toggle source

Returns a range covering all versions greater than or equal to the given `expr`.

@param expr [String] the version to be greater than or equal to @return [VersionRange] a range covering all versions greater than or

equal to the given %x`expr`
# File lib/semantic_puppet/version_range.rb, line 135
def parse_gte_expression(expr)
  if expr =~ /^[^+]*-/
    start = Version.parse(expr)
  else
    start = process_loose_expr(expr).first.send(:first_prerelease)
  end

  self.new(start, SemanticPuppet::Version::MAX)
end
parse_inclusive_range_expression(start, finish) click to toggle source

An “inclusive range” expression takes two version numbers (or partial version numbers) and creates a range that covers all versions between them. These take the form:

[Version] - [Version]

@param start [String] a “loose” expresssion for the start of the range @param finish [String] a “loose” expression for the end of the range @return [VersionRange] a {VersionRange} covering `start` to `finish`

# File lib/semantic_puppet/version_range.rb, line 224
def parse_inclusive_range_expression(start, finish)
  start, _ = process_loose_expr(start)
  _, finish = process_loose_expr(finish)

  start = start.send(:first_prerelease) if start.stable?
  if finish.stable?
    exclude = true
    finish = finish.send(:first_prerelease)
  end

  self.new(start, finish, exclude)
end
parse_inequality_expression(comp, expr) click to toggle source

Creates an open-ended version range from an inequality expression.

@overload ::parse_inequality_expression('<', expr)

{include:.parse_lt_expression}

@overload ::parse_inequality_expression('<=', expr)

{include:.parse_lte_expression}

@overload ::parse_inequality_expression('>', expr)

{include:.parse_gt_expression}

@overload ::parse_inequality_expression('>=', expr)

{include:.parse_gte_expression}

@param comp ['<', '<=', '>', '>='] an inequality operator @param expr [String] a “loose” version expression @return [VersionRange] a range covering all versions in the inequality

# File lib/semantic_puppet/version_range.rb, line 101
def parse_inequality_expression(comp, expr)
  case comp
  when '>'
    parse_gt_expression(expr)
  when '>='
    parse_gte_expression(expr)
  when '<'
    parse_lt_expression(expr)
  when '<='
    parse_lte_expression(expr)
  end
end
parse_intersection_expression(expr) click to toggle source

Creates a new {VersionRange} from a range intersection expression.

@param expr [String] a range intersection expression @return [VersionRange] a version range representing `expr`

# File lib/semantic_puppet/version_range.rb, line 58
def parse_intersection_expression(expr)
  expr.split(/[ ]+/).map { |x| parse(x) }.inject { |a,b| a & b }
end
parse_loose_version_expression(expr) click to toggle source

Creates a new {VersionRange} from a “loose” description of a Semantic Version number.

@see .process_loose_expr

@param expr [String] a “loose” version expression @return [VersionRange] a version range representing `expr`

# File lib/semantic_puppet/version_range.rb, line 69
def parse_loose_version_expression(expr)
  start, finish = process_loose_expr(expr)

  if start.stable?
    start = start.send(:first_prerelease)
  end

  if finish.stable?
    exclude = true
    finish = finish.send(:first_prerelease)
  end

  self.new(start, finish, exclude)
end
parse_lt_expression(expr) click to toggle source

Returns a range covering all versions less than the given `expr`.

@param expr [String] the version to be less than @return [VersionRange] a range covering all versions less than the

given %x`expr`
# File lib/semantic_puppet/version_range.rb, line 150
def parse_lt_expression(expr)
  if expr =~ /^[^+]*-/
    finish = Version.parse(expr)
  else
    finish = process_loose_expr(expr).first.send(:first_prerelease)
  end

  self.new(SemanticPuppet::Version::MIN, finish, true)
end
parse_lte_expression(expr) click to toggle source

Returns a range covering all versions less than or equal to the given `expr`.

@param expr [String] the version to be less than or equal to @return [VersionRange] a range covering all versions less than or equal

to the given %x`expr`
# File lib/semantic_puppet/version_range.rb, line 166
def parse_lte_expression(expr)
  if expr =~ /^[^+]*-/
    finish = Version.parse(expr)
    self.new(SemanticPuppet::Version::MIN, finish)
  else
    finish = process_loose_expr(expr).last.send(:first_prerelease)
    self.new(SemanticPuppet::Version::MIN, finish, true)
  end
end
parse_reasonably_close_expression(expr) click to toggle source

The “reasonably close” expression is used to designate ranges that have a reasonable proximity to the given “loose” version number. These take the form:

~[Version]

The general semantics of these expressions are that the given version forms a lower bound for the range, and the upper bound is either the next version number increment (at whatever precision the expression provides) or the next stable version (in the case of a prerelease version).

@example “Reasonably close” major version

"~1" # => (>=1.0.0 <2.0.0)

@example “Reasonably close” minor version

"~1.2" # => (>=1.2.0 <1.3.0)

@example “Reasonably close” patch version

"~1.2.3" # => (>=1.2.3 <1.3.0)

@example “Reasonably close” prerelease version

"~1.2.3-alpha" # => (>=1.2.3-alpha <1.2.4)

@param expr [String] a “loose” expression to build the range around @return [VersionRange] a “reasonably close” version range

# File lib/semantic_puppet/version_range.rb, line 199
def parse_reasonably_close_expression(expr)
  parsed, succ = process_loose_expr(expr)

  if parsed.stable?
    parsed = parsed.send(:first_prerelease)

    # Handle the special case of "~1.2.3" expressions.
    succ = succ.next(:minor) if ((parsed.major == succ.major) && (parsed.minor == succ.minor))

    succ = succ.send(:first_prerelease)
    self.new(parsed, succ, true)
  else
    self.new(parsed, succ.next(:patch).send(:first_prerelease), true)
  end
end
process_loose_expr(expr) click to toggle source

A “loose expression” is one that takes the form of all or part of a valid Semantic Version number. Particularly:

Various placeholders are also permitted in “loose expressions” (typically an 'x' or an asterisk).

This method parses these expressions into a minimal and maximal version number pair.

@todo Stabilize whether the second value is inclusive or exclusive

@param expr [String] a string containing a “loose” version expression @return [(VersionNumber, VersionNumber)] a minimal and maximal

version pair for the given expression
# File lib/semantic_puppet/version_range.rb, line 256
def process_loose_expr(expr)
  case expr
  when /^(\d+)(?:[.][xX*])?$/
    expr = "#{$1}.0.0"
    arity = :major
  when /^(\d+[.]\d+)(?:[.][xX*])?$/
    expr = "#{$1}.0"
    arity = :minor
  when /^\d+[.]\d+[.]\d+$/
    arity = :patch
  end

  version = next_version = Version.parse(expr)

  if arity
    next_version = version.next(arity)
  end

  [ version, next_version ]
end

Public Instance Methods

&(other)
Alias for: intersection
inspect()
Alias for: to_s
intersection(other) click to toggle source

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

# File lib/semantic_puppet/version_range.rb, line 283
def intersection(other)
  raise NOT_A_VERSION_RANGE unless other.kind_of?(VersionRange)

  if self.begin < other.begin
    return other.intersection(self)
  end

  unless include?(other.begin) || other.include?(self.begin)
    return EMPTY_RANGE
  end

  endpoint = ends_before?(other) ? self : other
  VersionRange.new(self.begin, endpoint.end, endpoint.exclude_end?)
end
Also aliased as: &
to_s() click to toggle source

Returns a string representation of this range, prefering simple common expressions for comprehension.

@return [String] a range expression representing this VersionRange

# File lib/semantic_puppet/version_range.rb, line 303
def to_s
  start, finish  = self.begin, self.end
  inclusive = exclude_end? ? '' : '='

  case
  when EMPTY_RANGE == self
    "<0.0.0"
  when exact_version?, patch_version?
    "#{ start }"
  when minor_version?
    "#{ start }".sub(/.0$/, '.x')
  when major_version?
    "#{ start }".sub(/.0.0$/, '.x')
  when open_end? && start.to_s =~ /-.*[.]0$/
    ">#{ start }".sub(/.0$/, '')
  when open_end?
    ">=#{ start }"
  when open_begin?
    "<#{ inclusive }#{ finish }"
  else
    ">=#{ start } <#{ inclusive }#{ finish }"
  end
end
Also aliased as: inspect

Private Instance Methods

ends_before?(other) click to toggle source

Determines whether this {VersionRange} has an earlier endpoint than the give `other` range.

@param other [VersionRange] the range to compare against @return [Boolean] true if the endpoint for this range is less than or

equal to the endpoint of the `other` range.
# File lib/semantic_puppet/version_range.rb, line 336
def ends_before?(other)
  self.end < other.end || (self.end == other.end && self.exclude_end?)
end
exact_version?() click to toggle source

Describes whether this range follows the patterns for matching all releases with the same exact version. @return [Boolean] true if this range matches only a single exact version

# File lib/semantic_puppet/version_range.rb, line 355
def exact_version?
  self.begin == self.end
end
major_version?() click to toggle source

Describes whether this range follows the patterns for matching all releases with the same major version. @return [Boolean] true if this range matches only a single major version

# File lib/semantic_puppet/version_range.rb, line 362
def major_version?
  start, finish = self.begin, self.end

  exclude_end? &&
  start.major.next == finish.major &&
  same_minor? && start.minor == 0 &&
  same_patch? && start.patch == 0 &&
  [start.prerelease, finish.prerelease] == ['', '']
end
minor_version?() click to toggle source

Describes whether this range follows the patterns for matching all releases with the same minor version. @return [Boolean] true if this range matches only a single minor version

# File lib/semantic_puppet/version_range.rb, line 375
def minor_version?
  start, finish = self.begin, self.end

  exclude_end? &&
  same_major? &&
  start.minor.next == finish.minor &&
  same_patch? && start.patch == 0 &&
  [start.prerelease, finish.prerelease] == ['', '']
end
open_begin?() click to toggle source

Describes whether this range has a lower limit. @return [Boolean] true if this range has no lower limit

# File lib/semantic_puppet/version_range.rb, line 348
def open_begin?
  self.begin == SemanticPuppet::Version::MIN
end
open_end?() click to toggle source

Describes whether this range has an upper limit. @return [Boolean] true if this range has no upper limit

# File lib/semantic_puppet/version_range.rb, line 342
def open_end?
  self.end == SemanticPuppet::Version::MAX
end
patch_version?() click to toggle source

Describes whether this range follows the patterns for matching all releases with the same patch version. @return [Boolean] true if this range matches only a single patch version

# File lib/semantic_puppet/version_range.rb, line 388
def patch_version?
  start, finish = self.begin, self.end

  exclude_end? &&
  same_major? &&
  same_minor? &&
  start.patch.next == finish.patch &&
  [start.prerelease, finish.prerelease] == ['', '']
end
same_major?() click to toggle source

@return [Boolean] true if `begin` and `end` share the same major verion

# File lib/semantic_puppet/version_range.rb, line 399
def same_major?
  self.begin.major == self.end.major
end
same_minor?() click to toggle source

@return [Boolean] true if `begin` and `end` share the same minor verion

# File lib/semantic_puppet/version_range.rb, line 404
def same_minor?
  self.begin.minor == self.end.minor
end
same_patch?() click to toggle source

@return [Boolean] true if `begin` and `end` share the same patch verion

# File lib/semantic_puppet/version_range.rb, line 409
def same_patch?
  self.begin.patch == self.end.patch
end