brew/Library/Homebrew/version.rb
Jack Nagel c924de7086 Version: build-in devel version comparisons
The heuristic used by the default version comparison is simple. A
version string is scanned for strings of digits, split into an array of
these strings, and then an element-wise comparison is done.

This fails when presented with something like

  Version.new("1.0.0beta7") <=> Version.new("1.0.0")

because the first three digits match, and the fourth digit of the
receiver (7) is greater than the assumed fourth digit of the parameter
(0).

Fix this by defining an element-wise comparator on a new VersionElement
class. This allows us to correctly compare "alpha", "beta", and "rc"
style version strings, and keeps the logic out of the main version
comparison.

Signed-off-by: Jack Nagel <jacknagel@gmail.com>
2012-08-18 15:53:04 -05:00

227 lines
5.2 KiB
Ruby

class VersionElement
include Comparable
attr_reader :elem
def initialize elem
elem = elem.to_s.downcase
@elem = case elem
when 'a', 'alpha' then 'alpha'
when 'b', 'beta' then 'beta'
when /\d+/ then elem.to_i
else elem
end
end
def <=>(other)
return unless other.is_a? VersionElement
return -1 if string? and other.numeric?
return 1 if numeric? and other.string?
return elem <=> other.elem
end
def to_s
@elem.to_s
end
def string?
@elem.is_a? String
end
def numeric?
@elem.is_a? Numeric
end
end
class Version
include Comparable
def initialize val, detected=false
return val if val.is_a? Version or val.nil?
@version = val.to_s
@detected_from_url = detected
end
def detected_from_url?
@detected_from_url
end
def to_a
@array ||= @version.scan(/\d+|[a-zA-Z]+/).map { |e| VersionElement.new(e) }
end
def head?
@version == 'HEAD'
end
def devel?
alpha? or beta? or rc?
end
def alpha?
to_a.any? { |e| e.to_s == 'alpha' }
end
def beta?
to_a.any? { |e| e.to_s == 'beta' }
end
def rc?
to_a.any? { |e| e.to_s == 'rc' }
end
def <=>(other)
# Return nil if objects aren't comparable
return unless other.is_a? Version
# Versions are equal if both are HEAD
return 0 if head? and other.head?
# HEAD is greater than any numerical version
return 1 if head? and not other.head?
return -1 if not head? and other.head?
stuple, otuple = to_a, other.to_a
max = [stuple.length, otuple.length].max
stuple.fill(VersionElement.new(0), stuple.length, max - stuple.length)
otuple.fill(VersionElement.new(0), otuple.length, max - otuple.length)
stuple <=> otuple
end
def to_s
@version
end
alias_method :to_str, :to_s
def self.parse spec
version = _parse(spec)
Version.new(version, true) unless version.nil?
end
private
def self._parse spec
spec = Pathname.new(spec) unless spec.is_a? Pathname
stem = if spec.directory?
spec.basename.to_s
elsif %r[((?:sourceforge.net|sf.net)/.*)/download$].match(spec.to_s)
Pathname.new(spec.dirname).stem
else
spec.stem
end
# GitHub tarballs, e.g. v1.2.3
m = %r[github.com/.+/(?:zip|tar)ball/v?((\d+\.)+\d+)$].match(spec.to_s)
return m.captures.first unless m.nil?
# e.g. https://github.com/sam-github/libnet/tarball/libnet-1.1.4
m = %r[github.com/.+/(?:zip|tar)ball/.*-((\d+\.)+\d+)$].match(spec.to_s)
return m.captures.first unless m.nil?
# e.g. https://github.com/isaacs/npm/tarball/v0.2.5-1
m = %r[github.com/.+/(?:zip|tar)ball/v?((\d+\.)+\d+-(\d+))$].match(spec.to_s)
return m.captures.first unless m.nil?
# e.g. https://github.com/petdance/ack/tarball/1.93_02
m = %r[github.com/.+/(?:zip|tar)ball/v?((\d+\.)+\d+_(\d+))$].match(spec.to_s)
return m.captures.first unless m.nil?
# e.g. boost_1_39_0
m = /((\d+_)+\d+)$/.match(stem)
return m.captures.first.gsub('_', '.') unless m.nil?
# e.g. foobar-4.5.1-1
# e.g. ruby-1.9.1-p243
m = /-((\d+\.)*\d\.\d+-(p|rc|RC)?\d+)$/.match(stem)
return m.captures.first unless m.nil?
# e.g. lame-398-1
m = /-((\d)+-\d)/.match(stem)
return m.captures.first unless m.nil?
# e.g. foobar-4.5.1
m = /-((\d+\.)*\d+)$/.match(stem)
return m.captures.first unless m.nil?
# e.g. foobar-4.5.1b
m = /-((\d+\.)*\d+([abc]|rc|RC)\d*)$/.match(stem)
return m.captures.first unless m.nil?
# e.g. foobar-4.5.0-beta1, or foobar-4.50-beta
m = /-((\d+\.)*\d+-beta(\d+)?)$/.match(stem)
return m.captures.first unless m.nil?
# e.g. foobar4.5.1
m = /((\d+\.)*\d+)$/.match(stem)
return m.captures.first unless m.nil?
# e.g. foobar-4.5.0-bin
m = /-((\d+\.)+\d+[abc]?)[-._](bin|dist|stable|src|sources?)$/.match(stem)
return m.captures.first unless m.nil?
# e.g. dash_0.5.5.1.orig.tar.gz (Debian style)
m = /_((\d+\.)+\d+[abc]?)[.]orig$/.match(stem)
return m.captures.first unless m.nil?
# e.g. erlang-R14B03-bottle.tar.gz (old erlang bottle style)
m = /-([^-]+)/.match(stem)
return m.captures.first unless m.nil?
# e.g. opt_src_R13B (erlang)
m = /otp_src_(.+)/.match(stem)
return m.captures.first unless m.nil?
# e.g. astyle_1.23_macosx.tar.gz
m = /_([^_]+)/.match(stem)
return m.captures.first unless m.nil?
end
# DSL for defining comparators
class << self
def compare &blk
send(:define_method, '<=>', &blk)
end
end
end
class VersionSchemeDetector
def initialize scheme
@scheme = scheme
end
def detect
if @scheme.is_a? Class and @scheme.ancestors.include? Version
@scheme
elsif @scheme.is_a? Symbol then detect_from_symbol
else
raise "Unknown version scheme #{@scheme} was requested."
end
end
private
def detect_from_symbol
raise "Unknown version scheme #{@scheme} was requested."
end
end
# Enable things like "MacOS.version >= :lion"
class MacOSVersion < Version
compare do |other|
case other
when Symbol, Fixnum, Float, Version
super Version.new case other
when :mountain_lion then 10.8
when :lion then 10.7
when :snow_leopard then 10.6
when :leopard then 10.5
else other
end
else
nil
end
end
end