php-composer 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +10 -10
  3. data/Gemfile +15 -15
  4. data/LICENSE.txt +21 -21
  5. data/README.md +35 -35
  6. data/Rakefile +1 -1
  7. data/lib/composer.rb +52 -52
  8. data/lib/composer/error.rb +8 -8
  9. data/lib/composer/json/json_file.rb +270 -270
  10. data/lib/composer/package/alias_package.rb +273 -273
  11. data/lib/composer/package/base_package.rb +130 -130
  12. data/lib/composer/package/complete_package.rb +55 -55
  13. data/lib/composer/package/dumper/hash_dumper.rb +169 -169
  14. data/lib/composer/package/link.rb +51 -51
  15. data/lib/composer/package/link_constraint/base_constraint.rb +35 -35
  16. data/lib/composer/package/link_constraint/empty_constraint.rb +34 -34
  17. data/lib/composer/package/link_constraint/multi_constraint.rb +66 -66
  18. data/lib/composer/package/link_constraint/specific_constraint.rb +40 -40
  19. data/lib/composer/package/link_constraint/version_constraint.rb +220 -220
  20. data/lib/composer/package/loader/hash_loader.rb +316 -316
  21. data/lib/composer/package/loader/json_loader.rb +47 -47
  22. data/lib/composer/package/loader/project_attributes_loader.rb +71 -71
  23. data/lib/composer/package/loader/project_root_package_loader.rb +28 -28
  24. data/lib/composer/package/package.rb +118 -118
  25. data/lib/composer/package/root_alias_package.rb +37 -37
  26. data/lib/composer/package/root_package.rb +37 -37
  27. data/lib/composer/package/version/version_parser.rb +583 -583
  28. data/lib/composer/package/version/version_selector.rb +106 -106
  29. data/lib/composer/repository/filesystem_repository.rb +84 -85
  30. data/lib/composer/repository/{array_repository.rb → hash_repository.rb} +195 -195
  31. data/lib/composer/repository/{writeable_array_repository.rb → writeable_hash_repository.rb} +57 -59
  32. data/lib/composer/version.rb +3 -3
  33. data/php-composer.gemspec +31 -31
  34. metadata +4 -4
@@ -1,37 +1,37 @@
1
- #
2
- # This file was ported to ruby from Composer php source code file.
3
- # Original Source: Composer\Package\RootAliasPackage.php
4
- #
5
- # (c) Nils Adermann <naderman@naderman.de>
6
- # Jordi Boggiano <j.boggiano@seld.be>
7
- #
8
- # For the full copyright and license information, please view the LICENSE
9
- # file that was distributed with this source code.
10
- #
11
-
12
- module Composer
13
- module Package
14
-
15
- # The root package represents the project's composer.json
16
- # and contains additional metadata
17
- class RootAliasPackage < Composer::Package::CompletePackage
18
-
19
- attr_accessor :minimum_stability, :prefer_stable, :stability_flags,
20
- :references, :aliases
21
-
22
- # Creates a new root package in memory package.
23
- # Param: string name The package's name
24
- # Param: string version The package's version
25
- # Param: string prettyVersion The package's non-normalized version
26
- def initialize(name, version, pretty_version)
27
- super(name, version, pretty_version)
28
-
29
- @minimum_stability = 'stable'
30
- @stability_flags = []
31
- @references = []
32
- @aliases = []
33
- end
34
-
35
- end
36
- end
37
- end
1
+ #
2
+ # This file was ported to ruby from Composer php source code file.
3
+ # Original Source: Composer\Package\RootAliasPackage.php
4
+ #
5
+ # (c) Nils Adermann <naderman@naderman.de>
6
+ # Jordi Boggiano <j.boggiano@seld.be>
7
+ #
8
+ # For the full copyright and license information, please view the LICENSE
9
+ # file that was distributed with this source code.
10
+ #
11
+
12
+ module Composer
13
+ module Package
14
+
15
+ # The root package represents the project's composer.json
16
+ # and contains additional metadata
17
+ class RootAliasPackage < Composer::Package::CompletePackage
18
+
19
+ attr_accessor :minimum_stability, :prefer_stable, :stability_flags,
20
+ :references, :aliases
21
+
22
+ # Creates a new root package in memory package.
23
+ # Param: string name The package's name
24
+ # Param: string version The package's version
25
+ # Param: string prettyVersion The package's non-normalized version
26
+ def initialize(name, version, pretty_version)
27
+ super(name, version, pretty_version)
28
+
29
+ @minimum_stability = 'stable'
30
+ @stability_flags = []
31
+ @references = []
32
+ @aliases = []
33
+ end
34
+
35
+ end
36
+ end
37
+ end
@@ -1,37 +1,37 @@
1
- #
2
- # This file was ported to ruby from Composer php source code file.
3
- # Original Source: Composer\Package\RootPackage.php
4
- #
5
- # (c) Nils Adermann <naderman@naderman.de>
6
- # Jordi Boggiano <j.boggiano@seld.be>
7
- #
8
- # For the full copyright and license information, please view the LICENSE
9
- # file that was distributed with this source code.
10
- #
11
-
12
- module Composer
13
- module Package
14
-
15
- # The root package represents the project's composer.json
16
- # and contains additional metadata
17
- class RootPackage < Composer::Package::CompletePackage
18
-
19
- attr_accessor :minimum_stability, :prefer_stable, :stability_flags,
20
- :references, :aliases
21
-
22
- # Creates a new root package in memory package.
23
- # Param: string name The package's name
24
- # Param: string version The package's version
25
- # Param: string prettyVersion The package's non-normalized version
26
- def initialize(name, version, pretty_version)
27
- super(name, version, pretty_version)
28
-
29
- @minimum_stability = 'stable'
30
- @stability_flags = []
31
- @references = []
32
- @aliases = []
33
- end
34
-
35
- end
36
- end
37
- end
1
+ #
2
+ # This file was ported to ruby from Composer php source code file.
3
+ # Original Source: Composer\Package\RootPackage.php
4
+ #
5
+ # (c) Nils Adermann <naderman@naderman.de>
6
+ # Jordi Boggiano <j.boggiano@seld.be>
7
+ #
8
+ # For the full copyright and license information, please view the LICENSE
9
+ # file that was distributed with this source code.
10
+ #
11
+
12
+ module Composer
13
+ module Package
14
+
15
+ # The root package represents the project's composer.json
16
+ # and contains additional metadata
17
+ class RootPackage < Composer::Package::CompletePackage
18
+
19
+ attr_accessor :minimum_stability, :prefer_stable, :stability_flags,
20
+ :references, :aliases
21
+
22
+ # Creates a new root package in memory package.
23
+ # Param: string name The package's name
24
+ # Param: string version The package's version
25
+ # Param: string prettyVersion The package's non-normalized version
26
+ def initialize(name, version, pretty_version)
27
+ super(name, version, pretty_version)
28
+
29
+ @minimum_stability = 'stable'
30
+ @stability_flags = []
31
+ @references = []
32
+ @aliases = []
33
+ end
34
+
35
+ end
36
+ end
37
+ end
@@ -1,583 +1,583 @@
1
- #
2
- # This file was ported to ruby from Composer php source code file.
3
- # Original Source: Composer\Package\Version\VersionParser.php
4
- #
5
- # (c) Nils Adermann <naderman@naderman.de>
6
- # Jordi Boggiano <j.boggiano@seld.be>
7
- #
8
- # For the full copyright and license information, please view the LICENSE
9
- # file that was distributed with this source code.
10
- #
11
-
12
- module Composer
13
- module Package
14
- module Version
15
- # Version Parser
16
- #
17
- # PHP Authors:
18
- # Jordi Boggiano <j.boggiano@seld.be>
19
- #
20
- # Ruby Authors:
21
- # Ioannis Kappas <ikappas@devworks.gr>
22
- class VersionParser
23
- MODIFIER_REGEX = '[._-]?(?:(stable|beta|b|RC|alpha|a|patch|pl|p)(?:[.-]?(\d+))?)?([.-]?dev)?'.freeze()
24
-
25
- class << self
26
-
27
- # Returns the stability of a version
28
- #
29
- # Params:
30
- # +version+:: string The version to parse for stability
31
- #
32
- # Returns:
33
- # string The version's stability
34
- def parse_stability(version)
35
- raise ArgumentError, 'version must be specified' unless version
36
- raise TypeError, 'version must be of type String' unless version.is_a?(String)
37
- raise UnexpectedValueError, 'version string must not be empty' if version.empty?
38
-
39
- version = version.gsub(/#.+$/i, '')
40
-
41
- if version.start_with?('dev-') || version.end_with?('-dev')
42
- return 'dev'
43
- end
44
-
45
- if matches = /#{MODIFIER_REGEX}$/i.match(version.downcase)
46
- if matches[3]
47
- return 'dev'
48
- elsif matches[1]
49
- if 'beta' === matches[1] || 'b' === matches[1]
50
- return 'beta'
51
- elsif 'alpha' === matches[1] || 'a' === matches[1]
52
- return 'alpha'
53
- elsif 'rc' === matches[1]
54
- return 'RC'
55
- end
56
- end
57
- end
58
-
59
- 'stable'
60
- end
61
-
62
- # Normalize the specified stability
63
- # Param: string stability
64
- # Return: string
65
- def normalize_stability(stability)
66
- raise ArgumentError, 'stability must be specified' unless stability
67
- raise TypeError, 'stability must be of type String' unless stability.is_a?(String)
68
- stability = stability.downcase
69
- stability === 'rc' ? 'RC' : stability
70
- end
71
-
72
- # Formats package version
73
- # Param: Composer::Package::Package package
74
- # Param: boolean truncate
75
- # Return: string
76
- def format_version(package, truncate = true)
77
- if !package.is_dev || !['hg', 'git'].include?(package.source_type)
78
- return package.pretty_version
79
- end
80
-
81
- # if source reference is a sha1 hash -- truncate
82
- if truncate && package.source_reference.length === 40
83
- return "#{package.pretty_version} #{package.source_reference[0..6]}"
84
- end
85
-
86
- "#{package.pretty_version} #{package.source_reference}"
87
- end
88
- end
89
-
90
- # Normalizes a version string to be able to perform comparisons on it
91
- #
92
- # Params:
93
- # +version+:: <tt>String</tt> The version string to normalize
94
- # +full_version+:: <tt>String</tt> optional complete version string to
95
- # give more context
96
- #
97
- # Throws:
98
- # +InvalidVersionStringError+
99
- #
100
- # Returns:
101
- # +String+
102
- def normalize(version, full_version = nil)
103
- raise ArgumentError, 'version must be specified' unless version
104
- raise TypeError, 'version must be of type String' unless version.is_a?(String)
105
- raise UnexpectedValueError, 'version string must not be empty' if version.empty?
106
-
107
- version.strip!
108
- if full_version == nil
109
- full_version = version
110
- end
111
-
112
- # ignore aliases and just assume the alias is required
113
- # instead of the source
114
- if matches = /^([^,\s]+) +as +([^,\s]+)$/.match(version)
115
- version = matches[1]
116
- end
117
-
118
- # ignore build metadata
119
- if matches = /^([^,\s+]+)\+[^\s]+$/.match(version)
120
- version = matches[1]
121
- end
122
-
123
- # match master-like branches
124
- if matches = /^(?:dev-)?(?:master|trunk|default)$/i.match(version)
125
- return '9999999-dev'
126
- end
127
-
128
- if 'dev-' === version[0...4].downcase
129
- return "dev-#{version[4..version.size]}"
130
- end
131
-
132
- # match classical versioning
133
- index = 0
134
- if matches = /^v?(\d{1,3})(\.\d+)?(\.\d+)?(\.\d+)?#{MODIFIER_REGEX}$/i.match(version)
135
- version = ''
136
- matches.to_a[1..4].each do |c|
137
- version += c ? c : '.0'
138
- end
139
- index = 5
140
- elsif matches = /^v?(\d{4}(?:[.:-]?\d{2}){1,6}(?:[.:-]?\d{1,3})?)#{MODIFIER_REGEX}$/i.match(version)
141
- version = matches[1].gsub(/\D/, '-')
142
- index = 2
143
- elsif matches = /^v?(\d{4,})(\.\d+)?(\.\d+)?(\.\d+)?#{MODIFIER_REGEX}$/i.match(version)
144
- version = ''
145
- matches.to_a[1..4].each do |c|
146
- version << (c.nil? ? '.0' : c)
147
- end
148
- index = 5
149
- end
150
-
151
- # add version modifiers if a version was matched
152
- if index > 0
153
- if matches[index]
154
- if 'stable' === matches[index]
155
- return version
156
- end
157
- stability = expand_stability(matches[index])
158
- version = "#{version}-#{stability ? stability : matches[index]}#{matches[index + 1] ? matches[index + 1] : ''}"
159
- end
160
-
161
- if matches[index + 2]
162
- version = "#{version}-dev"
163
- end
164
-
165
- return version
166
- end
167
-
168
- # match dev branches
169
- if matches = /(.*?)[.-]?dev$/i.match(version)
170
- begin
171
- return normalize_branch(matches[1])
172
- rescue
173
- end
174
- end
175
-
176
- extra_message = ''
177
- if matches = / +as +#{Regexp.escape(version)}$/.match(full_version)
178
- extra_message = " in \"#{full_version}\", the alias must be an exact version"
179
- elsif matches = /^#{Regexp.escape(version)} +as +/.match(full_version)
180
- extra_message = " in \"#{full_version}\", the alias source must be an exact version, if it is a branch name you should prefix it with dev-"
181
- end
182
-
183
- raise UnexpectedValueError, "Invalid version string \"#{version}\"#{extra_message}"
184
- end
185
-
186
- # Normalizes a branch name to be able to perform comparisons on it
187
- #
188
- # Params:
189
- # +name+:: string The branch name to normalize
190
- #
191
- # Returns:
192
- # string The normalized branch name
193
- def normalize_branch(name)
194
- name.strip!
195
-
196
- if ['master', 'trunk', 'default'].include?(name)
197
- return normalize(name)
198
- end
199
-
200
- if matches = /^v?(\d+)(\.(?:\d+|[xX*]))?(\.(?:\d+|[xX*]))?(\.(?:\d+|[xX*]))?$/i.match(name)
201
- version = ''
202
-
203
- # for i in 0..3
204
- # # version << matches[i] ? matches[i].gsub('*', 'x').gsub('X', 'x') : '.x'
205
- # end
206
- matches.captures.each { |match| version << (match != nil ? match.gsub('*', 'x').gsub('X', 'x') : '.x') }
207
- return "#{version.gsub('x', '9999999')}-dev"
208
- end
209
-
210
- "dev-#{name}"
211
- end
212
-
213
- # Params:
214
- # +source+:: string source package name
215
- # +source_version+:: string source package version (pretty version ideally)
216
- # +description+:: string link description (e.g. requires, replaces, ..)
217
- # +links+:: array An array of package name => constraint mappings
218
- #
219
- # Returns:
220
- # Link[]
221
- def parse_links(source, source_version, description, links)
222
- res = {}
223
- links.each do |target, constraint|
224
- if 'self.version' === constraint
225
- parsed_constraint = parse_constraints(source_version)
226
- else
227
- parsed_constraint = parse_constraints(constraint)
228
- end
229
- res[target.downcase] = Composer::Package::Link.new(
230
- source,
231
- target,
232
- parsed_constraint,
233
- description,
234
- constraint
235
- )
236
- end
237
- res
238
- end
239
-
240
- def parse_constraints(constraints)
241
- raise ArgumentError, 'version must be specified' unless constraints
242
- raise TypeError, 'version must be of type String' unless constraints.is_a?(String)
243
- raise UnexpectedValueError, 'version string must not be empty' if constraints.empty?
244
-
245
- pretty_constraint = constraints
246
-
247
- stabilites = Composer::Package::BasePackage.stabilities.keys.join('|')
248
- if match = /^([^,\s]*?)@(#{stabilites})$/i.match(constraints)
249
- constraints = match[1].nil? || match[1].empty? ? '*' : match[1]
250
- end
251
-
252
- if match = /^(dev-[^,\s@]+?|[^,\s@]+?\.x-dev)#.+$/i.match(constraints)
253
- constraints = match[1]
254
- end
255
-
256
- # or_constraints = preg_split('{\s*\|\|?\s*}', trim(constraints))
257
- or_constraints = constraints.strip.split(/\s*\|\|?\s*/)
258
- or_groups = []
259
- or_constraints.each do |constraints|
260
-
261
- # and_constraints = preg_split('{(?<!^|as|[=>< ,]) *(?<!-)[, ](?!-) *(?!,|as|$)}', constraints)
262
- and_constraints = constraints.split(/(?<!^|as|[=>< ,]) *(?<!-)[, ](?!-) *(?!,|as|$)/)
263
-
264
- if and_constraints.length > 1
265
- constraint_objects = []
266
- and_constraints.each do |constraint|
267
- constraint_objects << parse_constraint(constraint)
268
- end
269
- else
270
- constraint_objects = parse_constraint(and_constraints[0])
271
- end
272
-
273
- if constraint_objects.length === 1
274
- constraint = constraint_objects[0]
275
- else
276
- constraint = Composer::Package::LinkConstraint::MultiConstraint.new(constraint_objects)
277
- end
278
-
279
- or_groups << constraint
280
- end
281
-
282
- if or_groups.length === 1
283
- constraint = or_groups[0]
284
- else
285
- constraint = Composer::Package::LinkConstraint::MultiConstraint.new(or_groups, false)
286
- end
287
-
288
- constraint.pretty_string = pretty_constraint
289
-
290
- constraint
291
- end
292
-
293
- # Extract numeric prefix from alias, if it is in numeric format,
294
- # suitable for version comparison
295
- #
296
- # Params:
297
- # +branch+:: string Branch name (e.g. 2.1.x-dev)
298
- #
299
- # Returns:
300
- # string|false Numeric prefix if present (e.g. 2.1.) or false
301
- def parse_numeric_alias_prefix(branch)
302
- if matches = /^(?<version>(\d+\.)*\d+)(?:\.x)?-dev$/i.match(branch)
303
- return "#{matches['version']}."
304
- end
305
- false
306
- end
307
-
308
- # Parses a name/version pairs and returns an array of pairs + the
309
- #
310
- # Params:
311
- # +pairs+:: array a set of package/version pairs separated by ":", "=" or " "
312
- #
313
- # Returns:
314
- # array[] array of arrays containing a name and (if provided) a version
315
- def parse_name_version_pairs(pairs)
316
- pairs = pairs.values
317
- result = []
318
-
319
- for i in 0..(pairs.length - 1)
320
- pair = pairs[i].strip!.gsub(/^([^=: ]+)[=: ](.*)$/, '$1 $2')
321
- if nil === pair.index(' ') && pairs.key?(i + 1) && nil === pairs[i + 1].index('/')
322
- pair = "#{pair} #{pairs[i + 1]}"
323
- i = i + 1
324
- end
325
-
326
- if pair.index(' ')
327
- name, version = pair.split(' ', 2)
328
- result << { 'name' => name, 'version' => version }
329
- else
330
- result << { 'name' => pair }
331
- end
332
-
333
- end
334
-
335
- result
336
- end
337
-
338
- # PRIVATE METHODS
339
- private
340
-
341
- def parse_constraint(constraint)
342
-
343
- stabilites = Composer::Package::BasePackage.stabilities.keys.join('|')
344
- if match = /^([^,\s]+?)@(#{stabilites})$/i.match(constraint)
345
- constraint = match[1]
346
- if match[2] != 'stable'
347
- stability_modifier = match[2]
348
- end
349
- end
350
-
351
- if /^[xX*](\.[xX*])*$/i.match(constraint)
352
- return [
353
- Composer::Package::LinkConstraint::EmptyConstraint.new
354
- ]
355
- end
356
-
357
- version_regex = '(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:\.(\d+))?' + MODIFIER_REGEX
358
-
359
- # match tilde constraints
360
- # like wildcard constraints, unsuffixed tilde constraints say that they must be greater than the previous
361
- # version, to ensure that unstable instances of the current version are allowed.
362
- # however, if a stability suffix is added to the constraint, then a >= match on the current version is
363
- # used instead
364
- if matches = /^~>?#{version_regex}$/i.match(constraint)
365
-
366
- if constraint[0...2] === '~>'
367
- raise UnexpectedValueError,
368
- "Could not parse version constraint #{constraint}: \
369
- Invalid operator \"~>\", you probably meant to use the \"~\" operator"
370
- end
371
-
372
- # Work out which position in the version we are operating at
373
- if matches[4] && matches[4] != ''
374
- position = 4
375
- elsif matches[3] && matches[3] != ''
376
- position = 3
377
- elsif matches[2] && matches[2] != ''
378
- position = 2
379
- else
380
- position = 1
381
- end
382
-
383
- # Calculate the stability suffix
384
- stability_suffix = ''
385
- if !matches[5].nil? && !matches[5].empty?
386
- stability_suffix << "-#{expand_stability(matches[5])}"
387
- if !matches[6].nil? && !matches[6].empty?
388
- stability_suffix << matches[6]
389
- end
390
- end
391
-
392
- if !matches[7].nil? && !matches[7].empty?
393
- stability_suffix << '-dev'
394
- end
395
-
396
- stability_suffix = '-dev' if stability_suffix.empty?
397
-
398
- low_version = manipulate_version_string(matches.to_a, position, 0) + stability_suffix
399
- lower_bound = Composer::Package::LinkConstraint::VersionConstraint.new('>=', low_version)
400
-
401
- # For upper bound, we increment the position of one more significance,
402
- # but high_position = 0 would be illegal
403
- high_position = [1, position - 1].max
404
- high_version = manipulate_version_string(matches.to_a, high_position, 1) + '-dev'
405
- upper_bound = Composer::Package::LinkConstraint::VersionConstraint.new('<', high_version)
406
-
407
- return [
408
- lower_bound,
409
- upper_bound
410
- ]
411
- end
412
-
413
- # match caret constraints
414
- if matches = /^\^#{version_regex}($)/i.match(constraint)
415
- # Create comparison array
416
- has_match = []
417
- for i in 0..(matches.to_a.length - 1)
418
- has_match[i] = !matches[i].nil? && !matches[i].empty?
419
- end
420
-
421
- # Work out which position in the version we are operating at
422
- if matches[1] != '0' || !has_match[2]
423
- position = 1
424
- elsif matches[2] != '0' || !has_match[3]
425
- position = 2
426
- else
427
- position = 3
428
- end
429
-
430
- # Calculate the stability suffix
431
- stability_suffix = ''
432
- if !has_match[5] && !has_match[7]
433
- stability_suffix << '-dev'
434
- end
435
-
436
- low_pretty = "#{constraint}#{stability_suffix}"
437
- low_version = normalize(low_pretty[1..low_pretty.length - 1])
438
- lower_bound = Composer::Package::LinkConstraint::VersionConstraint.new('>=', low_version)
439
-
440
- # For upper bound, we increment the position of one more significance,
441
- # but high_position = 0 would be illegal
442
- high_version = manipulate_version_string(matches.to_a, position, 1) + '-dev'
443
- upper_bound = Composer::Package::LinkConstraint::VersionConstraint.new('<', high_version)
444
-
445
- return [
446
- lower_bound,
447
- upper_bound
448
- ]
449
- end
450
-
451
- # match wildcard constraints
452
- if matches = /^(\d+)(?:\.(\d+))?(?:\.(\d+))?\.[xX*]$/.match(constraint)
453
- if matches[3] && matches[3] != ''
454
- position = 3
455
- elsif matches[2] && matches[2] != ''
456
- position = 2
457
- else
458
- position = 1
459
- end
460
-
461
- low_version = manipulate_version_string(matches.to_a, position) + '-dev'
462
- high_version = manipulate_version_string(matches.to_a, position, 1) + '-dev'
463
-
464
- if low_version === "0.0.0.0-dev"
465
- return [Composer::Package::LinkConstraint::VersionConstraint.new('<', high_version)]
466
- end
467
-
468
- return [
469
- Composer::Package::LinkConstraint::VersionConstraint.new('>=', low_version),
470
- Composer::Package::LinkConstraint::VersionConstraint.new('<', high_version),
471
- ]
472
- end
473
-
474
- # match hyphen constraints
475
- if matches = /^(#{version_regex}) +- +(#{version_regex})($)/i.match(constraint)
476
-
477
- match_from = matches[1]
478
- match_to = matches[9]
479
-
480
- # Create comparison array
481
- has_match = []
482
- for i in 0..(matches.to_a.length - 1)
483
- has_match[i] = !matches[i].nil? && !matches[i].empty?
484
- end
485
-
486
- # Calculate the stability suffix
487
- low_stability_suffix = ''
488
- if !has_match[6] && !has_match[8]
489
- low_stability_suffix = '-dev'
490
- end
491
-
492
- low_version = normalize(match_from)
493
- lower_bound = Composer::Package::LinkConstraint::VersionConstraint.new('>=', low_version + low_stability_suffix)
494
-
495
- # high_version = matches[10]
496
-
497
- if (has_match[11] && has_match[12]) || has_match[14] || has_match[16]
498
- high_version = normalize(match_to)
499
- upper_bound = Composer::Package::LinkConstraint::VersionConstraint.new('<=', high_version)
500
- else
501
- high_match = ['', matches[10], matches[11], matches[12], matches[13]]
502
- high_version = manipulate_version_string(high_match, (!has_match[11] ? 1 : 2), 1) + '-dev'
503
- upper_bound = Composer::Package::LinkConstraint::VersionConstraint.new('<', high_version)
504
- end
505
-
506
- return [
507
- lower_bound,
508
- upper_bound
509
- ]
510
- end
511
-
512
- # match operators constraints
513
- if matches = /^(<>|!=|>=?|<=?|==?)?\s*(.*)/.match(constraint)
514
- begin
515
- version = normalize(matches[2])
516
- stability = VersionParser::parse_stability(version)
517
- if !stability_modifier.nil? && !stability_modifier.empty? && (stability === 'stable')
518
- version << "-#{stability_modifier}"
519
- elsif matches[1] === '<'
520
- unless /-#{MODIFIER_REGEX}$/.match(matches[2].downcase)
521
- version << '-dev'
522
- end
523
- end
524
- operator = matches[1].nil? ? '=' : matches[1]
525
- return [Composer::Package::LinkConstraint::VersionConstraint.new(operator, version)]
526
- rescue Exception => e
527
- end
528
- end
529
-
530
- message = "Could not parse version constraint #{constraint}"
531
- message << ": #{e.message}" if e
532
- raise UnexpectedValueError, message
533
- end
534
-
535
- # Increment, decrement, or simply pad a version number.
536
- # Support function for {@link parse_constraint()}
537
- #
538
- # Params:
539
- # +matches+ Array with version parts in array indexes 1,2,3,4
540
- # +position+ Integer 1,2,3,4 - which segment of the version to decrement
541
- # +increment+ Integer
542
- # +pad+ String The string to pad version parts after position
543
- #
544
- # Returns:
545
- # string The new version
546
- def manipulate_version_string(matches, position, increment = 0, pad = '0')
547
- 4.downto(1).each do |i|
548
- if i > position
549
- matches[i] = pad
550
- elsif i == position && increment
551
- matches[i] = matches[i].to_i + increment
552
- # If matches[i] was 0, carry the decrement
553
- if matches[i] < 0
554
- matches[i] = pad
555
- position -= 1
556
-
557
- # Return nil on a carry overflow
558
- return nil if i == 1
559
- end
560
- end
561
- end
562
- "#{matches[1]}.#{matches[2]}.#{matches[3]}.#{matches[4]}"
563
- end
564
-
565
- def expand_stability(stability)
566
- stability = stability.downcase
567
- case stability
568
- when 'a'
569
- 'alpha'
570
- when 'b'
571
- 'beta'
572
- when 'p', 'pl'
573
- 'patch'
574
- when 'rc'
575
- 'RC'
576
- else
577
- stability
578
- end
579
- end
580
- end
581
- end
582
- end
583
- end
1
+ #
2
+ # This file was ported to ruby from Composer php source code file.
3
+ # Original Source: Composer\Package\Version\VersionParser.php
4
+ #
5
+ # (c) Nils Adermann <naderman@naderman.de>
6
+ # Jordi Boggiano <j.boggiano@seld.be>
7
+ #
8
+ # For the full copyright and license information, please view the LICENSE
9
+ # file that was distributed with this source code.
10
+ #
11
+
12
+ module Composer
13
+ module Package
14
+ module Version
15
+ # Version Parser
16
+ #
17
+ # PHP Authors:
18
+ # Jordi Boggiano <j.boggiano@seld.be>
19
+ #
20
+ # Ruby Authors:
21
+ # Ioannis Kappas <ikappas@devworks.gr>
22
+ class VersionParser
23
+ MODIFIER_REGEX = '[._-]?(?:(stable|beta|b|RC|alpha|a|patch|pl|p)(?:[.-]?(\d+))?)?([.-]?dev)?'.freeze()
24
+
25
+ class << self
26
+
27
+ # Returns the stability of a version
28
+ #
29
+ # Params:
30
+ # +version+:: string The version to parse for stability
31
+ #
32
+ # Returns:
33
+ # string The version's stability
34
+ def parse_stability(version)
35
+ raise ArgumentError, 'version must be specified' unless version
36
+ raise TypeError, 'version must be of type String' unless version.is_a?(String)
37
+ raise UnexpectedValueError, 'version string must not be empty' if version.empty?
38
+
39
+ version = version.gsub(/#.+$/i, '')
40
+
41
+ if version.start_with?('dev-') || version.end_with?('-dev')
42
+ return 'dev'
43
+ end
44
+
45
+ if matches = /#{MODIFIER_REGEX}$/i.match(version.downcase)
46
+ if matches[3]
47
+ return 'dev'
48
+ elsif matches[1]
49
+ if 'beta' === matches[1] || 'b' === matches[1]
50
+ return 'beta'
51
+ elsif 'alpha' === matches[1] || 'a' === matches[1]
52
+ return 'alpha'
53
+ elsif 'rc' === matches[1]
54
+ return 'RC'
55
+ end
56
+ end
57
+ end
58
+
59
+ 'stable'
60
+ end
61
+
62
+ # Normalize the specified stability
63
+ # Param: string stability
64
+ # Return: string
65
+ def normalize_stability(stability)
66
+ raise ArgumentError, 'stability must be specified' unless stability
67
+ raise TypeError, 'stability must be of type String' unless stability.is_a?(String)
68
+ stability = stability.downcase
69
+ stability === 'rc' ? 'RC' : stability
70
+ end
71
+
72
+ # Formats package version
73
+ # Param: Composer::Package::Package package
74
+ # Param: boolean truncate
75
+ # Return: string
76
+ def format_version(package, truncate = true)
77
+ if !package.is_dev || !['hg', 'git'].include?(package.source_type)
78
+ return package.pretty_version
79
+ end
80
+
81
+ # if source reference is a sha1 hash -- truncate
82
+ if truncate && package.source_reference.length === 40
83
+ return "#{package.pretty_version} #{package.source_reference[0..6]}"
84
+ end
85
+
86
+ "#{package.pretty_version} #{package.source_reference}"
87
+ end
88
+ end
89
+
90
+ # Normalizes a version string to be able to perform comparisons on it
91
+ #
92
+ # Params:
93
+ # +version+:: <tt>String</tt> The version string to normalize
94
+ # +full_version+:: <tt>String</tt> optional complete version string to
95
+ # give more context
96
+ #
97
+ # Throws:
98
+ # +InvalidVersionStringError+
99
+ #
100
+ # Returns:
101
+ # +String+
102
+ def normalize(version, full_version = nil)
103
+ raise ArgumentError, 'version must be specified' unless version
104
+ raise TypeError, 'version must be of type String' unless version.is_a?(String)
105
+ raise UnexpectedValueError, 'version string must not be empty' if version.empty?
106
+
107
+ version.strip!
108
+ if full_version == nil
109
+ full_version = version
110
+ end
111
+
112
+ # ignore aliases and just assume the alias is required
113
+ # instead of the source
114
+ if matches = /^([^,\s]+) +as +([^,\s]+)$/.match(version)
115
+ version = matches[1]
116
+ end
117
+
118
+ # ignore build metadata
119
+ if matches = /^([^,\s+]+)\+[^\s]+$/.match(version)
120
+ version = matches[1]
121
+ end
122
+
123
+ # match master-like branches
124
+ if matches = /^(?:dev-)?(?:master|trunk|default)$/i.match(version)
125
+ return '9999999-dev'
126
+ end
127
+
128
+ if 'dev-' === version[0...4].downcase
129
+ return "dev-#{version[4..version.size]}"
130
+ end
131
+
132
+ # match classical versioning
133
+ index = 0
134
+ if matches = /^v?(\d{1,3})(\.\d+)?(\.\d+)?(\.\d+)?#{MODIFIER_REGEX}$/i.match(version)
135
+ version = ''
136
+ matches.to_a[1..4].each do |c|
137
+ version += c ? c : '.0'
138
+ end
139
+ index = 5
140
+ elsif matches = /^v?(\d{4}(?:[.:-]?\d{2}){1,6}(?:[.:-]?\d{1,3})?)#{MODIFIER_REGEX}$/i.match(version)
141
+ version = matches[1].gsub(/\D/, '-')
142
+ index = 2
143
+ elsif matches = /^v?(\d{4,})(\.\d+)?(\.\d+)?(\.\d+)?#{MODIFIER_REGEX}$/i.match(version)
144
+ version = ''
145
+ matches.to_a[1..4].each do |c|
146
+ version << (c.nil? ? '.0' : c)
147
+ end
148
+ index = 5
149
+ end
150
+
151
+ # add version modifiers if a version was matched
152
+ if index > 0
153
+ if matches[index]
154
+ if 'stable' === matches[index]
155
+ return version
156
+ end
157
+ stability = expand_stability(matches[index])
158
+ version = "#{version}-#{stability ? stability : matches[index]}#{matches[index + 1] ? matches[index + 1] : ''}"
159
+ end
160
+
161
+ if matches[index + 2]
162
+ version = "#{version}-dev"
163
+ end
164
+
165
+ return version
166
+ end
167
+
168
+ # match dev branches
169
+ if matches = /(.*?)[.-]?dev$/i.match(version)
170
+ begin
171
+ return normalize_branch(matches[1])
172
+ rescue
173
+ end
174
+ end
175
+
176
+ extra_message = ''
177
+ if matches = / +as +#{Regexp.escape(version)}$/.match(full_version)
178
+ extra_message = " in \"#{full_version}\", the alias must be an exact version"
179
+ elsif matches = /^#{Regexp.escape(version)} +as +/.match(full_version)
180
+ extra_message = " in \"#{full_version}\", the alias source must be an exact version, if it is a branch name you should prefix it with dev-"
181
+ end
182
+
183
+ raise UnexpectedValueError, "Invalid version string \"#{version}\"#{extra_message}"
184
+ end
185
+
186
+ # Normalizes a branch name to be able to perform comparisons on it
187
+ #
188
+ # Params:
189
+ # +name+:: string The branch name to normalize
190
+ #
191
+ # Returns:
192
+ # string The normalized branch name
193
+ def normalize_branch(name)
194
+ name.strip!
195
+
196
+ if ['master', 'trunk', 'default'].include?(name)
197
+ return normalize(name)
198
+ end
199
+
200
+ if matches = /^v?(\d+)(\.(?:\d+|[xX*]))?(\.(?:\d+|[xX*]))?(\.(?:\d+|[xX*]))?$/i.match(name)
201
+ version = ''
202
+
203
+ # for i in 0..3
204
+ # # version << matches[i] ? matches[i].gsub('*', 'x').gsub('X', 'x') : '.x'
205
+ # end
206
+ matches.captures.each { |match| version << (match != nil ? match.gsub('*', 'x').gsub('X', 'x') : '.x') }
207
+ return "#{version.gsub('x', '9999999')}-dev"
208
+ end
209
+
210
+ "dev-#{name}"
211
+ end
212
+
213
+ # Params:
214
+ # +source+:: string source package name
215
+ # +source_version+:: string source package version (pretty version ideally)
216
+ # +description+:: string link description (e.g. requires, replaces, ..)
217
+ # +links+:: array An array of package name => constraint mappings
218
+ #
219
+ # Returns:
220
+ # Link[]
221
+ def parse_links(source, source_version, description, links)
222
+ res = {}
223
+ links.each do |target, constraint|
224
+ if 'self.version' === constraint
225
+ parsed_constraint = parse_constraints(source_version)
226
+ else
227
+ parsed_constraint = parse_constraints(constraint)
228
+ end
229
+ res[target.downcase] = Composer::Package::Link.new(
230
+ source,
231
+ target,
232
+ parsed_constraint,
233
+ description,
234
+ constraint
235
+ )
236
+ end
237
+ res
238
+ end
239
+
240
+ def parse_constraints(constraints)
241
+ raise ArgumentError, 'version must be specified' unless constraints
242
+ raise TypeError, 'version must be of type String' unless constraints.is_a?(String)
243
+ raise UnexpectedValueError, 'version string must not be empty' if constraints.empty?
244
+
245
+ pretty_constraint = constraints
246
+
247
+ stabilites = Composer::Package::BasePackage.stabilities.keys.join('|')
248
+ if match = /^([^,\s]*?)@(#{stabilites})$/i.match(constraints)
249
+ constraints = match[1].nil? || match[1].empty? ? '*' : match[1]
250
+ end
251
+
252
+ if match = /^(dev-[^,\s@]+?|[^,\s@]+?\.x-dev)#.+$/i.match(constraints)
253
+ constraints = match[1]
254
+ end
255
+
256
+ # or_constraints = preg_split('{\s*\|\|?\s*}', trim(constraints))
257
+ or_constraints = constraints.strip.split(/\s*\|\|?\s*/)
258
+ or_groups = []
259
+ or_constraints.each do |constraints|
260
+
261
+ # and_constraints = preg_split('{(?<!^|as|[=>< ,]) *(?<!-)[, ](?!-) *(?!,|as|$)}', constraints)
262
+ and_constraints = constraints.split(/(?<!^|as|[=>< ,]) *(?<!-)[, ](?!-) *(?!,|as|$)/)
263
+
264
+ if and_constraints.length > 1
265
+ constraint_objects = []
266
+ and_constraints.each do |constraint|
267
+ constraint_objects << parse_constraint(constraint)
268
+ end
269
+ else
270
+ constraint_objects = parse_constraint(and_constraints[0])
271
+ end
272
+
273
+ if constraint_objects.length === 1
274
+ constraint = constraint_objects[0]
275
+ else
276
+ constraint = Composer::Package::LinkConstraint::MultiConstraint.new(constraint_objects)
277
+ end
278
+
279
+ or_groups << constraint
280
+ end
281
+
282
+ if or_groups.length === 1
283
+ constraint = or_groups[0]
284
+ else
285
+ constraint = Composer::Package::LinkConstraint::MultiConstraint.new(or_groups, false)
286
+ end
287
+
288
+ constraint.pretty_string = pretty_constraint
289
+
290
+ constraint
291
+ end
292
+
293
+ # Extract numeric prefix from alias, if it is in numeric format,
294
+ # suitable for version comparison
295
+ #
296
+ # Params:
297
+ # +branch+:: string Branch name (e.g. 2.1.x-dev)
298
+ #
299
+ # Returns:
300
+ # string|false Numeric prefix if present (e.g. 2.1.) or false
301
+ def parse_numeric_alias_prefix(branch)
302
+ if matches = /^(?<version>(\d+\.)*\d+)(?:\.x)?-dev$/i.match(branch)
303
+ return "#{matches['version']}."
304
+ end
305
+ false
306
+ end
307
+
308
+ # Parses a name/version pairs and returns an array of pairs + the
309
+ #
310
+ # Params:
311
+ # +pairs+:: array a set of package/version pairs separated by ":", "=" or " "
312
+ #
313
+ # Returns:
314
+ # array[] array of arrays containing a name and (if provided) a version
315
+ def parse_name_version_pairs(pairs)
316
+ pairs = pairs.values
317
+ result = []
318
+
319
+ for i in 0..(pairs.length - 1)
320
+ pair = pairs[i].strip!.gsub(/^([^=: ]+)[=: ](.*)$/, '$1 $2')
321
+ if nil === pair.index(' ') && pairs.key?(i + 1) && nil === pairs[i + 1].index('/')
322
+ pair = "#{pair} #{pairs[i + 1]}"
323
+ i = i + 1
324
+ end
325
+
326
+ if pair.index(' ')
327
+ name, version = pair.split(' ', 2)
328
+ result << { 'name' => name, 'version' => version }
329
+ else
330
+ result << { 'name' => pair }
331
+ end
332
+
333
+ end
334
+
335
+ result
336
+ end
337
+
338
+ # PRIVATE METHODS
339
+ private
340
+
341
+ def parse_constraint(constraint)
342
+
343
+ stabilites = Composer::Package::BasePackage.stabilities.keys.join('|')
344
+ if match = /^([^,\s]+?)@(#{stabilites})$/i.match(constraint)
345
+ constraint = match[1]
346
+ if match[2] != 'stable'
347
+ stability_modifier = match[2]
348
+ end
349
+ end
350
+
351
+ if /^[xX*](\.[xX*])*$/i.match(constraint)
352
+ return [
353
+ Composer::Package::LinkConstraint::EmptyConstraint.new
354
+ ]
355
+ end
356
+
357
+ version_regex = '(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:\.(\d+))?' + MODIFIER_REGEX
358
+
359
+ # match tilde constraints
360
+ # like wildcard constraints, unsuffixed tilde constraints say that they must be greater than the previous
361
+ # version, to ensure that unstable instances of the current version are allowed.
362
+ # however, if a stability suffix is added to the constraint, then a >= match on the current version is
363
+ # used instead
364
+ if matches = /^~>?#{version_regex}$/i.match(constraint)
365
+
366
+ if constraint[0...2] === '~>'
367
+ raise UnexpectedValueError,
368
+ "Could not parse version constraint #{constraint}: \
369
+ Invalid operator \"~>\", you probably meant to use the \"~\" operator"
370
+ end
371
+
372
+ # Work out which position in the version we are operating at
373
+ if matches[4] && matches[4] != ''
374
+ position = 4
375
+ elsif matches[3] && matches[3] != ''
376
+ position = 3
377
+ elsif matches[2] && matches[2] != ''
378
+ position = 2
379
+ else
380
+ position = 1
381
+ end
382
+
383
+ # Calculate the stability suffix
384
+ stability_suffix = ''
385
+ if !matches[5].nil? && !matches[5].empty?
386
+ stability_suffix << "-#{expand_stability(matches[5])}"
387
+ if !matches[6].nil? && !matches[6].empty?
388
+ stability_suffix << matches[6]
389
+ end
390
+ end
391
+
392
+ if !matches[7].nil? && !matches[7].empty?
393
+ stability_suffix << '-dev'
394
+ end
395
+
396
+ stability_suffix = '-dev' if stability_suffix.empty?
397
+
398
+ low_version = manipulate_version_string(matches.to_a, position, 0) + stability_suffix
399
+ lower_bound = Composer::Package::LinkConstraint::VersionConstraint.new('>=', low_version)
400
+
401
+ # For upper bound, we increment the position of one more significance,
402
+ # but high_position = 0 would be illegal
403
+ high_position = [1, position - 1].max
404
+ high_version = manipulate_version_string(matches.to_a, high_position, 1) + '-dev'
405
+ upper_bound = Composer::Package::LinkConstraint::VersionConstraint.new('<', high_version)
406
+
407
+ return [
408
+ lower_bound,
409
+ upper_bound
410
+ ]
411
+ end
412
+
413
+ # match caret constraints
414
+ if matches = /^\^#{version_regex}($)/i.match(constraint)
415
+ # Create comparison array
416
+ has_match = []
417
+ for i in 0..(matches.to_a.length - 1)
418
+ has_match[i] = !matches[i].nil? && !matches[i].empty?
419
+ end
420
+
421
+ # Work out which position in the version we are operating at
422
+ if matches[1] != '0' || !has_match[2]
423
+ position = 1
424
+ elsif matches[2] != '0' || !has_match[3]
425
+ position = 2
426
+ else
427
+ position = 3
428
+ end
429
+
430
+ # Calculate the stability suffix
431
+ stability_suffix = ''
432
+ if !has_match[5] && !has_match[7]
433
+ stability_suffix << '-dev'
434
+ end
435
+
436
+ low_pretty = "#{constraint}#{stability_suffix}"
437
+ low_version = normalize(low_pretty[1..low_pretty.length - 1])
438
+ lower_bound = Composer::Package::LinkConstraint::VersionConstraint.new('>=', low_version)
439
+
440
+ # For upper bound, we increment the position of one more significance,
441
+ # but high_position = 0 would be illegal
442
+ high_version = manipulate_version_string(matches.to_a, position, 1) + '-dev'
443
+ upper_bound = Composer::Package::LinkConstraint::VersionConstraint.new('<', high_version)
444
+
445
+ return [
446
+ lower_bound,
447
+ upper_bound
448
+ ]
449
+ end
450
+
451
+ # match wildcard constraints
452
+ if matches = /^(\d+)(?:\.(\d+))?(?:\.(\d+))?\.[xX*]$/.match(constraint)
453
+ if matches[3] && matches[3] != ''
454
+ position = 3
455
+ elsif matches[2] && matches[2] != ''
456
+ position = 2
457
+ else
458
+ position = 1
459
+ end
460
+
461
+ low_version = manipulate_version_string(matches.to_a, position) + '-dev'
462
+ high_version = manipulate_version_string(matches.to_a, position, 1) + '-dev'
463
+
464
+ if low_version === "0.0.0.0-dev"
465
+ return [Composer::Package::LinkConstraint::VersionConstraint.new('<', high_version)]
466
+ end
467
+
468
+ return [
469
+ Composer::Package::LinkConstraint::VersionConstraint.new('>=', low_version),
470
+ Composer::Package::LinkConstraint::VersionConstraint.new('<', high_version),
471
+ ]
472
+ end
473
+
474
+ # match hyphen constraints
475
+ if matches = /^(#{version_regex}) +- +(#{version_regex})($)/i.match(constraint)
476
+
477
+ match_from = matches[1]
478
+ match_to = matches[9]
479
+
480
+ # Create comparison array
481
+ has_match = []
482
+ for i in 0..(matches.to_a.length - 1)
483
+ has_match[i] = !matches[i].nil? && !matches[i].empty?
484
+ end
485
+
486
+ # Calculate the stability suffix
487
+ low_stability_suffix = ''
488
+ if !has_match[6] && !has_match[8]
489
+ low_stability_suffix = '-dev'
490
+ end
491
+
492
+ low_version = normalize(match_from)
493
+ lower_bound = Composer::Package::LinkConstraint::VersionConstraint.new('>=', low_version + low_stability_suffix)
494
+
495
+ # high_version = matches[10]
496
+
497
+ if (has_match[11] && has_match[12]) || has_match[14] || has_match[16]
498
+ high_version = normalize(match_to)
499
+ upper_bound = Composer::Package::LinkConstraint::VersionConstraint.new('<=', high_version)
500
+ else
501
+ high_match = ['', matches[10], matches[11], matches[12], matches[13]]
502
+ high_version = manipulate_version_string(high_match, (!has_match[11] ? 1 : 2), 1) + '-dev'
503
+ upper_bound = Composer::Package::LinkConstraint::VersionConstraint.new('<', high_version)
504
+ end
505
+
506
+ return [
507
+ lower_bound,
508
+ upper_bound
509
+ ]
510
+ end
511
+
512
+ # match operators constraints
513
+ if matches = /^(<>|!=|>=?|<=?|==?)?\s*(.*)/.match(constraint)
514
+ begin
515
+ version = normalize(matches[2])
516
+ stability = VersionParser::parse_stability(version)
517
+ if !stability_modifier.nil? && !stability_modifier.empty? && (stability === 'stable')
518
+ version << "-#{stability_modifier}"
519
+ elsif matches[1] === '<'
520
+ unless /-#{MODIFIER_REGEX}$/.match(matches[2].downcase)
521
+ version << '-dev'
522
+ end
523
+ end
524
+ operator = matches[1].nil? ? '=' : matches[1]
525
+ return [Composer::Package::LinkConstraint::VersionConstraint.new(operator, version)]
526
+ rescue Exception => e
527
+ end
528
+ end
529
+
530
+ message = "Could not parse version constraint #{constraint}"
531
+ message << ": #{e.message}" if e
532
+ raise UnexpectedValueError, message
533
+ end
534
+
535
+ # Increment, decrement, or simply pad a version number.
536
+ # Support function for {@link parse_constraint()}
537
+ #
538
+ # Params:
539
+ # +matches+ Array with version parts in array indexes 1,2,3,4
540
+ # +position+ Integer 1,2,3,4 - which segment of the version to decrement
541
+ # +increment+ Integer
542
+ # +pad+ String The string to pad version parts after position
543
+ #
544
+ # Returns:
545
+ # string The new version
546
+ def manipulate_version_string(matches, position, increment = 0, pad = '0')
547
+ 4.downto(1).each do |i|
548
+ if i > position
549
+ matches[i] = pad
550
+ elsif i == position && increment
551
+ matches[i] = matches[i].to_i + increment
552
+ # If matches[i] was 0, carry the decrement
553
+ if matches[i] < 0
554
+ matches[i] = pad
555
+ position -= 1
556
+
557
+ # Return nil on a carry overflow
558
+ return nil if i == 1
559
+ end
560
+ end
561
+ end
562
+ "#{matches[1]}.#{matches[2]}.#{matches[3]}.#{matches[4]}"
563
+ end
564
+
565
+ def expand_stability(stability)
566
+ stability = stability.downcase
567
+ case stability
568
+ when 'a'
569
+ 'alpha'
570
+ when 'b'
571
+ 'beta'
572
+ when 'p', 'pl'
573
+ 'patch'
574
+ when 'rc'
575
+ 'RC'
576
+ else
577
+ stability
578
+ end
579
+ end
580
+ end
581
+ end
582
+ end
583
+ end