buildr 1.3.3-java → 1.3.4-java

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 (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