php-composer 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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