Gentoo Archives: gentoo-dev

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

Attachments

File name MIME type
signature.asc application/pgp-signature

Replies

Subject Author
Re: [gentoo-dev] JFYIOR: A Simple Package Versioning Spec konsolebox <konsolebox@×××××.com>