1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175
|
module Puppet::Util::Package::Version
class Debian < Numeric
include Comparable
# Version string matching regexes
REGEX_EPOCH = '(?:([0-9]+):)?'
# alphanumerics and the characters . + - ~ , starts with a digit, ~ only of debian_revision is present
REGEX_UPSTREAM_VERSION = '([\.\+~0-9a-zA-Z-]+?)'
#alphanumerics and the characters + . ~
REGEX_DEBIAN_REVISION = '(?:-([\.\+~0-9a-zA-Z]*))?'
REGEX_FULL = REGEX_EPOCH + REGEX_UPSTREAM_VERSION + REGEX_DEBIAN_REVISION.freeze
REGEX_FULL_RX = /\A#{REGEX_FULL}\Z/
class ValidationFailure < ArgumentError; end
def self.parse(ver)
raise ValidationFailure, "Unable to parse '#{ver}' as a string" unless ver.is_a?(String)
match, epoch, upstream_version, debian_revision = *ver.match(REGEX_FULL_RX)
raise ValidationFailure, "Unable to parse '#{ver}' as a debian version identifier" unless match
new(epoch.to_i, upstream_version, debian_revision).freeze
end
def to_s
s = @upstream_version
s = "#{@epoch}:#{s}" if @epoch != 0
s = "#{s}-#{@debian_revision}" if @debian_revision
s
end
alias inspect to_s
def eql?(other)
other.is_a?(self.class) &&
@epoch.eql?(other.epoch) &&
@upstream_version.eql?(other.upstream_version) &&
@debian_revision.eql?(other.debian_revision)
end
alias == eql?
def <=>(other)
return nil unless other.is_a?(self.class)
cmp = @epoch <=> other.epoch
if cmp == 0
cmp = compare_upstream_version(other)
if cmp == 0
cmp = compare_debian_revision(other)
end
end
cmp
end
attr_reader :epoch, :upstream_version, :debian_revision
private
def initialize(epoch, upstream_version, debian_revision)
@epoch = epoch
@upstream_version = upstream_version
@debian_revision = debian_revision
end
def compare_upstream_version(other)
mine = @upstream_version
yours = other.upstream_version
compare_debian_versions(mine, yours)
end
def compare_debian_revision(other)
mine = @debian_revision
yours = other.debian_revision
compare_debian_versions(mine, yours)
end
def compare_debian_versions(mine, yours)
# First the initial part of each string consisting entirely of non-digit characters is determined.
# These two parts (one of which may be empty) are compared lexically. If a difference is found it is
# returned. The lexical comparison is a comparison of ASCII values modified so that all the letters
# sort earlier than all the non-letters and so that a tilde sorts before anything, even the end of a
# part. For example, the following parts are in sorted order from earliest to latest: ~~, ~~a, ~, the
# empty part, a.
#
# Then the initial part of the remainder of each string which consists entirely of digit characters
# is determined. The numerical values of these two parts are compared, and any difference found is
# returned as the result of the comparison. For these purposes an empty string (which can only occur
# at the end of one or both version strings being compared) counts as zero.
#
# These two steps (comparing and removing initial non-digit strings and initial digit strings) are
# repeated until a difference is found or both strings are exhausted.
mine_index = 0
yours_index = 0
cmp = 0
mine ||= ''
yours ||= ''
while mine_index < mine.length && yours_index < yours.length && cmp == 0
#handle ~
_mymatch, mytilde = *match_tildes(mine.slice(mine_index..-1))
mytilde ||= ''
_yoursmatch, yourstilde = *match_tildes(yours.slice(yours_index..-1))
yourstilde ||= ''
cmp = -1 * (mytilde.length <=> yourstilde.length)
mine_index += mytilde.length
yours_index += yourstilde.length
if cmp == 0 # handle letters
_mymatch, myletters = *match_letters(mine.slice(mine_index..-1))
myletters ||= ''
_yoursmatch, yoursletters = *match_letters(yours.slice(yours_index..-1))
yoursletters ||= ''
cmp = myletters <=> yoursletters
mine_index += myletters.length
yours_index += yoursletters.length
if cmp == 0 # handle nonletters except tilde
_mymatch, mynon_letters = *match_non_letters(mine.slice(mine_index..-1))
mynon_letters ||= ''
_yoursmatch, yoursnon_letters = *match_non_letters(yours.slice(yours_index..-1))
yoursnon_letters ||= ''
cmp = mynon_letters <=> yoursnon_letters
mine_index += mynon_letters.length
yours_index += yoursnon_letters.length
if cmp == 0 # handle digits
_mymatch, mydigits = *match_digits(mine.slice(mine_index..-1))
mydigits ||= ''
_yoursmatch, yoursdigits = *match_digits(yours.slice(yours_index..-1))
yoursdigits ||= ''
cmp = mydigits.to_i <=> yoursdigits.to_i
mine_index += mydigits.length
yours_index += yoursdigits.length
end
end
end
end
if cmp == 0
if mine_index < mine.length && match_tildes(mine[mine_index])
cmp = -1
elsif yours_index < yours.length && match_tildes(yours[yours_index])
cmp = 1
else
cmp = mine.length <=> yours.length
end
end
cmp
end
def match_digits(a)
a.match(/^([0-9]+)/)
end
def match_non_letters(a)
a.match(/^([\.\+-]+)/)
end
def match_tildes(a)
a.match(/^(~+)/)
end
def match_letters(a)
a.match(/^([A-Za-z]+)/)
end
end
end
|