buildr 1.3.3-java → 1.3.4-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (144) hide show
  1. data/CHANGELOG +76 -0
  2. data/NOTICE +1 -1
  3. data/README.rdoc +9 -21
  4. data/Rakefile +17 -34
  5. data/_buildr +3 -12
  6. data/{doc/print.toc.yaml → _jbuildr} +14 -14
  7. data/addon/buildr/cobertura.rb +5 -219
  8. data/addon/buildr/drb.rb +281 -0
  9. data/addon/buildr/emma.rb +5 -221
  10. data/addon/buildr/nailgun.rb +93 -689
  11. data/bin/buildr +0 -9
  12. data/buildr.buildfile +4 -4
  13. data/buildr.gemspec +27 -21
  14. data/doc/_layouts/default.html +82 -0
  15. data/doc/_layouts/preface.html +22 -0
  16. data/doc/{pages/artifacts.textile → artifacts.textile} +82 -42
  17. data/doc/{pages/building.textile → building.textile} +89 -47
  18. data/doc/{pages/contributing.textile → contributing.textile} +53 -45
  19. data/doc/css/default.css +6 -5
  20. data/doc/css/print.css +17 -24
  21. data/doc/css/syntax.css +7 -36
  22. data/doc/download.textile +68 -0
  23. data/doc/{pages/extending.textile → extending.textile} +45 -24
  24. data/doc/{pages/getting_started.textile → getting_started.textile} +158 -88
  25. data/doc/images/asf-logo.gif +0 -0
  26. data/doc/images/note.png +0 -0
  27. data/doc/index.textile +47 -0
  28. data/doc/{pages/languages.textile → languages.textile} +108 -54
  29. data/doc/mailing_lists.textile +25 -0
  30. data/doc/{pages/more_stuff.textile → more_stuff.textile} +152 -73
  31. data/doc/{pages/packaging.textile → packaging.textile} +181 -96
  32. data/doc/preface.textile +28 -0
  33. data/doc/{pages/projects.textile → projects.textile} +55 -40
  34. data/doc/scripts/buildr-git.rb +364 -264
  35. data/doc/scripts/gitflow.rb +296 -0
  36. data/doc/scripts/install-jruby.sh +2 -2
  37. data/doc/scripts/install-linux.sh +6 -6
  38. data/doc/scripts/install-osx.sh +2 -2
  39. data/doc/{pages/settings_profiles.textile → settings_profiles.textile} +83 -45
  40. data/doc/{pages/testing.textile → testing.textile} +77 -41
  41. data/lib/buildr.rb +5 -5
  42. data/lib/buildr/core.rb +2 -0
  43. data/lib/buildr/core/application.rb +321 -151
  44. data/lib/buildr/core/build.rb +298 -167
  45. data/lib/buildr/core/checks.rb +4 -132
  46. data/lib/buildr/core/common.rb +1 -5
  47. data/lib/buildr/core/compile.rb +3 -9
  48. data/lib/buildr/core/environment.rb +12 -3
  49. data/lib/buildr/core/filter.rb +20 -18
  50. data/lib/buildr/core/generate.rb +36 -36
  51. data/lib/buildr/core/help.rb +2 -1
  52. data/lib/buildr/core/osx.rb +46 -0
  53. data/lib/buildr/core/progressbar.rb +1 -1
  54. data/lib/buildr/core/project.rb +7 -34
  55. data/lib/buildr/core/test.rb +12 -6
  56. data/lib/buildr/core/transports.rb +13 -11
  57. data/lib/buildr/core/util.rb +14 -23
  58. data/lib/buildr/groovy/bdd.rb +3 -2
  59. data/lib/buildr/groovy/compiler.rb +1 -1
  60. data/lib/buildr/ide/eclipse.rb +31 -21
  61. data/lib/buildr/ide/idea.rb +3 -2
  62. data/lib/buildr/ide/idea7x.rb +6 -4
  63. data/lib/buildr/java/ant.rb +3 -1
  64. data/lib/buildr/java/bdd.rb +9 -7
  65. data/lib/buildr/java/cobertura.rb +243 -0
  66. data/lib/buildr/java/compiler.rb +5 -4
  67. data/lib/buildr/java/emma.rb +244 -0
  68. data/lib/buildr/java/packaging.rb +11 -8
  69. data/lib/buildr/java/pom.rb +0 -4
  70. data/lib/buildr/java/rjb.rb +1 -1
  71. data/lib/buildr/java/test_result.rb +5 -7
  72. data/lib/buildr/java/tests.rb +17 -11
  73. data/lib/buildr/packaging.rb +5 -2
  74. data/lib/buildr/packaging/archive.rb +488 -0
  75. data/lib/buildr/packaging/artifact.rb +48 -29
  76. data/lib/buildr/packaging/artifact_namespace.rb +6 -6
  77. data/lib/buildr/packaging/gems.rb +4 -4
  78. data/lib/buildr/packaging/package.rb +3 -2
  79. data/lib/buildr/packaging/tar.rb +85 -3
  80. data/lib/buildr/packaging/version_requirement.rb +172 -0
  81. data/lib/buildr/packaging/zip.rb +24 -682
  82. data/lib/buildr/packaging/ziptask.rb +313 -0
  83. data/lib/buildr/scala.rb +5 -0
  84. data/lib/buildr/scala/bdd.rb +100 -0
  85. data/lib/buildr/scala/compiler.rb +45 -4
  86. data/lib/buildr/scala/tests.rb +12 -59
  87. data/rakelib/checks.rake +57 -0
  88. data/rakelib/doc.rake +58 -68
  89. data/rakelib/jekylltask.rb +110 -0
  90. data/rakelib/package.rake +35 -37
  91. data/rakelib/release.rake +119 -35
  92. data/rakelib/rspec.rake +29 -39
  93. data/rakelib/setup.rake +21 -59
  94. data/rakelib/stage.rake +184 -26
  95. data/spec/addon/drb_spec.rb +328 -0
  96. data/spec/core/application_spec.rb +32 -25
  97. data/spec/core/build_spec.rb +336 -126
  98. data/spec/core/checks_spec.rb +292 -310
  99. data/spec/core/common_spec.rb +8 -2
  100. data/spec/core/compile_spec.rb +17 -1
  101. data/spec/core/generate_spec.rb +3 -3
  102. data/spec/core/project_spec.rb +18 -10
  103. data/spec/core/test_spec.rb +8 -1
  104. data/spec/core/transport_spec.rb +40 -3
  105. data/spec/core/util_spec.rb +67 -0
  106. data/spec/ide/eclipse_spec.rb +96 -28
  107. data/spec/ide/idea7x_spec.rb +84 -0
  108. data/spec/java/ant.rb +5 -0
  109. data/spec/java/bdd_spec.rb +12 -3
  110. data/spec/{addon → java}/cobertura_spec.rb +6 -6
  111. data/spec/{addon → java}/emma_spec.rb +5 -6
  112. data/spec/java/java_spec.rb +12 -2
  113. data/spec/java/packaging_spec.rb +31 -2
  114. data/spec/{addon → java}/test_coverage_spec.rb +3 -3
  115. data/spec/java/tests_spec.rb +5 -0
  116. data/spec/packaging/archive_spec.rb +11 -1
  117. data/spec/{core → packaging}/artifact_namespace_spec.rb +10 -2
  118. data/spec/packaging/artifact_spec.rb +44 -3
  119. data/spec/packaging/packaging_spec.rb +1 -1
  120. data/spec/sandbox.rb +17 -14
  121. data/spec/scala/bdd_spec.rb +150 -0
  122. data/spec/scala/compiler_spec.rb +27 -0
  123. data/spec/scala/scala.rb +38 -0
  124. data/spec/scala/tests_spec.rb +78 -33
  125. data/spec/spec_helpers.rb +29 -5
  126. data/spec/version_requirement_spec.rb +6 -0
  127. metadata +176 -172
  128. data/DISCLAIMER +0 -7
  129. data/doc/images/apache-incubator-logo.png +0 -0
  130. data/doc/pages/download.textile +0 -51
  131. data/doc/pages/index.textile +0 -42
  132. data/doc/pages/mailing_lists.textile +0 -17
  133. data/doc/pages/recipes.textile +0 -103
  134. data/doc/pages/troubleshooting.textile +0 -103
  135. data/doc/pages/whats_new.textile +0 -323
  136. data/doc/print.haml +0 -51
  137. data/doc/site.haml +0 -56
  138. data/doc/site.toc.yaml +0 -47
  139. data/etc/git-svn-authors +0 -16
  140. data/lib/buildr/core/application_cli.rb +0 -139
  141. data/rakelib/apache.rake +0 -191
  142. data/rakelib/changelog.rake +0 -57
  143. data/rakelib/rubyforge.rake +0 -53
  144. data/rakelib/scm.rake +0 -49
@@ -14,7 +14,6 @@
14
14
  # the License.
15
15
 
16
16
 
17
- require 'builder'
18
17
  require 'buildr/core/project'
19
18
  require 'buildr/core/transports'
20
19
  require 'buildr/packaging/artifact_namespace'
@@ -25,6 +24,9 @@ module Buildr
25
24
  desc 'Download all artifacts'
26
25
  task 'artifacts'
27
26
 
27
+ desc "Download all artifacts' sources"
28
+ task 'artifacts:sources'
29
+
28
30
  # Mixin with a task to make it behave like an artifact. Implemented by the packaging tasks.
29
31
  #
30
32
  # An artifact has an identifier, group identifier, type, version number and
@@ -81,7 +83,7 @@ module Buildr
81
83
  # Returns the artifact specification, in the structure:
82
84
  # <group>:<artifact>:<type>:<version>
83
85
  # or
84
- # <group>:<artifact>:<type>:<classifier><:version>
86
+ # <group>:<artifact>:<type>:<classifier>:<version>
85
87
  def to_spec
86
88
  classifier ? "#{group}:#{id}:#{type}:#{classifier}:#{version}" : "#{group}:#{id}:#{type}:#{version}"
87
89
  end
@@ -95,6 +97,17 @@ module Buildr
95
97
  Buildr.artifact(:group=>group, :id=>id, :version=>version, :type=>:pom)
96
98
  end
97
99
 
100
+ # :call-seq:
101
+ # sources_artifact => Artifact
102
+ #
103
+ # Convenience method that returns a sources artifact.
104
+ def sources_artifact
105
+ sources_spec = to_spec_hash.merge(:classifier=>'sources')
106
+ sources_task = OptionalArtifact.define_task(Buildr.repositories.locate(sources_spec))
107
+ sources_task.send :apply_spec, sources_spec
108
+ sources_task
109
+ end
110
+
98
111
  # :call-seq:
99
112
  # pom_xml => string
100
113
  #
@@ -110,26 +123,22 @@ module Buildr
110
123
  xml.classifier classifier if classifier
111
124
  end
112
125
  end
113
-
126
+
114
127
  def install
115
128
  pom.install if pom && pom != self
116
129
  invoke
117
130
  installed = Buildr.repositories.locate(self)
118
131
  unless installed == name # If not already in local repository.
119
- verbose(Buildr.application.options.trace || false) do
120
- mkpath File.dirname(installed)
121
- cp name, installed
122
- end
132
+ mkpath File.dirname(installed)
133
+ cp name, installed
123
134
  info "Installed #{installed}"
124
135
  end
125
136
  end
126
137
 
127
138
  def uninstall
128
- verbose(Buildr.application.options.trace || false) do
129
- installed = Buildr.repositories.locate(self)
130
- rm installed if File.exist?(installed)
131
- pom.uninstall if pom && pom != self
132
- end
139
+ installed = Buildr.repositories.locate(self)
140
+ rm installed if File.exist?(installed)
141
+ pom.uninstall if pom && pom != self
133
142
  end
134
143
 
135
144
  # :call-seq:
@@ -312,23 +321,19 @@ module Buildr
312
321
  # Use this when you want to install or upload an artifact from a given file, for example:
313
322
  # test = artifact('group:id:jar:1.0').from('test.jar')
314
323
  # install test
315
- # See also Buildr#install and Buildr#deploy.
324
+ # See also Buildr#install and Buildr#upload.
316
325
  def from(path)
317
326
  path = File.expand_path(path.to_s)
318
327
  enhance [path] do
319
- verbose false do
320
- mkpath File.dirname(name)
321
- pom.invoke unless type == :pom
322
- cp path, name
323
- info "Installed #{path} as #{to_spec}"
324
- end
328
+ mkpath File.dirname(name)
329
+ pom.invoke unless type == :pom
330
+ cp path, name
331
+ info "Installed #{path} as #{to_spec}"
325
332
  end
326
333
  unless type == :pom
327
334
  pom.enhance do
328
- verbose false do
329
- mkpath File.dirname(pom.name)
330
- File.open(pom.name, 'w') { |file| file.write pom.pom_xml }
331
- end
335
+ mkpath File.dirname(pom.name)
336
+ File.open(pom.name, 'w') { |file| file.write pom.pom_xml }
332
337
  end
333
338
  end
334
339
  self
@@ -340,11 +345,10 @@ module Buildr
340
345
  # download
341
346
  #
342
347
  # Downloads an artifact from one of the remote repositories, and stores it in the local
343
- # repository. Accepts a String or Hash artifact specification, and returns a path to the
344
- # artifact in the local repository. Raises an exception if the artifact is not found.
348
+ # repository. Raises an exception if the artifact is not found.
345
349
  #
346
350
  # This method attempts to download the artifact from each repository in the order in
347
- # which they are returned from #remote, until successful. It always downloads the POM first.
351
+ # which they are returned from #remote, until successful.
348
352
  def download
349
353
  trace "Downloading #{to_spec}"
350
354
  remote = Buildr.repositories.remote.map { |repo_url| URI === repo_url ? repo_url : URI.parse(repo_url) }
@@ -407,6 +411,22 @@ module Buildr
407
411
  fail "Failed to download #{to_spec}, tried the following repositories:\n#{remote_uris.join("\n")}"
408
412
  end
409
413
  end
414
+
415
+
416
+ # An artifact that is optional.
417
+ # If downloading fails, the user will be informed but it will not raise an exception.
418
+ class OptionalArtifact < Artifact
419
+
420
+ protected
421
+
422
+ # If downloading fails, the user will be informed but it will not raise an exception.
423
+ def download
424
+ super
425
+ rescue
426
+ info "Failed to download #{to_spec}. Skipping it."
427
+ end
428
+
429
+ end
410
430
 
411
431
 
412
432
  # Holds the path to the local repository, URLs for remote repositories, and settings for release server.
@@ -594,6 +614,7 @@ module Buildr
594
614
  task.send :apply_spec, spec
595
615
  Rake::Task['rake:artifacts'].enhance [task]
596
616
  Artifact.register(task)
617
+ Rake::Task['artifacts:sources'].enhance [task.sources_artifact] unless spec[:type] == :pom
597
618
  end
598
619
  task.enhance &block
599
620
  end
@@ -701,9 +722,7 @@ module Buildr
701
722
  task('install').tap do |task|
702
723
  task.enhance all, &block
703
724
  task 'uninstall' do
704
- verbose false do
705
- all.map(&:to_s ).each { |file| rm file if File.exist?(file) }
706
- end
725
+ all.map(&:to_s ).each { |file| rm file if File.exist?(file) }
707
726
  end
708
727
  end
709
728
  end
@@ -14,7 +14,7 @@
14
14
  # the License.
15
15
 
16
16
 
17
- require 'buildr/java/version_requirement'
17
+ require 'buildr/packaging/version_requirement'
18
18
 
19
19
 
20
20
  module Buildr
@@ -220,7 +220,7 @@ module Buildr
220
220
  # Forget all namespaces, create a new ROOT
221
221
  def clear
222
222
  @instances = nil
223
- remove_const(:ROOT) if const_defined?(:ROOT)
223
+ remove_const(:ROOT) rescue nil
224
224
  const_set(:ROOT, new('root'))
225
225
  end
226
226
 
@@ -737,7 +737,7 @@ module Buildr
737
737
  # Like Hash#fetch
738
738
  def fetch(name, default = nil, &block)
739
739
  block ||= lambda { raise IndexError.new("No artifact found by name #{name.inspect} in namespace #{self}") }
740
- real_name = name.to_s[/^\w+$/] ? name : ArtifactRequirement.unversioned_spec(name)
740
+ real_name = name.to_s[/^[\w\-\.]+$/] ? name : ArtifactRequirement.unversioned_spec(name)
741
741
  get(real_name.to_sym) || default || block.call(name)
742
742
  end
743
743
 
@@ -794,7 +794,7 @@ module Buildr
794
794
  def values_at(*names)
795
795
  names.map do |name|
796
796
  catch :artifact do
797
- unless name.to_s[/^\w+$/]
797
+ unless name.to_s[/^[\w\-\.]+$/]
798
798
  unvers = ArtifactRequirement.unversioned_spec(name)
799
799
  unless unvers.to_s == name.to_s
800
800
  req = ArtifactRequirement.new(name)
@@ -812,7 +812,7 @@ module Buildr
812
812
  end
813
813
 
814
814
  def key?(name, include_parents = false)
815
- name = ArtifactRequirement.unversioned_spec(name) unless name.to_s[/^\w+$/]
815
+ name = ArtifactRequirement.unversioned_spec(name) unless name.to_s[/^[\w\-\.]+$/]
816
816
  registry.key?(name, include_parents)
817
817
  end
818
818
 
@@ -883,7 +883,7 @@ module Buildr
883
883
  case name.to_s
884
884
  when /!$/ then
885
885
  name = $`.intern
886
- if args.size < 1 && args.size > 2
886
+ if args.size < 1 || args.size > 2
887
887
  raise ArgumentError.new("wrong number of arguments for #{name}!(spec, version_requirement?)")
888
888
  end
889
889
  need name => args.first
@@ -15,9 +15,9 @@
15
15
 
16
16
 
17
17
  require 'buildr/packaging/package'
18
- require 'buildr/packaging/zip'
19
- require 'rubyforge'
20
- require 'rubygems/package'
18
+ require 'buildr/packaging/archive'
19
+ gem 'rubyforge' ; autoload :RubyForge, 'rubyforge'
20
+ Gem.autoload :Package, 'rubygems/package'
21
21
 
22
22
 
23
23
  module Buildr
@@ -75,7 +75,7 @@ module Buildr
75
75
  end
76
76
 
77
77
 
78
- module PackageAsGem
78
+ module PackageAsGem #:nodoc:
79
79
 
80
80
  def package_as_gem(file_name) #:nodoc:
81
81
  PackageGemTask.define_task(file_name).tap do |gem|
@@ -35,7 +35,7 @@ module Buildr
35
35
  Project.local_task('uninstall') { |name| "Uninstalling packages from #{name}" }
36
36
  desc 'Upload packages created by the project'
37
37
  Project.local_task('upload'=>'package') { |name| "Deploying packages from #{name}" }
38
- # Anything that comes after local packaging (install, deploy) executes the integration tests,
38
+ # Anything that comes after local packaging (install, upload) executes the integration tests,
39
39
  # which do not conflict with integration invoking the project's own packaging (package=>
40
40
  # integration=>foo:package is not circular, just confusing to debug.)
41
41
  task 'package' do
@@ -160,6 +160,7 @@ module Buildr
160
160
  # used as a dependency, before we get to run the package task.
161
161
  task 'package'=>package
162
162
  package.enhance [task('build')]
163
+ package.enhance { info "Packaging #{File.basename(file_name)}" }
163
164
 
164
165
  if spec[:file]
165
166
  class << package ; self ; end.send(:define_method, :type) { spec[:type] }
@@ -170,7 +171,7 @@ module Buildr
170
171
  # Another task to create the POM file.
171
172
  pom = package.pom
172
173
  pom.enhance do
173
- mkpath File.dirname(pom.name), :verbose=>false
174
+ mkpath File.dirname(pom.name)
174
175
  File.open(pom.name, 'w') { |file| file.write pom.pom_xml }
175
176
  end
176
177
  file(Buildr.repositories.locate(package)=>package) { package.install }
@@ -14,8 +14,8 @@
14
14
  # the License.
15
15
 
16
16
 
17
- require 'buildr/packaging/zip'
18
- require 'archive/tar/minitar'
17
+ require 'buildr/packaging/archive'
18
+ gem 'archive-tar-minitar' ; autoload :Archive, 'archive/tar/minitar'
19
19
 
20
20
 
21
21
  module Buildr
@@ -45,6 +45,35 @@ module Buildr
45
45
  self.mode = '0755'
46
46
  end
47
47
 
48
+ # :call-seq:
49
+ # entry(name) => Entry
50
+ #
51
+ # Returns a Tar file entry. You can use this to check if the entry exists and its contents,
52
+ # for example:
53
+ # package(:tar).entry("src/LICENSE").should contain(/Apache Software License/)
54
+ def entry(entry_name)
55
+ Buildr::TarEntry.new(self, entry_name)
56
+ end
57
+
58
+ def entries() #:nodoc:
59
+ tar_entries = nil
60
+ with_uncompressed_tar { |tar| tar_entries = tar.entries }
61
+ tar_entries
62
+ end
63
+
64
+ # :call-seq:
65
+ # with_uncompressed_tar { |tar_entries| ... }
66
+ #
67
+ # Yields an Archive::Tar::Minitar::Input object to the provided block.
68
+ # Opening, closing and Gzip-decompressing is automatically taken care of.
69
+ def with_uncompressed_tar &block
70
+ if gzip
71
+ Zlib::GzipReader.open(name) { |tar| Archive::Tar::Minitar.open(tar, &block) }
72
+ else
73
+ Archive::Tar::Minitar.open(name, &block)
74
+ end
75
+ end
76
+
48
77
  private
49
78
 
50
79
  def create_from(file_map)
@@ -81,7 +110,60 @@ module Buildr
81
110
  end
82
111
 
83
112
  end
84
-
113
+
114
+
115
+ class TarEntry #:nodoc:
116
+
117
+ def initialize(tar_task, entry_name)
118
+ @tar_task = tar_task
119
+ @entry_name = entry_name
120
+ end
121
+
122
+ # :call-seq:
123
+ # contain?(*patterns) => boolean
124
+ #
125
+ # Returns true if this Tar file entry matches against all the arguments. An argument may be
126
+ # a string or regular expression.
127
+ def contain?(*patterns)
128
+ content = read_content_from_tar
129
+ patterns.map { |pattern| Regexp === pattern ? pattern : Regexp.new(Regexp.escape(pattern.to_s)) }.
130
+ all? { |pattern| content =~ pattern }
131
+ end
132
+
133
+ # :call-seq:
134
+ # empty?() => boolean
135
+ #
136
+ # Returns true if this entry is empty.
137
+ def empty?()
138
+ read_content_from_tar.nil?
139
+ end
140
+
141
+ # :call-seq:
142
+ # exist() => boolean
143
+ #
144
+ # Returns true if this entry exists.
145
+ def exist?()
146
+ exist = false
147
+ @tar_task.with_uncompressed_tar { |tar| exist = tar.any? { |entry| entry.name == @entry_name } }
148
+ exist
149
+ end
150
+
151
+ def to_s #:nodoc:
152
+ @entry_name
153
+ end
154
+
155
+ private
156
+
157
+ def read_content_from_tar
158
+ content = Errno::ENOENT.new("No such file or directory - #{@entry_name}")
159
+ @tar_task.with_uncompressed_tar do |tar|
160
+ content = tar.inject(content) { |content, entry| entry.name == @entry_name ? entry.read : content }
161
+ end
162
+ raise content if Exception === content
163
+ content
164
+ end
165
+ end
166
+
85
167
  end
86
168
 
87
169
 
@@ -0,0 +1,172 @@
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
+
17
+ module Buildr
18
+
19
+ #
20
+ # See ArtifactNamespace#need
21
+ class VersionRequirement
22
+
23
+ CMP_PROCS = Gem::Requirement::OPS.dup
24
+ CMP_REGEX = Gem::Requirement::OP_RE.dup
25
+ CMP_CHARS = CMP_PROCS.keys.join
26
+ BOOL_CHARS = '\|\&\!'
27
+ VER_CHARS = '\w\.\-'
28
+
29
+ class << self
30
+ # is +str+ a version string?
31
+ def version?(str)
32
+ /^\s*[#{VER_CHARS}]+\s*$/ === str
33
+ end
34
+
35
+ # is +str+ a version requirement?
36
+ def requirement?(str)
37
+ /[#{BOOL_CHARS}#{CMP_CHARS}\(\)]/ === str
38
+ end
39
+
40
+ # :call-seq:
41
+ # VersionRequirement.create(" >1 <2 !(1.5) ") -> requirement
42
+ #
43
+ # parse the +str+ requirement
44
+ def create(str)
45
+ instance_eval normalize(str)
46
+ rescue StandardError => e
47
+ raise "Failed to parse #{str.inspect} due to: #{e}"
48
+ end
49
+
50
+ private
51
+ def requirement(req)
52
+ unless req =~ /^\s*(#{CMP_REGEX})?\s*([#{VER_CHARS}]+)\s*$/
53
+ raise "Invalid requirement string: #{req}"
54
+ end
55
+ comparator, version = $1, $2
56
+ version = Gem::Version.new(0).tap { |v| v.version = version }
57
+ VersionRequirement.new(nil, [$1, version])
58
+ end
59
+
60
+ def negate(vreq)
61
+ vreq.negative = !vreq.negative
62
+ vreq
63
+ end
64
+
65
+ def normalize(str)
66
+ str = str.strip
67
+ if str[/[^\s\(\)#{BOOL_CHARS + VER_CHARS + CMP_CHARS}]/]
68
+ raise "version string #{str.inspect} contains invalid characters"
69
+ end
70
+ str.gsub!(/\s+(and|\&\&)\s+/, ' & ')
71
+ str.gsub!(/\s+(or|\|\|)\s+/, ' | ')
72
+ str.gsub!(/(^|\s*)not\s+/, ' ! ')
73
+ pattern = /(#{CMP_REGEX})?\s*[#{VER_CHARS}]+/
74
+ left_pattern = /[#{VER_CHARS}\)]$/
75
+ right_pattern = /^(#{pattern}|\()/
76
+ str = str.split.inject([]) do |ary, i|
77
+ ary << '&' if ary.last =~ left_pattern && i =~ right_pattern
78
+ ary << i
79
+ end
80
+ str = str.join(' ')
81
+ str.gsub!(/!([^=])?/, ' negate \1')
82
+ str.gsub!(pattern) do |expr|
83
+ case expr.strip
84
+ when 'not', 'negate' then 'negate '
85
+ else 'requirement("' + expr + '")'
86
+ end
87
+ end
88
+ str.gsub!(/negate\s+\(/, 'negate(')
89
+ str
90
+ end
91
+ end
92
+
93
+ def initialize(op, *requirements) #:nodoc:
94
+ @op, @requirements = op, requirements
95
+ end
96
+
97
+ # Is this object a composed requirement?
98
+ # VersionRequirement.create('1').composed? -> false
99
+ # VersionRequirement.create('1 | 2').composed? -> true
100
+ # VersionRequirement.create('1 & 2').composed? -> true
101
+ def composed?
102
+ requirements.size > 1
103
+ end
104
+
105
+ # Return the last requirement on this object having an = operator.
106
+ def default
107
+ default = nil
108
+ requirements.reverse.find do |r|
109
+ if Array === r
110
+ if !negative && (r.first.nil? || r.first.include?('='))
111
+ default = r.last.to_s
112
+ end
113
+ else
114
+ default = r.default
115
+ end
116
+ end
117
+ default
118
+ end
119
+
120
+ # Test if this requirement can be satisfied by +version+
121
+ def satisfied_by?(version)
122
+ return false unless version
123
+ unless version.kind_of?(Gem::Version)
124
+ raise "Invalid version: #{version.inspect}" unless self.class.version?(version)
125
+ version = Gem::Version.new(0).tap { |v| v.version = version.strip }
126
+ end
127
+ message = op == :| ? :any? : :all?
128
+ result = requirements.send message do |req|
129
+ if Array === req
130
+ cmp, rv = *req
131
+ CMP_PROCS[cmp || '='].call(version, rv)
132
+ else
133
+ req.satisfied_by?(version)
134
+ end
135
+ end
136
+ negative ? !result : result
137
+ end
138
+
139
+ # Either modify the current requirement (if it's already an or operation)
140
+ # or create a new requirement
141
+ def |(other)
142
+ operation(:|, other)
143
+ end
144
+
145
+ # Either modify the current requirement (if it's already an and operation)
146
+ # or create a new requirement
147
+ def &(other)
148
+ operation(:&, other)
149
+ end
150
+
151
+ # return the parsed expression
152
+ def to_s
153
+ str = requirements.map(&:to_s).join(" " + @op.to_s + " ").to_s
154
+ str = "( " + str + " )" if negative || requirements.size > 1
155
+ str = "!" + str if negative
156
+ str
157
+ end
158
+
159
+ attr_accessor :negative
160
+ protected
161
+ attr_reader :requirements, :op
162
+ def operation(op, other)
163
+ @op ||= op
164
+ if negative == other.negative && @op == op && other.requirements.size == 1
165
+ @requirements << other.requirements.first
166
+ self
167
+ else
168
+ self.class.new(op, self, other)
169
+ end
170
+ end
171
+ end # VersionRequirement
172
+ end