realityforge-buildr 1.5.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (85) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +5 -0
  3. data/LICENSE +176 -0
  4. data/NOTICE +26 -0
  5. data/README.md +3 -0
  6. data/Rakefile +50 -0
  7. data/addon/buildr/checkstyle-report.xsl +104 -0
  8. data/addon/buildr/checkstyle.rb +254 -0
  9. data/addon/buildr/git_auto_version.rb +36 -0
  10. data/addon/buildr/gpg.rb +90 -0
  11. data/addon/buildr/gwt.rb +413 -0
  12. data/addon/buildr/jacoco.rb +161 -0
  13. data/addon/buildr/pmd.rb +185 -0
  14. data/addon/buildr/single_intermediate_layout.rb +71 -0
  15. data/addon/buildr/spotbugs.rb +265 -0
  16. data/addon/buildr/top_level_generate_dir.rb +37 -0
  17. data/addon/buildr/wsgen.rb +192 -0
  18. data/bin/buildr +20 -0
  19. data/buildr.gemspec +61 -0
  20. data/lib/buildr.rb +86 -0
  21. data/lib/buildr/core/application.rb +705 -0
  22. data/lib/buildr/core/assets.rb +96 -0
  23. data/lib/buildr/core/build.rb +587 -0
  24. data/lib/buildr/core/common.rb +167 -0
  25. data/lib/buildr/core/compile.rb +599 -0
  26. data/lib/buildr/core/console.rb +124 -0
  27. data/lib/buildr/core/doc.rb +275 -0
  28. data/lib/buildr/core/environment.rb +128 -0
  29. data/lib/buildr/core/filter.rb +405 -0
  30. data/lib/buildr/core/help.rb +114 -0
  31. data/lib/buildr/core/progressbar.rb +161 -0
  32. data/lib/buildr/core/project.rb +994 -0
  33. data/lib/buildr/core/test.rb +776 -0
  34. data/lib/buildr/core/transports.rb +456 -0
  35. data/lib/buildr/core/util.rb +77 -0
  36. data/lib/buildr/ide/idea.rb +1664 -0
  37. data/lib/buildr/java/commands.rb +230 -0
  38. data/lib/buildr/java/compiler.rb +85 -0
  39. data/lib/buildr/java/custom_pom.rb +300 -0
  40. data/lib/buildr/java/doc.rb +62 -0
  41. data/lib/buildr/java/packaging.rb +393 -0
  42. data/lib/buildr/java/pom.rb +191 -0
  43. data/lib/buildr/java/test_result.rb +54 -0
  44. data/lib/buildr/java/tests.rb +111 -0
  45. data/lib/buildr/packaging/archive.rb +586 -0
  46. data/lib/buildr/packaging/artifact.rb +1113 -0
  47. data/lib/buildr/packaging/artifact_namespace.rb +1010 -0
  48. data/lib/buildr/packaging/artifact_search.rb +138 -0
  49. data/lib/buildr/packaging/package.rb +237 -0
  50. data/lib/buildr/packaging/version_requirement.rb +189 -0
  51. data/lib/buildr/packaging/zip.rb +189 -0
  52. data/lib/buildr/packaging/ziptask.rb +387 -0
  53. data/lib/buildr/version.rb +18 -0
  54. data/rakelib/release.rake +99 -0
  55. data/spec/addon/checkstyle_spec.rb +58 -0
  56. data/spec/core/application_spec.rb +576 -0
  57. data/spec/core/build_spec.rb +922 -0
  58. data/spec/core/common_spec.rb +670 -0
  59. data/spec/core/compile_spec.rb +656 -0
  60. data/spec/core/console_spec.rb +65 -0
  61. data/spec/core/doc_spec.rb +194 -0
  62. data/spec/core/extension_spec.rb +200 -0
  63. data/spec/core/project_spec.rb +736 -0
  64. data/spec/core/test_spec.rb +1131 -0
  65. data/spec/core/transport_spec.rb +452 -0
  66. data/spec/core/util_spec.rb +154 -0
  67. data/spec/ide/idea_spec.rb +1952 -0
  68. data/spec/java/commands_spec.rb +79 -0
  69. data/spec/java/compiler_spec.rb +274 -0
  70. data/spec/java/custom_pom_spec.rb +165 -0
  71. data/spec/java/doc_spec.rb +55 -0
  72. data/spec/java/packaging_spec.rb +786 -0
  73. data/spec/java/pom_spec.rb +162 -0
  74. data/spec/java/test_coverage_helper.rb +257 -0
  75. data/spec/java/tests_spec.rb +224 -0
  76. data/spec/packaging/archive_spec.rb +686 -0
  77. data/spec/packaging/artifact_namespace_spec.rb +757 -0
  78. data/spec/packaging/artifact_spec.rb +1351 -0
  79. data/spec/packaging/packaging_helper.rb +63 -0
  80. data/spec/packaging/packaging_spec.rb +690 -0
  81. data/spec/sandbox.rb +166 -0
  82. data/spec/spec_helpers.rb +420 -0
  83. data/spec/version_requirement_spec.rb +145 -0
  84. data/spec/xpath_matchers.rb +123 -0
  85. metadata +295 -0
@@ -0,0 +1,138 @@
1
+ # Licensed to the Apache Software Foundation (ASF) under one or more
2
+ # contributor license agreements. See the NOTICE file distributed with this
3
+ # work for additional information regarding copyright ownership. The ASF
4
+ # licenses this file to you under the Apache License, Version 2.0 (the
5
+ # "License"); you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12
+ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13
+ # License for the specific language governing permissions and limitations under
14
+ # the License.
15
+
16
+ autoload :Hpricot, 'hpricot'
17
+
18
+ module Buildr #:nodoc:
19
+
20
+ # Search best artifact version from remote repositories
21
+ module ArtifactSearch
22
+ extend self
23
+
24
+ def include(method = nil)
25
+ (@includes ||= []).tap { push method if method }
26
+ end
27
+
28
+ def exclude(method = nil)
29
+ (@excludes ||= []).tap { push method if method }
30
+ end
31
+
32
+ # TODO: return the url for best matching repo
33
+ def best_version(spec, *methods)
34
+ spec = Artifact.to_hash(spec)
35
+ spec[:version] = requirement = VersionRequirement.create(spec[:version])
36
+ select = lambda do |candidates|
37
+ candidates.find { |candidate| requirement.satisfied_by?(candidate) }
38
+ end
39
+ result = nil
40
+ methods = search_methods if methods.empty?
41
+ if requirement.composed?
42
+ until result || methods.empty?
43
+ method = methods.shift
44
+ type = method.keys.first
45
+ from = method[type]
46
+ if (include.empty? || !(include & [:all, type, from]).empty?) &&
47
+ (exclude & [:all, type, from]).empty?
48
+ if from.respond_to?(:call)
49
+ versions = from.call(spec.dup)
50
+ else
51
+ versions = send("#{type}_versions", spec.dup, *from)
52
+ end
53
+ result = select[versions]
54
+ end
55
+ end
56
+ end
57
+ result ||= requirement.default
58
+ raise "Could not find #{Artifact.to_spec(spec)}" +
59
+ "\n You may need to use an specific version instead of a requirement" unless result
60
+ spec.merge :version => result
61
+ end
62
+
63
+ def requirement?(spec)
64
+ VersionRequirement.requirement?(spec[:version])
65
+ end
66
+
67
+ private
68
+ def search_methods
69
+ [].tap do
70
+ push :runtime => [Artifact.list]
71
+ push :local => Buildr.repositories.local
72
+ Buildr.repositories.remote.each { |remote| push :remote => remote }
73
+ push :mvnrepository => []
74
+ end
75
+ end
76
+
77
+ def depend_version(spec)
78
+ spec[:version][/[\w\.]+/]
79
+ end
80
+
81
+ def runtime_versions(spec, artifacts)
82
+ spec_classif = spec.values_at(:group, :id, :type)
83
+ artifacts.inject([]) do |in_memory, str|
84
+ candidate = Artifact.to_hash(str)
85
+ if spec_classif == candidate.values_at(:group, :id, :type)
86
+ in_memory << candidate[:version]
87
+ end
88
+ in_memory
89
+ end
90
+ end
91
+
92
+ def local_versions(spec, repo)
93
+ path = (spec[:group].split(/\./) + [spec[:id]]).flatten.join('/')
94
+ Dir[File.expand_path(path + "/*", repo)].map { |d| d.pathmap("%f") }.sort.reverse
95
+ end
96
+
97
+ def remote_versions(art, base, from = :metadata, fallback = true)
98
+ path = (art[:group].split(/\./) + [art[:id]]).flatten.join('/')
99
+ base ||= "https://repo1.maven.org/maven2"
100
+ uris = {:metadata => "#{base}/#{path}/maven-metadata.xml"}
101
+ uris[:listing] = "#{base}/#{path}/" if base =~ /^https?:/
102
+ xml = nil
103
+ until xml || uris.empty?
104
+ begin
105
+ xml = URI.read(uris.delete(from))
106
+ rescue URI::NotFoundError => e
107
+ from = fallback ? uris.keys.first : nil
108
+ end
109
+ end
110
+ return [] unless xml
111
+ doc = Hpricot(xml)
112
+ case from
113
+ when :metadata then
114
+ doc.search("versions/version").map(&:innerHTML).reverse
115
+ when :listing then
116
+ doc.search("a[@href]").inject([]) { |vers, a|
117
+ vers << a.innerHTML.chop if a.innerHTML[-1..-1] == '/'
118
+ vers
119
+ }.sort.reverse
120
+ else
121
+ fail "Don't know how to parse #{from}: \n#{xml.inspect}"
122
+ end
123
+ end
124
+
125
+ def mvnrepository_versions(art)
126
+ uri = "http://www.mvnrepository.com/artifact/#{art[:group]}/#{art[:id]}"
127
+ xml = begin
128
+ URI.read(uri)
129
+ rescue URI::NotFoundError => e
130
+ puts e.class, e
131
+ return []
132
+ end
133
+ doc = Hpricot(xml)
134
+ doc.search("table.grid/tr/td[1]/a").map(&:innerHTML)
135
+ end
136
+
137
+ end # ArtifactSearch
138
+ end
@@ -0,0 +1,237 @@
1
+ # Licensed to the Apache Software Foundation (ASF) under one or more
2
+ # contributor license agreements. See the NOTICE file distributed with this
3
+ # work for additional information regarding copyright ownership. The ASF
4
+ # licenses this file to you under the Apache License, Version 2.0 (the
5
+ # "License"); you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12
+ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13
+ # License for the specific language governing permissions and limitations under
14
+ # the License.
15
+
16
+ module Buildr #:nodoc:
17
+ # Methods added to Project to support packaging and tasks for packaging,
18
+ # installing and uploading packages.
19
+ module Package
20
+
21
+ include Extension
22
+
23
+ first_time do
24
+ desc 'Create packages'
25
+ Project.local_task('package'=>'build') { |name| "Packaging #{name}" }
26
+ desc 'Install packages created by the project'
27
+ Project.local_task('install'=>'package') { |name| "Installing packages from #{name}" }
28
+ desc 'Remove previously installed packages'
29
+ Project.local_task('uninstall') { |name| "Uninstalling packages from #{name}" }
30
+ desc 'Upload packages created by the project'
31
+ Project.local_task('upload'=>'package') { |name| "Deploying packages from #{name}" }
32
+ # Anything that comes after local packaging (install, upload) executes the integration tests,
33
+ # which do not conflict with integration invoking the project's own packaging (package=>
34
+ # integration=>foo:package is not circular, just confusing to debug.)
35
+ task 'package' do
36
+ task('integration').invoke if Buildr.options.test && Buildr.application.original_dir == Dir.pwd
37
+ end
38
+ end
39
+
40
+ before_define(:package => :build) do |project|
41
+ [ :package, :install, :uninstall, :upload ].each { |name| project.recursive_task name }
42
+ # Need to run build before package, since package is often used as a dependency by tasks that
43
+ # expect build to happen.
44
+ project.task('package'=>project.task('build'))
45
+ project.group ||= project.parent && project.parent.group || project.name
46
+ project.version ||= project.parent && project.parent.version
47
+ end
48
+
49
+ after_define(:package)
50
+
51
+ # The project's identifier. Same as the project name, with colons replaced by dashes.
52
+ # The ID for project foo:bar is foo-bar.
53
+ def id
54
+ name.gsub(':', '-')
55
+ end
56
+
57
+ # Group used for packaging. Inherited from parent project. Defaults to the top-level project name.
58
+ attr_accessor :group
59
+
60
+ # Version used for packaging. Inherited from parent project.
61
+ attr_accessor :version
62
+
63
+ # :call-seq:
64
+ # package(type, spec?) => task
65
+ #
66
+ # Defines and returns a package created by this project.
67
+ #
68
+ # The first argument declares the package type. For example, :jar to create a JAR file.
69
+ # The package is an artifact that takes its artifact specification from the project.
70
+ # You can override the artifact specification by passing various options in the second
71
+ # argument, for example:
72
+ # package(:zip, :classifier=>'sources')
73
+ #
74
+ # Packages that are ZIP files provides various ways to include additional files, directories,
75
+ # and even merge ZIPs together. Have a look at ZipTask for more information. In case you're
76
+ # wondering, JAR and WAR packages are ZIP files.
77
+ #
78
+ # You can also enhance a JAR package using the ZipTask#with method that accepts the following options:
79
+ # * :manifest -- Specifies how to create the MANIFEST.MF. By default, uses the project's
80
+ # #manifest property.
81
+ # * :meta_inf -- Specifies files to be included in the META-INF directory. By default,
82
+ # uses the project's #meta-inf property.
83
+ #
84
+ # The WAR package supports the same options and adds a few more:
85
+ # * :classes -- Directories of class files to include in WEB-INF/classes. Includes the compile
86
+ # target directory by default.
87
+ # * :libs -- Artifacts and files to include in WEB-INF/libs. Includes the compile classpath
88
+ # dependencies by default.
89
+ #
90
+ # For example:
91
+ # define 'project' do
92
+ # define 'beans' do
93
+ # package :jar
94
+ # end
95
+ # define 'webapp' do
96
+ # compile.with project('beans')
97
+ # package(:war).with :libs=>MYSQL_JDBC
98
+ # end
99
+ # package(:zip, :classifier=>'sources').include path_to('.')
100
+ # end
101
+ #
102
+ # Two other packaging types are:
103
+ # * package :sources -- Creates a JAR file with the source code and classifier 'sources', for use by IDEs.
104
+ # * package :javadoc -- Creates a ZIP file with the Javadocs and classifier 'javadoc'. You can use the
105
+ # javadoc method to further customize it.
106
+ #
107
+ # A package is also an artifact. The following tasks operate on packages created by the project:
108
+ # buildr upload # Upload packages created by the project
109
+ # buildr install # Install packages created by the project
110
+ # buildr package # Create packages
111
+ # buildr uninstall # Remove previously installed packages
112
+ #
113
+ # If you want to add additional packaging types, implement a method with the name package_as_[type]
114
+ # that accepts a file name and returns an appropriate Rake task. For example:
115
+ # def package_as_zip(file_name) #:nodoc:
116
+ # ZipTask.define_task(file_name)
117
+ # end
118
+ #
119
+ # The file name is determined from the specification passed to the package method, however, some
120
+ # packagers need to override this. For example, package(:sources) produces a file with the extension
121
+ # 'jar' and the classifier 'sources'. If you need to overwrite the default implementation, you should
122
+ # also include a method named package_as_[type]_spec. For example:
123
+ # def package_as_sources_spec(spec) #:nodoc:
124
+ # # Change the source distribution to .zip extension
125
+ # spec.merge({ :type=>:zip, :classifier=>'sources' })
126
+ # end
127
+ def package(*args)
128
+ spec = Hash === args.last ? args.pop.dup : {}
129
+ no_options = spec.empty? # since spec is mutated
130
+ if spec[:file]
131
+ rake_check_options spec, :file, :type
132
+ spec[:type] = args.shift || spec[:type] || spec[:file].split('.').last.to_sym
133
+ file_name = spec[:file]
134
+ else
135
+ rake_check_options spec, *ActsAsArtifact::ARTIFACT_ATTRIBUTES
136
+ spec[:id] ||= self.id
137
+ spec[:group] ||= self.group
138
+ spec[:version] ||= self.version
139
+ spec[:type] = args.shift || spec[:type] || compile.packaging || :zip
140
+ end
141
+
142
+ packager = method("package_as_#{spec[:type]}") rescue fail("Don't know how to create a package of type #{spec[:type]}")
143
+ if packager.arity == 1
144
+ unless file_name
145
+ spec = send("package_as_#{spec[:type]}_spec", spec) if respond_to?("package_as_#{spec[:type]}_spec")
146
+ file_name = path_to(:target, Artifact.hash_to_file_name(spec))
147
+ end
148
+ package = (no_options && packages.detect { |pkg| pkg.type == spec[:type] && (pkg.id.nil? || pkg.id == spec[:id]) &&
149
+ (pkg.respond_to?(:classifier) ? pkg.classifier : nil) == spec[:classifier]}) ||
150
+ packages.find { |pkg| pkg.name == file_name } ||
151
+ packager.call(file_name)
152
+ else
153
+ Buildr.application.deprecated "We changed the way package_as methods are implemented. See the package method documentation for more details."
154
+ file_name ||= path_to(:target, Artifact.hash_to_file_name(spec))
155
+ package = packager.call(file_name, spec)
156
+ end
157
+
158
+ # First time: prepare package for install, uninstall and upload tasks.
159
+ unless packages.include?(package)
160
+ # We already run build before package, but we also need to do so if the package itself is
161
+ # used as a dependency, before we get to run the package task.
162
+ task 'package'=>package
163
+ package.enhance [task('build')]
164
+ package.enhance { info "Packaging #{File.basename(file_name)}" }
165
+ if spec[:file]
166
+ class << package ; self ; end.send(:define_method, :type) { spec[:type] }
167
+ class << package ; self ; end.send(:define_method, :id) { nil }
168
+ else
169
+ # Make it an artifact using the specifications, and tell it how to create a POM.
170
+ package.extend ActsAsArtifact
171
+ package.buildr_project = self
172
+ package.send :apply_spec, spec.dup.delete_if{|key, _|!ActsAsArtifact::ARTIFACT_ATTRIBUTES.include?(key)}
173
+
174
+ # Create pom associated with package
175
+ class << package
176
+ def pom
177
+ unless @pom
178
+ pom_filename = self.name.sub(/\.[^.]+\z/, '.pom')
179
+ spec = {:group=>group, :id=>id, :version=>version, :type=>:pom}
180
+ @pom = Buildr.artifact(spec, pom_filename)
181
+ @pom.content Buildr::CustomPom.pom_xml(self.buildr_project, self)
182
+ end
183
+ @pom
184
+ end
185
+ end if package.classifier.nil?
186
+
187
+ file(Buildr.repositories.locate(package)=>package) { package.install }
188
+
189
+ # Add the package to the list of packages created by this project, and
190
+ # register it as an artifact. The later is required so if we look up the spec
191
+ # we find the package in the project's target directory, instead of finding it
192
+ # in the local repository and attempting to install it.
193
+ Artifact.register package
194
+ Artifact.register package.pom if package.classifier.nil?
195
+ end
196
+
197
+ task('install') { package.install if package.respond_to?(:install) }
198
+ task('uninstall') { package.uninstall if package.respond_to?(:uninstall) }
199
+ task('upload') { package.upload if package.respond_to?(:upload) }
200
+
201
+ packages << package
202
+ end
203
+ package
204
+ end
205
+
206
+ # :call-seq:
207
+ # packages => tasks
208
+ #
209
+ # Returns all packages created by this project. A project may create any number of packages.
210
+ #
211
+ # This method is used whenever you pass a project to Buildr#artifact or any other method
212
+ # that accepts artifact specifications and projects. You can use it to list all packages
213
+ # created by the project. If you want to return a specific package, it is often more
214
+ # convenient to call #package with the type.
215
+ def packages
216
+ @packages ||= []
217
+ end
218
+
219
+ def package_as_zip(file_name) #:nodoc:
220
+ ZipTask.define_task(file_name)
221
+ end
222
+
223
+ def package_as_sources_spec(spec) #:nodoc:
224
+ spec.merge(:type=>:jar, :classifier=>'sources')
225
+ end
226
+
227
+ def package_as_sources(file_name) #:nodoc:
228
+ ZipTask.define_task(file_name).tap do |zip|
229
+ zip.include :from=>[compile.sources, resources.target].compact
230
+ end
231
+ end
232
+ end
233
+ end
234
+
235
+ class Buildr::Project
236
+ include Buildr::Package
237
+ end
@@ -0,0 +1,189 @@
1
+ # Licensed to the Apache Software Foundation (ASF) under one or more
2
+ # contributor license agreements. See the NOTICE file distributed with this
3
+ # work for additional information regarding copyright ownership. The ASF
4
+ # licenses this file to you under the Apache License, Version 2.0 (the
5
+ # "License"); you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12
+ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13
+ # License for the specific language governing permissions and limitations under
14
+ # the License.
15
+
16
+ # Rubygems 1.3.6 removed the 'version' accessor so monkey-patch it back to
17
+ # circumvent version validation. This is needed because Gem::Version doesn't
18
+ # accept version specs with dashes.
19
+ unless Gem::Version.new("0").respond_to?(:version=)
20
+ class Gem::Version
21
+ def version=(version)
22
+ @version = version.to_s.strip
23
+
24
+ # re-prime @segments
25
+ @segments = nil
26
+ @canonical_segments = nil
27
+ segments
28
+ end
29
+ end
30
+ end
31
+
32
+ module Buildr #:nodoc:
33
+
34
+ #
35
+ # See ArtifactNamespace#need
36
+ class VersionRequirement
37
+
38
+ CMP_PROCS = Gem::Requirement::OPS.dup
39
+ CMP_REGEX = Gem::Requirement::OPS.keys.map { |k| Regexp.quote k }.join "|"
40
+ CMP_CHARS = CMP_PROCS.keys.join
41
+ BOOL_CHARS = '\|\&\!'
42
+ VER_CHARS = '\w\.\-'
43
+
44
+ class << self
45
+ # is +str+ a version string?
46
+ def version?(str)
47
+ /^\s*r?\d[#{VER_CHARS}]*\s*$/ === str
48
+ end
49
+
50
+ # is +str+ a version requirement?
51
+ def requirement?(str)
52
+ /[#{BOOL_CHARS}#{CMP_CHARS}\(\)]/ === str
53
+ end
54
+
55
+ # :call-seq:
56
+ # VersionRequirement.create(" >1 <2 !(1.5) ") -> requirement
57
+ #
58
+ # parse the +str+ requirement
59
+ def create(str)
60
+ instance_eval normalize(str)
61
+ rescue StandardError => e
62
+ raise "Failed to parse #{str.inspect} due to: #{e}"
63
+ end
64
+
65
+ private
66
+ def requirement(req)
67
+ unless req =~ /^\s*(#{CMP_REGEX})?\s*([#{VER_CHARS}]+)\s*$/
68
+ raise "Invalid requirement string: #{req}"
69
+ end
70
+ comparator, version = $1, $2
71
+ # dup required due to jruby 1.7.13 bug/feature that caches versions?
72
+ version = Gem::Version.new(0).dup.tap { |v| v.version = version }
73
+ VersionRequirement.new(nil, [$1, version])
74
+ end
75
+
76
+ def negate(vreq)
77
+ vreq.negative = !vreq.negative
78
+ vreq
79
+ end
80
+
81
+ def normalize(str)
82
+ str = str.strip
83
+ if str[/[^\s\(\)#{BOOL_CHARS + VER_CHARS + CMP_CHARS}]/]
84
+ raise "version string #{str.inspect} contains invalid characters"
85
+ end
86
+ str.gsub!(/\s+(and|&&)\s+/, ' & ')
87
+ str.gsub!(/\s+(or|\|\|)\s+/, ' | ')
88
+ str.gsub!(/(^|\s*)not\s+/, ' ! ')
89
+ pattern = /(#{CMP_REGEX})?\s*[#{VER_CHARS}]+/
90
+ left_pattern = /[#{VER_CHARS}\)]$/
91
+ right_pattern = /^(#{pattern}|\()/
92
+ str = str.split.inject([]) do |ary, i|
93
+ ary << '&' if ary.last =~ left_pattern && i =~ right_pattern
94
+ ary << i
95
+ end
96
+ str = str.join(' ')
97
+ str.gsub!(/!([^=])?/, ' negate \1')
98
+ str.gsub!(pattern) do |expr|
99
+ case expr.strip
100
+ when 'not', 'negate' then 'negate '
101
+ else 'requirement("' + expr + '")'
102
+ end
103
+ end
104
+ str.gsub!(/negate\s+\(/, 'negate(')
105
+ str
106
+ end
107
+ end
108
+
109
+ def initialize(op, *requirements) #:nodoc:
110
+ @op, @requirements = op, requirements
111
+ end
112
+
113
+ # Is this object a composed requirement?
114
+ # VersionRequirement.create('1').composed? -> false
115
+ # VersionRequirement.create('1 | 2').composed? -> true
116
+ # VersionRequirement.create('1 & 2').composed? -> true
117
+ def composed?
118
+ requirements.size > 1
119
+ end
120
+
121
+ # Return the last requirement on this object having an = operator.
122
+ def default
123
+ default = nil
124
+ requirements.reverse.find do |r|
125
+ if Array === r
126
+ if !negative && (r.first.nil? || r.first.include?('='))
127
+ default = r.last.to_s
128
+ end
129
+ else
130
+ default = r.default
131
+ end
132
+ end
133
+ default
134
+ end
135
+
136
+ # Test if this requirement can be satisfied by +version+
137
+ def satisfied_by?(version)
138
+ return false unless version
139
+ unless version.kind_of?(Gem::Version)
140
+ raise "Invalid version: #{version.inspect}" unless self.class.version?(version)
141
+ # dup required due to jruby 1.7.13 bug/feature that caches versions?
142
+ version = Gem::Version.new(0).dup.tap { |v| v.version = version.strip }
143
+ end
144
+ message = op == :| ? :any? : :all?
145
+ result = requirements.send message do |req|
146
+ if Array === req
147
+ cmp, rv = *req
148
+ CMP_PROCS[cmp || '='].call(version, rv)
149
+ else
150
+ req.satisfied_by?(version)
151
+ end
152
+ end
153
+ negative ? !result : result
154
+ end
155
+
156
+ # Either modify the current requirement (if it's already an or operation)
157
+ # or create a new requirement
158
+ def |(other)
159
+ operation(:|, other)
160
+ end
161
+
162
+ # Either modify the current requirement (if it's already an and operation)
163
+ # or create a new requirement
164
+ def &(other)
165
+ operation(:&, other)
166
+ end
167
+
168
+ # return the parsed expression
169
+ def to_s
170
+ str = requirements.map(&:to_s).join(" " + @op.to_s + " ").to_s
171
+ str = "( " + str + " )" if negative || requirements.size > 1
172
+ str = "!" + str if negative
173
+ str
174
+ end
175
+
176
+ attr_accessor :negative
177
+ protected
178
+ attr_reader :requirements, :op
179
+ def operation(op, other)
180
+ @op ||= op
181
+ if negative == other.negative && @op == op && other.requirements.size == 1
182
+ @requirements << other.requirements.first
183
+ self
184
+ else
185
+ self.class.new(op, self, other)
186
+ end
187
+ end
188
+ end # VersionRequirement
189
+ end