# vim: set sts=2 sw=2 ts=8 et:1#2# Copyright (c) 2001-2004 Akinori MUSHA <[email protected]>3# Copyright (c) 2006-2008 Sergey Matveychuk <[email protected]>4# Copyright (c) 2009-2012 Stanislav Sedov <[email protected]>5#6# All rights reserved.7#8# Redistribution and use in source and binary forms, with or without9# modification, are permitted provided that the following conditions10# are met:11# 1. Redistributions of source code must retain the above copyright12# notice, this list of conditions and the following disclaimer.13# 2. Redistributions in binary form must reproduce the above copyright14# notice, this list of conditions and the following disclaimer in the15# documentation and/or other materials provided with the distribution.16#17# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND18# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE19# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE20# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE21# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL22# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS23# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)24# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT25# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY26# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF27# SUCH DAMAGE.28#2930class PkgVersion31include Comparable3233attr_accessor :version, :revision, :epoch3435def initialize(pkgversion)36if /[\s-]/ =~ pkgversion37raise ArgumentError, "#{pkgversion}: Must not contain a '-' or whitespace."38end3940if /^([^_,]+)(?:_(\d+))?(?:,(\d+))?$/ !~ pkgversion41raise ArgumentError, "#{pkgversion}: Not in due form: '<version>[_<revision>][,<epoch>]'."42end4344@version = $145@revision = $2 ? $2.to_i : 046@epoch = $3 ? $3.to_i : 047end4849def to_s50s = @version51s += '_' + @revision.to_s if @revision.nonzero?52s += ',' + @epoch.to_s if @epoch.nonzero?5354s55end5657def coerce(other)58case other59when PkgVersion60return other, self61when String62return PkgVersion.new(other), self63else64raise TypeError, "Coercion between #{other.class} and #{self.class} is not supported."65end66end6768def <=>(other)69case other70when PkgVersion71# ok72when String73other = PkgVersion.new(other)74else75a, b = other.coerce(self)7677return a <=> b78end7980(@epoch <=> other.epoch).nonzero? ||81PkgVersion.compare_numbers(@version, other.version).nonzero? ||82@revision <=> other.revision83end8485def PkgVersion::compare_numbers(n1, n2)86# For full comparing rules see file:87# /usr/src/usr.sbin/pkg_install/lib/version.c88special = { 'pl' => 'pl', 'alpha' => 'a', 'beta' => 'b',89'pre' => 'p', 'rc' => 'r' }9091n1 ||= ''92n2 ||= ''9394# Remove padded 0's95n1 = n1.gsub(/(^|\D)0+(\d)/, "\\1\\2")96n2 = n2.gsub(/(^|\D)0+(\d)/, "\\1\\2")9798# Short-cut in case of equality99if n1 == n2100return 0101end102103# For versions seperated with '+': e.g. 1.0.1+2004.09.06104# split them and compare by pairs105if /\+/ =~ n1 || /\+/ =~ n2106a1 = n1.split(/\+/)107a2 = n2.split(/\+/)108c = compare_numbers(a1.shift, a2.shift)109if c != 0110return c111else112return compare_numbers(a1.shift, a2.shift)113end114end115116# Add separators before specials117for s in special.keys118n1 = n1.gsub(/(#{s})/, ".#{special[s]}")119n2 = n2.gsub(/(#{s})/, ".#{special[s]}")120end121122# Add missed separators.123n1 = n1.gsub(/([a-zA-Z]\d)([a-zA-Z])/, "\\1.\\2");124n2 = n2.gsub(/([a-zA-Z]\d)([a-zA-Z])/, "\\1.\\2");125126# Collaps consecutive separators127n1 = n1.gsub(/([^a-zA-Z\d])+/, "\\1");128n2 = n2.gsub(/([^a-zA-Z\d])+/, "\\1");129130# Split into subnumbers131a1 = n1.split(/[^a-zA-Z\d]/)132a2 = n2.split(/[^a-zA-Z\d]/)133134s1 = nil135s2 = nil136137# Look for first different subnumber138begin139break if a1.empty? && a2.empty?140141s1 = a1.shift142s2 = a2.shift143# Magic for missing '0's: 1.0 == 1.0.0144s1 ||= '0'145s2 ||= '0'146end while s1 == s2147148# Short-cut in case of equality149if s1 == s2150return 0151end152153# Split into sub-subnumbers154a1 = s1.split(/(\D+)/)155a2 = s2.split(/(\D+)/)156157# If a string starts with a splitter, split() returns158# an empty first element. Adjust it.159if /^\D/ =~ s1160a1.shift161end162if /^\D/ =~ s2163a2.shift164end165166# Look for first different sub-subnumber167begin168break if a1.empty? && a2.empty?169170x1 = a1.shift171x2 = a2.shift172end while x1 == x2173174x1 ||= ''175x2 ||= ''176177# Short-cut in case of equality178if x1 == x2179return 0180end181182# Specail case - pl. It always lose.183if x1 == "pl"184return -1185elsif x2 == "pl"186return 1187end188189if /^\D/ =~ x1 # x1: non-number190if x2.empty? # vs. x2: null (5.0a > 5.0.b)191return 1 # -> x1 wins192end193if /^\D/ !~ x2 # vs. x2: number194return -1 # -> x2 wins195end196# vs. x2: non-number197return x1 <=> x2 # -> Compare in dictionary order198end199200if /^\d/ =~ x1 # x1: number201if /^\d/ =~ x2 # vs. x2: number202return x1.to_i <=> x2.to_i # -> Compare numerically203end204# vs. x2: non-number205return 1 # -> x1 wins206end207# x1: null (4a < 40a)208return -1 # -> x2 wins209end210end211212213