Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/portupgrade
Path: blob/master/lib/pkgtools/pkgversion.rb
102 views
1
# vim: set sts=2 sw=2 ts=8 et:
2
#
3
# Copyright (c) 2001-2004 Akinori MUSHA <[email protected]>
4
# Copyright (c) 2006-2008 Sergey Matveychuk <[email protected]>
5
# Copyright (c) 2009-2012 Stanislav Sedov <[email protected]>
6
#
7
# All rights reserved.
8
#
9
# Redistribution and use in source and binary forms, with or without
10
# modification, are permitted provided that the following conditions
11
# are met:
12
# 1. Redistributions of source code must retain the above copyright
13
# notice, this list of conditions and the following disclaimer.
14
# 2. Redistributions in binary form must reproduce the above copyright
15
# notice, this list of conditions and the following disclaimer in the
16
# documentation and/or other materials provided with the distribution.
17
#
18
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28
# SUCH DAMAGE.
29
#
30
31
class PkgVersion
32
include Comparable
33
34
attr_accessor :version, :revision, :epoch
35
36
def initialize(pkgversion)
37
if /[\s-]/ =~ pkgversion
38
raise ArgumentError, "#{pkgversion}: Must not contain a '-' or whitespace."
39
end
40
41
if /^([^_,]+)(?:_(\d+))?(?:,(\d+))?$/ !~ pkgversion
42
raise ArgumentError, "#{pkgversion}: Not in due form: '<version>[_<revision>][,<epoch>]'."
43
end
44
45
@version = $1
46
@revision = $2 ? $2.to_i : 0
47
@epoch = $3 ? $3.to_i : 0
48
end
49
50
def to_s
51
s = @version
52
s += '_' + @revision.to_s if @revision.nonzero?
53
s += ',' + @epoch.to_s if @epoch.nonzero?
54
55
s
56
end
57
58
def coerce(other)
59
case other
60
when PkgVersion
61
return other, self
62
when String
63
return PkgVersion.new(other), self
64
else
65
raise TypeError, "Coercion between #{other.class} and #{self.class} is not supported."
66
end
67
end
68
69
def <=>(other)
70
case other
71
when PkgVersion
72
# ok
73
when String
74
other = PkgVersion.new(other)
75
else
76
a, b = other.coerce(self)
77
78
return a <=> b
79
end
80
81
(@epoch <=> other.epoch).nonzero? ||
82
PkgVersion.compare_numbers(@version, other.version).nonzero? ||
83
@revision <=> other.revision
84
end
85
86
def PkgVersion::compare_numbers(n1, n2)
87
# For full comparing rules see file:
88
# /usr/src/usr.sbin/pkg_install/lib/version.c
89
special = { 'pl' => 'pl', 'alpha' => 'a', 'beta' => 'b',
90
'pre' => 'p', 'rc' => 'r' }
91
92
n1 ||= ''
93
n2 ||= ''
94
95
# Remove padded 0's
96
n1 = n1.gsub(/(^|\D)0+(\d)/, "\\1\\2")
97
n2 = n2.gsub(/(^|\D)0+(\d)/, "\\1\\2")
98
99
# Short-cut in case of equality
100
if n1 == n2
101
return 0
102
end
103
104
# For versions seperated with '+': e.g. 1.0.1+2004.09.06
105
# split them and compare by pairs
106
if /\+/ =~ n1 || /\+/ =~ n2
107
a1 = n1.split(/\+/)
108
a2 = n2.split(/\+/)
109
c = compare_numbers(a1.shift, a2.shift)
110
if c != 0
111
return c
112
else
113
return compare_numbers(a1.shift, a2.shift)
114
end
115
end
116
117
# Add separators before specials
118
for s in special.keys
119
n1 = n1.gsub(/(#{s})/, ".#{special[s]}")
120
n2 = n2.gsub(/(#{s})/, ".#{special[s]}")
121
end
122
123
# Add missed separators.
124
n1 = n1.gsub(/([a-zA-Z]\d)([a-zA-Z])/, "\\1.\\2");
125
n2 = n2.gsub(/([a-zA-Z]\d)([a-zA-Z])/, "\\1.\\2");
126
127
# Collaps consecutive separators
128
n1 = n1.gsub(/([^a-zA-Z\d])+/, "\\1");
129
n2 = n2.gsub(/([^a-zA-Z\d])+/, "\\1");
130
131
# Split into subnumbers
132
a1 = n1.split(/[^a-zA-Z\d]/)
133
a2 = n2.split(/[^a-zA-Z\d]/)
134
135
s1 = nil
136
s2 = nil
137
138
# Look for first different subnumber
139
begin
140
break if a1.empty? && a2.empty?
141
142
s1 = a1.shift
143
s2 = a2.shift
144
# Magic for missing '0's: 1.0 == 1.0.0
145
s1 ||= '0'
146
s2 ||= '0'
147
end while s1 == s2
148
149
# Short-cut in case of equality
150
if s1 == s2
151
return 0
152
end
153
154
# Split into sub-subnumbers
155
a1 = s1.split(/(\D+)/)
156
a2 = s2.split(/(\D+)/)
157
158
# If a string starts with a splitter, split() returns
159
# an empty first element. Adjust it.
160
if /^\D/ =~ s1
161
a1.shift
162
end
163
if /^\D/ =~ s2
164
a2.shift
165
end
166
167
# Look for first different sub-subnumber
168
begin
169
break if a1.empty? && a2.empty?
170
171
x1 = a1.shift
172
x2 = a2.shift
173
end while x1 == x2
174
175
x1 ||= ''
176
x2 ||= ''
177
178
# Short-cut in case of equality
179
if x1 == x2
180
return 0
181
end
182
183
# Specail case - pl. It always lose.
184
if x1 == "pl"
185
return -1
186
elsif x2 == "pl"
187
return 1
188
end
189
190
if /^\D/ =~ x1 # x1: non-number
191
if x2.empty? # vs. x2: null (5.0a > 5.0.b)
192
return 1 # -> x1 wins
193
end
194
if /^\D/ !~ x2 # vs. x2: number
195
return -1 # -> x2 wins
196
end
197
# vs. x2: non-number
198
return x1 <=> x2 # -> Compare in dictionary order
199
end
200
201
if /^\d/ =~ x1 # x1: number
202
if /^\d/ =~ x2 # vs. x2: number
203
return x1.to_i <=> x2.to_i # -> Compare numerically
204
end
205
# vs. x2: non-number
206
return 1 # -> x1 wins
207
end
208
# x1: null (4a < 40a)
209
return -1 # -> x2 wins
210
end
211
end
212
213