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