timcolonel/wow

View on GitHub
lib/wow/package/version_range.rb

Summary

Maintainability
A
30 mins
Test Coverage
require 'wow'
require 'wow/package/version'
require 'wow/package/version_range/patterns'

# Version Range
class Wow::Package::VersionRange
  include Wow::Package::VersionRange::Patterns
  # Range lower bound
  attr_accessor :lower_bound

  # Range upper bound, nil for none
  attr_accessor :upper_bound

  # Include hash Default: {lower_bound: true, upper_bound: false}
  attr_accessor :include

  # Create a new VersionRange
  # @param value [Version|String]
  # @param lower_bound [Version]
  # @param upper_bound [Version]
  # ```
  # version = Version.parse('1.2.3')
  # # The following are equivalents
  # VersionRange.new('1.2.3')
  # VersionRange.new(version)
  # VersionRange.new(lower_bound: version, upper_bound: version)
  #
  # # The upper bound can be omitted then any version over the lower bound will work
  # VersionRange.new(lower_bound: '1.2.3')
  # ```
  def initialize(value = nil, lower_bound: nil, upper_bound: nil, include: {})
    @include = default_include.merge(include)
    if value.nil?
      @lower_bound = lower_bound || Wow::Package::Version.zero
      @upper_bound = upper_bound
    elsif value.is_a? Wow::Package::Version
      @lower_bound = @upper_bound = value
    else
      @lower_bound = Wow::Package::Version.zero
      parse(value)
    end
  end

  def default_include
    {lower_bound: true, upper_bound: false}
  end

  def include_lower_bound?
    @include[:lower_bound]
  end

  def include_upper_bound?
    @include[:upper_bound]
  end

  def self.parse(str)
    new(str)
  end

  # Parse a version range
  # @param str [String]
  # @return [Wow::Package::VersionRange]
  def parse(str)
    parts = str.split(',')
    parts.each do |part|
      range = self.class.parse_part(part)
      merge!(range)
    end
  end

  def self.parse_part(part)
    @patterns.each do |_name, regex, block|
      next unless regex =~ part.squeeze(' ').strip
      value = Wow::Package::Version.parse(Regexp.last_match[1], true)
      return block.call(value)
    end
  end

  # Merge with another version range.
  # Modify method
  # @param other [Wow::Package::VersionRange]
  def merge!(other)
    if other.lower_bound > @lower_bound
      @lower_bound = other.lower_bound
      @include[:lower_bound] = other.include_lower_bound?
    end
    if other.upper_bound
      if @upper_bound.nil? || other.upper_bound < @upper_bound
        @upper_bound = other.upper_bound
        @include[:upper_bound] = other.include_upper_bound?
      end
    end
    self
  end

  def merge(other)
    clone.merge!(other)
  end

  # Test if the given version match the range
  def match?(version)
    return false if version < lower_bound
    return include_lower_bound? if version == lower_bound
    return true if upper_bound.nil?
    return false if version > upper_bound
    return include_upper_bound? if version == upper_bound
    true
  end

  alias_method :include?, :match?

  # Return a new range that accept any of the range
  # @return [Wow::Package::VersionRange]
  def self.any
    version = Wow::Package::Version.new(major: 0, minor: 0, patch: 0, stage: :alpha)
    Wow::Package::VersionRange.new(lower_bound: version)
  end

  def ==(other)
    return false unless other.is_a? Wow::Package::VersionRange
    @upper_bound == other.upper_bound &&
      @lower_bound == other.lower_bound &&
      @include == other.include
  end

  def to_s
    if upper_bound
      "#{lower_bound} - #{upper_bound}"
    else
      ">= #{lower_bound}"
    end
  end

  # Return if the range contains at least 1 version(lower_bound <= upper_bound)
  # @return [Boolean]
  def any?
    !empty?
  end

  # Return if the range cannot contain a version(lower_bound > upper_bound)
  # @return [Boolean]
  def empty?
    return false if @upper_bound.nil?
    @lower_bound > @upper_bound
  end
end