Package: libdpkg-ruby1.8
Version: 0.3.2
Severity: wishlist

Attached is an implementation of a DebianVersion class, along with a test
suite.  Please consider including it in the next release of libdpkg-ruby1.8.

Thanks,
- Matt
#
# debian_version.rb
#   Ruby class to parse and compare Debian version strings.
#   Copyright (C) 2007 Matthew Palmer <[EMAIL PROTECTED]>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
#

class DebianVersion
        include Comparable
        
        attr_reader :epoch, :upstream, :debian
        
        def initialize(val)
                if val.is_a? DebianVersion
                        @epoch = val.epoch
                        @upstream = val.upstream
                        @debian = val.debian
                        return
                end
                
                begin
                        matches = 
val.match(/^(?:(\d+):)?(\d[0-9a-zA-Z.+:-]*?)(?:-([0-9a-zA-Z+.~]+))?$/)
                rescue NoMethodError
                        raise TypeError, "Cannot match on value passed to 
DebianVersion.new (#{val.inspect})"
                end

                if matches.nil?
                        raise RuntimeError, "'#{val}' doesn't look like a 
version string"
                end
                
                @epoch = matches[1].to_i
                @upstream = matches[2].to_s
                @debian = matches[3].to_s
        end
        
        def <=>(other)
                if other.class != DebianVersion
                        other = DebianVersion.new(other)
                end

                rv = @epoch <=> other.epoch
                return rv if rv != 0

                rv = vercmp(@upstream, other.upstream)
                return rv if rv != 0
                
                return vercmp(@debian, other.debian)
        end

        private
        def vercmp(left, right)
                a = left.dup
                b = right.dup
                digitregex = /^([0-9]*)(.*)$/
                nondigitregex = /^([^0-9]*)(.*)$/
                
                digits = true
                while !a.empty? or !b.empty?
                        re = digits ? digitregex : nondigitregex
                        suba, a = substring(a, re)
                        subb, b = substring(b, re)

                        if digits
                                suba = suba.to_i
                                subb = subb.to_i
                                rv = suba <=> subb
                                return rv if rv != 0
                        else
                                rv = strvercmp(suba, subb)
                                return rv if rv != 0
                        end
                        
                        digits = !digits
                end
                
                return 0
        end

        def strvercmp(a, b)
                len = [a.length, b.length].max
                
                debver_array(a, len) <=> debver_array(b, len)
        end
        
        # Turns a string into an array of numeric values kind-of corresponding 
to
        # the ASCII numeric values of the characters in the string.  I say 
'kind-of'
        # because any character which is not an alphabetic character will be
        # it's ASCII value + 256, and the tilde (~) character will have the 
value
        # -1.
        #
        # Additionally, the +len+ parameter specifies how long the array needs 
to
        # be; any elements in the array beyond the length of the string will be 
0.
        #
        # This method has massive ASCII assumptions.  Use with caution.
        def debver_array(s, len)
                a = Array.new(len, 0)
                
                i = 0
                s.each_byte do |b|
                        if (b >= ?a and b <= ?z) or (b >= ?A and b <= ?Z)
                                a[i] = b
                        elsif b == '~'[0]
                                a[i] = -1
                        else
                                a[i] = b + 256
                        end
                        i += 1
                end
                
                a
        end

        # Runs the regex +re+ over the string in +x+ and returns a two element
        # array consisting of the first two subelements of the regex.
        def substring(x, re)
                m = x.match(re)
                if m.nil?
                        return ['', x]
                end
                
                if m[1].nil?
                        return ['', x]
                end
                if m[2].nil?
                        return [x, '']
                end
                
                return [m[1], m[2]]
        end
end
#   Copyright (C) 2007 Matthew Palmer <[EMAIL PROTECTED]>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
#

require 'test/unit'

require 'debian_version'

class DebianVersionTest < Test::Unit::TestCase
        def test_debver_array
                testcases = [
                             ['abcxyz', 6, ['a'[0], 'b'[0], 'c'[0], 'x'[0], 
'y'[0], 'z'[0]]],
                             ['ab', 4, ['a'[0], 'b'[0], 0, 0]],
                             ['a~b', 3, ['a'[0], -1, 'b'[0]]],
                             ['a+b', 3, ['a'[0], '+'[0]+256, 'b'[0]]]
                            ]
                
                dv = DebianVersion.new('1')
                # Pop open dv so we can poke at it's juicy innards
                class << dv; public :debver_array; end
                
                testcases.each do |t|
                        assert_equal t[2], dv.debver_array(t[0], t[1])
                end
        end

        def test_initialize
                testcases = [
                             ['1', 0, '1', ''],
                             ['1.0.1', 0, '1.0.1', ''],
                             ['1.0-1', 0, '1.0', '1'],
                             ['1:1.0-1', 1, '1.0', '1'],
                             ['2:3.4-5.6-7ubuntu8', 2, '3.4-5.6', '7ubuntu8']
                            ]
                
                testcases.each do |t|
                        o = DebianVersion.new(t[0])
                        
                        assert_equal t[1], o.epoch, "Epoch doesn't match for 
#{t[0]}"
                        assert_equal t[2], o.upstream, "Upstream version 
doesn't match for #{t[0]}"
                        assert_equal t[3], o.debian, "Debian version doesn't 
match for #{t[0]}"
                end
        end

        def test_spaceship
                # In each testcase, we have two version strings, and then the 
expected
                # result -- -1 if the first is less than the second; 1 if the 
first
                # is greater than the second, and 0 if they're equivalent.
                testcases = [
                             ['1', '2', -1],
                             ['1', '1', 0],
                             ['2', '1', 1],
                             ['1.0', '1.1', -1],
                             ['1.2.3', '1.2.1', 1],
                             ['1.0.0.1', '1.0.0.1', 0],
                             ['1.0', '1.0-0', 0],
                             ['1.0-1', '1.0-0', 1],
                             ['1.0-1', '1.0-0.1', 1],
                             ['1.0', '1:0.1', -1],
                             ['1.0beta1', '1.0', 1],
                             ['1.0beta1', '1.0-1', 1],
                             ['1.0', '1.0-1', -1],
                             ['1.0-1bpo1', '1.0-1', 1],
                             ['1.0-1bpo1', '1.0-1.1', -1],
                             ['1.0-1', '1.0-1~sarge1', 1]
                            ]
                
                testcases.each do |e|
                        assert_equal e[2], DebianVersion.new(e[0]) <=> e[1],
                      "#{e[0]} <=> #{e[1]} => #{e[2]}"
                end
        end
end

Reply via email to