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