class VersionElement include Comparable 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 protected attr_reader :elem def string? elem.is_a? String end def numeric? elem.is_a? Numeric end end class Version include Comparable def initialize val, detected=false @version = val.to_s @detected_from_url = detected end def detected_from_url? @detected_from_url 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 protected def to_a @array ||= @version.scan(/\d+|[a-zA-Z]+/).map { |e| VersionElement.new(e) } 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. https://github.com/erlang/otp/tarball/OTP_R15B01 (erlang style) m = /[-_](R\d+[AB]\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+)(?:[-._](?:bin|dist|stable|src|sources))?$/.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. http://www.openssl.org/source/openssl-0.9.8s.tar.gz m = /-([^-]+)/.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? # e.g. http://mirrors.jenkins-ci.org/war/1.486/jenkins.war m = /\/(\d\.\d+)\//.match(spec.to_s) 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| 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.to_s end end end