php-composer 0.1.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 (41) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +1006 -0
  5. data/Gemfile +15 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +35 -0
  8. data/Rakefile +1 -0
  9. data/lib/composer.rb +52 -0
  10. data/lib/composer/error.rb +8 -0
  11. data/lib/composer/json/json_file.rb +270 -0
  12. data/lib/composer/json/json_formatter.rb +159 -0
  13. data/lib/composer/json/json_validaton_error.rb +29 -0
  14. data/lib/composer/manager.rb +79 -0
  15. data/lib/composer/package/alias_package.rb +273 -0
  16. data/lib/composer/package/base_package.rb +130 -0
  17. data/lib/composer/package/complete_package.rb +55 -0
  18. data/lib/composer/package/dumper/hash_dumper.rb +169 -0
  19. data/lib/composer/package/link.rb +51 -0
  20. data/lib/composer/package/link_constraint/base_constraint.rb +36 -0
  21. data/lib/composer/package/link_constraint/empty_constraint.rb +35 -0
  22. data/lib/composer/package/link_constraint/multi_constraint.rb +67 -0
  23. data/lib/composer/package/link_constraint/specific_constraint.rb +41 -0
  24. data/lib/composer/package/link_constraint/version_constraint.rb +221 -0
  25. data/lib/composer/package/loader/hash_loader.rb +316 -0
  26. data/lib/composer/package/loader/json_loader.rb +47 -0
  27. data/lib/composer/package/loader/project_attributes_loader.rb +71 -0
  28. data/lib/composer/package/loader/project_root_package_loader.rb +28 -0
  29. data/lib/composer/package/package.rb +118 -0
  30. data/lib/composer/package/root_alias_package.rb +37 -0
  31. data/lib/composer/package/root_package.rb +37 -0
  32. data/lib/composer/package/version/version_parser.rb +583 -0
  33. data/lib/composer/package/version/version_selector.rb +106 -0
  34. data/lib/composer/provider.rb +94 -0
  35. data/lib/composer/repository/array_repository.rb +195 -0
  36. data/lib/composer/repository/filesystem_repository.rb +86 -0
  37. data/lib/composer/repository/writeable_array_repository.rb +60 -0
  38. data/lib/composer/version.rb +3 -0
  39. data/php-composer.gemspec +31 -0
  40. data/resources/composer-schema.json +421 -0
  41. metadata +188 -0
@@ -0,0 +1,47 @@
1
+ #
2
+ # This file was ported to ruby from Composer php source code.
3
+ # Original Source: Composer\Package\Loader\JsonLoader.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 Loader
15
+
16
+ # Loads a package from a json string or JsonFile
17
+ # @author Ioannis Kappas <ikappas@devworks.gr>
18
+ # @php_author Konstantin Kudryashiv <ever.zet@gmail.com>
19
+ class JsonLoader
20
+
21
+ def initialize(loader)
22
+ @loader = loader
23
+ end
24
+
25
+ # Load a json string or file
26
+ # Param: string|JsonFile json A filename, json string or JsonFile instance to load the package from
27
+ # Returns: Composer::Package::Package
28
+ def load(json)
29
+ if json.instance_of?(Composer::Json::JsonFile)
30
+ config = json.read
31
+ elsif File.exist?(json)
32
+ config = Composer::Json::JsonFile.parse_json(
33
+ File.open(filepath, "r") { |f| f.read },
34
+ json
35
+ )
36
+ elsif json.class === "String"
37
+ config = Composer::Json::JsonFile.parse_json(
38
+ json
39
+ )
40
+ end
41
+ @loader.load(config)
42
+ end
43
+
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,71 @@
1
+ require 'digest/crc32'
2
+
3
+ module Composer
4
+ module Package
5
+ module Loader
6
+
7
+ # Loads a package from project attributes
8
+ # @author Ioannis Kappas <ikappas@devworks.gr>
9
+ class ProjectAttributesLoader
10
+
11
+ def initialize(loader)
12
+ @loader = loader
13
+ end
14
+
15
+ # Load a json string or file
16
+ # Param: string|JsonFile json A filename, json string or JsonFile instance to load the package from
17
+ # Returns: Composer::Package::Package
18
+ def load(project, ref, type = 'library')
19
+
20
+ version = (ref.instance_of?(Gitlab::Git::Branch)) ? "dev-#{ref.name}" : ref.name
21
+
22
+ config = {
23
+ 'name' => project.path_with_namespace.gsub(/\s/, '').downcase,
24
+ 'description' => project.description,
25
+ 'version' => version,
26
+ 'uid' => Digest::CRC32.checksum(ref.name + ref.target),
27
+ 'source' => {
28
+ 'url' => project.url_to_repo,
29
+ 'type' => 'git',
30
+ 'reference' => ref.target
31
+ },
32
+ 'dist' => {
33
+ 'url' => [project.web_url, 'repository', 'archive.zip?ref=' + ref.name].join('/'),
34
+ 'type' => 'zip'
35
+ },
36
+ 'type' => type,
37
+ 'homepage' => project.web_url
38
+ }
39
+
40
+ if time = get_time(project, ref)
41
+ log("Ref: #{ref.to_json} Time: #{time}")
42
+ config['time'] = time
43
+ end
44
+
45
+ if keywords = get_keywords(project)
46
+ config['keywords'] = keywords
47
+ end
48
+
49
+ @loader.load(config)
50
+ end
51
+
52
+ private
53
+
54
+ def get_time(project, ref)
55
+ commit = project.repository.commit(ref.target)
56
+ commit.committed_date.strftime('%Y-%m-%d %H:%M:%S')
57
+ rescue
58
+ # If there's a problem, just skip the "time" field
59
+ end
60
+
61
+ def get_keywords(project)
62
+ project.tags.collect { |t| t['name'] }
63
+ end
64
+
65
+ def log(message)
66
+ Gitlab::AppLogger.error("ComposerService: #{message}")
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,28 @@
1
+ module Composer
2
+ module Package
3
+ module Loader
4
+
5
+ # Loads a package from the project root package
6
+ # @author Ioannis Kappas <ikappas@devworks.gr>
7
+ class ProjectRootPackageLoader
8
+
9
+ def initialize(loader)
10
+ @loader = loader
11
+ end
12
+
13
+ # Load a project ref
14
+ # Param: string|JsonFile json A filename, json string or JsonFile instance to load the package from
15
+ # Returns: Composer::Package::Package
16
+ def load(project, ref)
17
+
18
+ config = Composer::Json::JsonFile.parse_json(
19
+ project.repository.blob_at(ref.target, 'composer.json')
20
+ )
21
+
22
+ @loader.load(config)
23
+ end
24
+
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,118 @@
1
+ #
2
+ # This file was ported to ruby from Composer php source code file.
3
+ # Original Source: Composer\Package\Package.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
+ require 'digest/crc32'
13
+
14
+ module Composer
15
+ module Package
16
+
17
+ # Core package definitions that are needed to resolve dependencies
18
+ # and install packages
19
+ class Package < Composer::Package::BasePackage
20
+
21
+ attr_reader :stability
22
+
23
+ attr_accessor :installation_source, :source_type,
24
+ :source_url, :source_reference, :source_mirrors, :dist_type,
25
+ :dist_url, :dist_reference, :dist_sha1_checksum,
26
+ :dist_mirrors, :release_date, :extra, :binaries, :requires,
27
+ :conflicts, :provides, :replaces, :dev_requires, :suggests,
28
+ :autoload, :dev_autoload, :include_paths, :archive_excludes,
29
+ :notification_url
30
+
31
+ # complete package attributes
32
+
33
+
34
+ # Creates a new in memory package.
35
+ # Param: string name The package's name
36
+ # Param: string version The package's version
37
+ # Param: string prettyVersion The package's non-normalized version
38
+ def initialize(name, version, pretty_version)
39
+ super(name)
40
+
41
+ # default values
42
+ @extra = {}
43
+ @binaries = []
44
+ @requires = {}
45
+ @conflicts = {}
46
+ @provides = {}
47
+ @replaces = {}
48
+ @dev_requires = {}
49
+ @suggests = {}
50
+ @autoload = {}
51
+ @dev_autoload = {}
52
+ @include_paths = []
53
+ @archive_excludes = []
54
+
55
+ # init package attributes
56
+ replace_version(version, pretty_version)
57
+
58
+ end
59
+
60
+ def attributes
61
+ dumper = Composer::Package::Dumper::HashDumper.new
62
+ dumper.dump(self)
63
+ end
64
+
65
+ # Set package type
66
+ # Param: string type
67
+ def type=(type)
68
+ @type = type
69
+ end
70
+
71
+ # Get package type
72
+ # Return: string
73
+ def type
74
+ @type ? @type : 'library'
75
+ end
76
+
77
+ def target_dir=(target_dir)
78
+ @target_dir = target_dir
79
+ end
80
+
81
+ def target_dir
82
+ return unless @target_dir
83
+ regex = '(?:^|[\\\\/]+)\.\.?(?:[\\\\/]+|$)(?:\.\.?(?:[\\\\/]+|$))*'
84
+ @target_dir.gsub(/#{regex}/x, '/').gsub(/^\/+/, '')
85
+ end
86
+
87
+ # Returns package unique name, constructed from name, version and
88
+ # release type.
89
+ # Return: string
90
+ def unique_name
91
+ "#{name}-#{version}"
92
+ end
93
+
94
+ def pretty_string
95
+ "#{pretty_name} #{pretty_version}"
96
+ end
97
+
98
+ # Determine if development package
99
+ # Return: true if development package; Otherwise false.
100
+ def is_dev
101
+ @dev
102
+ end
103
+
104
+ # Replaces current version and pretty version with passed values.
105
+ # It also sets stability.
106
+ # Param: string version The package's normalized version
107
+ # Param: string prettyVersion The package's non-normalized version
108
+ def replace_version(version, pretty_version)
109
+ @version = version
110
+ @pretty_version = pretty_version
111
+
112
+ @stability = Composer::Package::Version::VersionParser::parse_stability(version)
113
+ @dev = @stability === 'dev'
114
+ end
115
+
116
+ end
117
+ end
118
+ end
@@ -0,0 +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
@@ -0,0 +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
@@ -0,0 +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