Gentoo Archives: gentoo-dev

From: konsolebox <konsolebox@×××××.com>
To: gentoo-dev@l.g.o
Subject: [gentoo-dev] JFYIOR: A Simple Package Versioning Spec
Date: Fri, 18 Sep 2015 09:32:36
Message-Id: CAJnmqwbz4gf26peqB=FC0wuct4BSVy0jOY+3_hR5ZSnTAo8-qg@mail.gmail.com
1 This is what an ideal and simple versioning spec should look like to
2 me. (Not the form, but the concept). I'm posting this here so it
3 could be used as an added reference to anyone that would consider
4 revising the current specification.
5
6 Note: Assigning default values can be bypassed depending on the implementation.
7
8 Comments are welcome.
9 ----------------------------------------
10 1 Version Specification
11
12 A version is composed of four component sets: the base part, the
13 stage, the patch and the revision.
14
15 Each component is composed mainly of version nodes to represent their
16 level values.
17
18 When a component is not specified, it gets a default node value of {0}.
19
20 1.1 Version Nodes
21
22 Nodes start basically with a number and then is optionally followed by
23 a set of letters.
24
25 Numbers and letters can coexist alternatingly to represent a single
26 node. The number of consecutive letters is also not limited to 1.
27 For example: "1a4xy" is allowed (can be restricted).
28
29 Each set of digits represents a single decimal number. Leading zeros
30 hold no meaning.
31
32 The numerical equivalent of a set of letters is calculated in base of
33 27 (0 exists along with it but is not included as a symbol).
34
35 Version nodes after processing are basically just an array of signed
36 integers where each set of digits or letters are converted to their
37 numerical value.
38
39 1.2 Version Parts
40
41 1.2.1 The Base Part
42
43 The base version is simply a set of version nodes that are separated
44 by dots. Examples: "4.1.2", "4.1.2aa" "4a", "4a.1", "4a.1.2".
45
46 After processing, the base version is an array of version nodes.
47
48 It is required.
49
50 1.2.2 The Stage Part
51
52 The stage part starts with _alpha, _beta, _pre or _rc. Their
53 numerical values are -4, -3, -2 or -1 respectively. Each of them can
54 optionally be followed by a version node string. For example:
55 "_alpha01".
56
57 The resulting value for the stage after processing is a single version
58 node where the first value in it is the numerical values of _alpha,
59 _beta, _pre or _rc, and the other values are based on the added node
60 string.
61
62 A version without a stage has a default stage value of {0}.
63
64 The stage part is optional and can be specified only once after the
65 base part. It can't be specified as a modifier for the patch, for the
66 revision, or for another stage.
67
68 1.2.3 The Patch Part
69
70 The patch part begins with _p and is followed by a version node
71 string. For example: "_p20150105a".
72
73 The patch part is optional and can be specified after the base part or
74 after the stage part, but not after the revision. It can only be
75 specified once.
76
77 It is processed as a single version node based on its version node string.
78
79 A version without a patch has a default patch value of {0}.
80
81 1.2.4 The Revision
82
83 The revision starts with -r and is followed by a number.
84
85 It is processed as a single version node based on its version node string.
86
87 A version without a revision has a default revision value of {0}.
88
89 1.3 Comparing Versions
90
91 Versions are compared as version nodes and the algorithm is simple:
92 each component and subcomponent is compared from left to right.
93
94 Anything that gives a difference decides which version is greater or lesser.
95
96 Any non-existing version-node has a default value of {0}.
97
98 Any non-existing element of a version node has a default value of 0.
99
100 If no difference is found during the process, it would mean that both
101 versions are equal.
102
103 1.3.1 Concept Code
104
105 #!/usr/bin/ruby
106
107 class ::Array
108 def adaptive_transpose
109 h_size = self.max_by{ |a| a.size }.size
110 v_size = self.size
111
112 result = Array.new(h_size)
113 with_index = self.each_with_index.to_a.freeze
114
115 0.upto(h_size - 1) do |i|
116 result[i] = Array.new(v_size)
117 with_index.each{ |a, j| result[i][j] = a[i] }
118 end
119
120 result
121 end
122 end
123
124 module Portage
125 class PackageVersion
126 class Node < ::Array
127 def initialize(*values)
128 self.concat(values)
129 end
130
131 def compare_with(another)
132 [self, another].adaptive_transpose.each do |a, b|
133 a ||= 0
134 b ||= 0
135 return -1 if a < b
136 return 1 if a > b
137 end
138
139 return 0
140 end
141
142 def self.parse(*args)
143 result = new
144
145 args.each do |a|
146 case a
147 when Integer
148 result << a
149 when /^[[:digit:]][[:alnum:]]*$/
150 a.scan(/[[:digit:]]+|[[:alpha:]]+/).each_with_index.map do |b, i|
151 if i.even?
152 str = b.gsub(/^0+/, '')
153 result << (str.empty? ? 0 : Integer(str))
154 else
155 value = 0
156
157 b.downcase.bytes.reverse.each_with_index do |c, i|
158 value += 27 ** i * (c - 96) ## a == 1, z == 26,
159 and 0 exists but is not used
160 end
161
162 result << value
163 end
164 end
165 else
166 raise ArgumentError.new("Invalid node string: #{a.inspect}")
167 end
168 end
169
170 result
171 end
172
173 def self.zero
174 @zero ||= new(0)
175 end
176
177 private_class_method :new, :allocate
178 end
179
180 attr_accessor :base, :stage, :patch, :revision
181
182 def initialize(base, stage, patch, revision)
183 @base, @stage, @patch, @revision = base, stage, patch, revision
184 end
185
186 def compare_with(another)
187 [self.base, another.base].adaptive_transpose.each do |a, b|
188 a ||= Node.zero
189 b ||= Node.zero
190 r = a.compare_with(b)
191 return r unless r == 0
192 end
193
194 r = self.stage.compare_with(another.stage)
195 return r unless r == 0
196
197 r = self.patch.compare_with(another.patch)
198 return r unless r == 0
199
200 r = self.revision.compare_with(another.revision)
201 return r unless r == 0
202
203 return 0
204 end
205
206 STAGES = { 'alpha' => -4, 'beta' => -3, 'pre' => -2, 'rc' => -1 }
207 REGEX = /^([[:digit:]][[:alnum:]]*(?:[.][[:alnum:]]+)*)?(?:_(alpha|beta|pre|rc)([[:digit:]][[:alnum:]]*)?)?(?:_p([[:digit:]][[:alnum:]]*))?(?:-r([[:digit:]]+))?(.+)?$/m
208
209 def self.parse(version_string)
210 __, base, stage, stage_ver, patch, revision, extra =
211 version_string.match(REGEX).to_a
212 raise_invalid_version_string(version_string) if extra
213
214 begin
215 base = base.split('.').map{ |e| Node.parse(e) }
216 stage = stage ? stage_ver ? Node.parse(STAGES[stage],
217 stage_ver) : Node.parse(STAGES[stage]) : Node.zero
218 patch = patch ? Node.parse(patch) : Node.zero
219 revision = revision ? Node.parse(revision) : Node.zero
220 rescue ArgumentError => e
221 raise_invalid_version_string("#{version_string}: #{e}")
222 end
223
224 new(base, stage, patch, revision)
225 end
226
227 def self.raise_invalid_version_string(version_string)
228 raise ArgumentError.new("Invalid version string: #{version_string}")
229 end
230
231 private_class_method :new, :allocate, :raise_invalid_version_string
232 end
233 end
234
235 samples = [
236 ["0", "0.01"],
237 ["0.01", "0.010"],
238 ["0.09", "0.090"],
239 ["0.10", "0.100"],
240 ["0.99", "0.990"],
241 ["0.100", "0.1000"],
242 ["0.100", "0.100"],
243 ["0.1", "0.1.1"],
244 ["0.1.1", "0.1a"],
245 ["0.1a", "0.2"],
246 ["0.2", "1"],
247 ["1", "1.0"],
248 ["1.0", "1.0_alpha"],
249 ["1.0_alpha", "1.0_alpha01"],
250 ["1.0_alpha01", "1.0_alpha01-r1"],
251 ["1.0_alpha01-r1", "1.0_alpha01_p20150105"],
252 ["1.0_alpha01_p20150105", "1.0_alpha01_p20150105-r1"],
253 ["1.0_alpha01", "1.0_beta"],
254 ["1.0_beta", "1.0_beta01"],
255 ["1.0_beta01", "1.0_pre01"],
256 ["1.0_pre01", "1.0_rc01"],
257 ["1.0_rc01", "1.0"],
258 ["1.0", "1.0-r1"],
259 ["1.0-r1", "1.0_p20150105"],
260 ["1.0_p20150105", "1.0_p20150105-r1"]
261 ]
262
263 samples.each do |a, b|
264 x = Portage::PackageVersion.parse(a)
265 y = Portage::PackageVersion.parse(b)
266 r = x.compare_with(y)
267 r = r < 0 ? '<' : r > 0 ? '>' : '=='
268 puts "#{a} #{r} #{b}"
269 end
270
271 1.3.2 Concept Code Output
272
273 0 < 0.01
274 0.01 < 0.010
275 0.09 < 0.090
276 0.10 < 0.100
277 0.99 < 0.990
278 0.100 < 0.1000
279 0.100 == 0.100
280 0.1 < 0.1.1
281 0.1.1 < 0.1a
282 0.1a < 0.2
283 0.2 < 1
284 1 == 1.0
285 1.0 > 1.0_alpha
286 1.0_alpha < 1.0_alpha01
287 1.0_alpha01 < 1.0_alpha01-r1
288 1.0_alpha01-r1 < 1.0_alpha01_p20150105
289 1.0_alpha01_p20150105 < 1.0_alpha01_p20150105-r1
290 1.0_alpha01 < 1.0_beta
291 1.0_beta < 1.0_beta01
292 1.0_beta01 < 1.0_pre01
293 1.0_pre01 < 1.0_rc01
294 1.0_rc01 < 1.0
295 1.0 < 1.0-r1
296 1.0-r1 < 1.0_p20150105
297 1.0_p20150105 < 1.0_p20150105-r1

Replies

Subject Author
Re: [gentoo-dev] JFYIOR: A Simple Package Versioning Spec Ciaran McCreesh <ciaran.mccreesh@××××××××××.com>
Re: [gentoo-dev] JFYIOR: A Simple Package Versioning Spec Matthew Thode <prometheanfire@g.o>
Re: [gentoo-dev] JFYIOR: A Simple Package Versioning Spec "Michał Górny" <mgorny@g.o>
Re: [gentoo-dev] JFYIOR: A Simple Package Versioning Spec Taahir Ahmed <ahmed.taahir@×××××.com>