assaf-buildr 1.3.3

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 (163) hide show
  1. data/CHANGELOG +887 -0
  2. data/DISCLAIMER +7 -0
  3. data/LICENSE +176 -0
  4. data/NOTICE +26 -0
  5. data/README.rdoc +146 -0
  6. data/Rakefile +62 -0
  7. data/_buildr +38 -0
  8. data/addon/buildr/antlr.rb +65 -0
  9. data/addon/buildr/cobertura.rb +236 -0
  10. data/addon/buildr/emma.rb +238 -0
  11. data/addon/buildr/hibernate.rb +142 -0
  12. data/addon/buildr/javacc.rb +85 -0
  13. data/addon/buildr/jdepend.rb +60 -0
  14. data/addon/buildr/jetty.rb +248 -0
  15. data/addon/buildr/jibx.rb +86 -0
  16. data/addon/buildr/nailgun.rb +817 -0
  17. data/addon/buildr/openjpa.rb +90 -0
  18. data/addon/buildr/org/apache/buildr/BuildrNail$Main.class +0 -0
  19. data/addon/buildr/org/apache/buildr/BuildrNail.class +0 -0
  20. data/addon/buildr/org/apache/buildr/BuildrNail.java +41 -0
  21. data/addon/buildr/org/apache/buildr/JettyWrapper$1.class +0 -0
  22. data/addon/buildr/org/apache/buildr/JettyWrapper$BuildrHandler.class +0 -0
  23. data/addon/buildr/org/apache/buildr/JettyWrapper.class +0 -0
  24. data/addon/buildr/org/apache/buildr/JettyWrapper.java +144 -0
  25. data/addon/buildr/xmlbeans.rb +93 -0
  26. data/bin/buildr +28 -0
  27. data/buildr.buildfile +53 -0
  28. data/buildr.gemspec +58 -0
  29. data/doc/css/default.css +228 -0
  30. data/doc/css/print.css +100 -0
  31. data/doc/css/syntax.css +52 -0
  32. data/doc/images/apache-incubator-logo.png +0 -0
  33. data/doc/images/buildr-hires.png +0 -0
  34. data/doc/images/buildr.png +0 -0
  35. data/doc/images/favicon.png +0 -0
  36. data/doc/images/growl-icon.tiff +0 -0
  37. data/doc/images/note.png +0 -0
  38. data/doc/images/project-structure.png +0 -0
  39. data/doc/images/tip.png +0 -0
  40. data/doc/images/zbuildr.tif +0 -0
  41. data/doc/pages/artifacts.textile +207 -0
  42. data/doc/pages/building.textile +240 -0
  43. data/doc/pages/contributing.textile +208 -0
  44. data/doc/pages/download.textile +62 -0
  45. data/doc/pages/extending.textile +175 -0
  46. data/doc/pages/getting_started.textile +273 -0
  47. data/doc/pages/index.textile +42 -0
  48. data/doc/pages/languages.textile +407 -0
  49. data/doc/pages/mailing_lists.textile +17 -0
  50. data/doc/pages/more_stuff.textile +286 -0
  51. data/doc/pages/packaging.textile +427 -0
  52. data/doc/pages/projects.textile +274 -0
  53. data/doc/pages/recipes.textile +103 -0
  54. data/doc/pages/settings_profiles.textile +274 -0
  55. data/doc/pages/testing.textile +212 -0
  56. data/doc/pages/troubleshooting.textile +103 -0
  57. data/doc/pages/whats_new.textile +323 -0
  58. data/doc/print.haml +51 -0
  59. data/doc/print.toc.yaml +29 -0
  60. data/doc/scripts/buildr-git.rb +412 -0
  61. data/doc/scripts/install-jruby.sh +44 -0
  62. data/doc/scripts/install-linux.sh +64 -0
  63. data/doc/scripts/install-osx.sh +52 -0
  64. data/doc/site.haml +56 -0
  65. data/doc/site.toc.yaml +47 -0
  66. data/etc/KEYS +151 -0
  67. data/etc/git-svn-authors +16 -0
  68. data/lib/buildr.rb +35 -0
  69. data/lib/buildr/core.rb +27 -0
  70. data/lib/buildr/core/application.rb +489 -0
  71. data/lib/buildr/core/application_cli.rb +139 -0
  72. data/lib/buildr/core/build.rb +311 -0
  73. data/lib/buildr/core/checks.rb +382 -0
  74. data/lib/buildr/core/common.rb +154 -0
  75. data/lib/buildr/core/compile.rb +596 -0
  76. data/lib/buildr/core/environment.rb +120 -0
  77. data/lib/buildr/core/filter.rb +362 -0
  78. data/lib/buildr/core/generate.rb +195 -0
  79. data/lib/buildr/core/help.rb +118 -0
  80. data/lib/buildr/core/progressbar.rb +156 -0
  81. data/lib/buildr/core/project.rb +892 -0
  82. data/lib/buildr/core/test.rb +715 -0
  83. data/lib/buildr/core/transports.rb +558 -0
  84. data/lib/buildr/core/util.rb +289 -0
  85. data/lib/buildr/groovy.rb +18 -0
  86. data/lib/buildr/groovy/bdd.rb +105 -0
  87. data/lib/buildr/groovy/compiler.rb +138 -0
  88. data/lib/buildr/ide.rb +19 -0
  89. data/lib/buildr/ide/eclipse.rb +212 -0
  90. data/lib/buildr/ide/idea.ipr.template +300 -0
  91. data/lib/buildr/ide/idea.rb +189 -0
  92. data/lib/buildr/ide/idea7x.ipr.template +290 -0
  93. data/lib/buildr/ide/idea7x.rb +210 -0
  94. data/lib/buildr/java.rb +23 -0
  95. data/lib/buildr/java/ant.rb +92 -0
  96. data/lib/buildr/java/bdd.rb +449 -0
  97. data/lib/buildr/java/commands.rb +211 -0
  98. data/lib/buildr/java/compiler.rb +348 -0
  99. data/lib/buildr/java/deprecated.rb +141 -0
  100. data/lib/buildr/java/jruby.rb +117 -0
  101. data/lib/buildr/java/jtestr_runner.rb.erb +116 -0
  102. data/lib/buildr/java/org/apache/buildr/JavaTestFilter.class +0 -0
  103. data/lib/buildr/java/org/apache/buildr/JavaTestFilter.java +119 -0
  104. data/lib/buildr/java/packaging.rb +713 -0
  105. data/lib/buildr/java/pom.rb +178 -0
  106. data/lib/buildr/java/rjb.rb +155 -0
  107. data/lib/buildr/java/test_result.rb +308 -0
  108. data/lib/buildr/java/tests.rb +324 -0
  109. data/lib/buildr/java/version_requirement.rb +172 -0
  110. data/lib/buildr/packaging.rb +21 -0
  111. data/lib/buildr/packaging/artifact.rb +730 -0
  112. data/lib/buildr/packaging/artifact_namespace.rb +972 -0
  113. data/lib/buildr/packaging/artifact_search.rb +140 -0
  114. data/lib/buildr/packaging/gems.rb +102 -0
  115. data/lib/buildr/packaging/package.rb +233 -0
  116. data/lib/buildr/packaging/tar.rb +104 -0
  117. data/lib/buildr/packaging/zip.rb +722 -0
  118. data/lib/buildr/resources/buildr.icns +0 -0
  119. data/lib/buildr/scala.rb +19 -0
  120. data/lib/buildr/scala/compiler.rb +109 -0
  121. data/lib/buildr/scala/tests.rb +203 -0
  122. data/rakelib/apache.rake +191 -0
  123. data/rakelib/changelog.rake +57 -0
  124. data/rakelib/doc.rake +103 -0
  125. data/rakelib/package.rake +73 -0
  126. data/rakelib/release.rake +65 -0
  127. data/rakelib/rspec.rake +83 -0
  128. data/rakelib/rubyforge.rake +53 -0
  129. data/rakelib/scm.rake +49 -0
  130. data/rakelib/setup.rake +86 -0
  131. data/rakelib/stage.rake +48 -0
  132. data/spec/addon/cobertura_spec.rb +77 -0
  133. data/spec/addon/emma_spec.rb +120 -0
  134. data/spec/addon/test_coverage_spec.rb +255 -0
  135. data/spec/core/application_spec.rb +412 -0
  136. data/spec/core/artifact_namespace_spec.rb +646 -0
  137. data/spec/core/build_spec.rb +415 -0
  138. data/spec/core/checks_spec.rb +537 -0
  139. data/spec/core/common_spec.rb +664 -0
  140. data/spec/core/compile_spec.rb +566 -0
  141. data/spec/core/generate_spec.rb +33 -0
  142. data/spec/core/project_spec.rb +754 -0
  143. data/spec/core/test_spec.rb +1091 -0
  144. data/spec/core/transport_spec.rb +500 -0
  145. data/spec/groovy/bdd_spec.rb +80 -0
  146. data/spec/groovy/compiler_spec.rb +239 -0
  147. data/spec/ide/eclipse_spec.rb +243 -0
  148. data/spec/java/ant.rb +28 -0
  149. data/spec/java/bdd_spec.rb +358 -0
  150. data/spec/java/compiler_spec.rb +446 -0
  151. data/spec/java/java_spec.rb +88 -0
  152. data/spec/java/packaging_spec.rb +1103 -0
  153. data/spec/java/tests_spec.rb +466 -0
  154. data/spec/packaging/archive_spec.rb +503 -0
  155. data/spec/packaging/artifact_spec.rb +754 -0
  156. data/spec/packaging/packaging_helper.rb +63 -0
  157. data/spec/packaging/packaging_spec.rb +589 -0
  158. data/spec/sandbox.rb +137 -0
  159. data/spec/scala/compiler_spec.rb +228 -0
  160. data/spec/scala/tests_spec.rb +215 -0
  161. data/spec/spec_helpers.rb +304 -0
  162. data/spec/version_requirement_spec.rb +123 -0
  163. metadata +369 -0
@@ -0,0 +1,558 @@
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
+ require 'cgi'
18
+ require 'net/http'
19
+ require 'net/https'
20
+ # PATCH: On Windows, Net::SSH 2.0.2 attempts to load the Pageant DLLs which break on JRuby.
21
+ $LOADED_FEATURES << 'net/ssh/authentication/pageant.rb' if RUBY_PLATFORM =~ /java/
22
+ require 'net/sftp'
23
+ require 'uri'
24
+ require 'digest/md5'
25
+ require 'digest/sha1'
26
+ require 'stringio'
27
+ require 'tempfile'
28
+ require 'buildr/core/progressbar'
29
+
30
+
31
+ # Not quite open-uri, but similar. Provides read and write methods for the resource represented by the URI.
32
+ # Currently supports reads for URI::HTTP and writes for URI::SFTP. Also provides convenience methods for
33
+ # downloads and uploads.
34
+ module URI
35
+
36
+ # Raised when trying to read/download a resource that doesn't exist.
37
+ class NotFoundError < RuntimeError
38
+ end
39
+
40
+ # How many bytes to read/write at once.
41
+ RW_CHUNK_SIZE = 2 ** 20 #:nodoc:
42
+
43
+ class << self
44
+
45
+ # :call-seq:
46
+ # read(uri, options?) => content
47
+ # read(uri, options?) { |chunk| ... }
48
+ #
49
+ # Reads from the resource behind this URI. The first form returns the content of the resource,
50
+ # the second form yields to the block with each chunk of content (usually more than one).
51
+ #
52
+ # For example:
53
+ # File.open 'image.jpg', 'w' do |file|
54
+ # URI.read('http://example.com/image.jpg') { |chunk| file.write chunk }
55
+ # end
56
+ # Shorter version:
57
+ # File.open('image.jpg', 'w') { |file| file.write URI.read('http://example.com/image.jpg') }
58
+ #
59
+ # Supported options:
60
+ # * :modified -- Only download if file modified since this timestamp. Returns nil if not modified.
61
+ # * :progress -- Show the progress bar while reading.
62
+ def read(uri, options = nil, &block)
63
+ uri = URI.parse(uri.to_s) unless URI === uri
64
+ uri.read options, &block
65
+ end
66
+
67
+ # :call-seq:
68
+ # download(uri, target, options?)
69
+ #
70
+ # Downloads the resource to the target.
71
+ #
72
+ # The target may be a file name (string or task), in which case the file is created from the resource.
73
+ # The target may also be any object that responds to +write+, e.g. File, StringIO, Pipe.
74
+ #
75
+ # Use the progress bar when running in verbose mode.
76
+ def download(uri, target, options = nil)
77
+ uri = URI.parse(uri.to_s) unless URI === uri
78
+ uri.download target, options
79
+ end
80
+
81
+ # :call-seq:
82
+ # write(uri, content, options?)
83
+ # write(uri, options?) { |bytes| .. }
84
+ #
85
+ # Writes to the resource behind the URI. The first form writes the content from a string or an object
86
+ # that responds to +read+ and optionally +size+. The second form writes the content by yielding to the
87
+ # block. Each yield should return up to the specified number of bytes, the last yield returns nil.
88
+ #
89
+ # For example:
90
+ # File.open 'killer-app.jar', 'rb' do |file|
91
+ # write('sftp://localhost/jars/killer-app.jar') { |chunk| file.read(chunk) }
92
+ # end
93
+ # Or:
94
+ # write 'sftp://localhost/jars/killer-app.jar', File.read('killer-app.jar')
95
+ #
96
+ # Supported options:
97
+ # * :progress -- Show the progress bar while reading.
98
+ def write(uri, *args, &block)
99
+ uri = URI.parse(uri.to_s) unless URI === uri
100
+ uri.write *args, &block
101
+ end
102
+
103
+ # :call-seq:
104
+ # upload(uri, source, options?)
105
+ #
106
+ # Uploads from source to the resource.
107
+ #
108
+ # The source may be a file name (string or task), in which case the file is uploaded to the resource.
109
+ # The source may also be any object that responds to +read+ (and optionally +size+), e.g. File, StringIO, Pipe.
110
+ #
111
+ # Use the progress bar when running in verbose mode.
112
+ def upload(uri, source, options = nil)
113
+ uri = URI.parse(uri.to_s) unless URI === uri
114
+ uri.upload source, options
115
+ end
116
+
117
+ end
118
+
119
+ class Generic
120
+
121
+ # :call-seq:
122
+ # read(options?) => content
123
+ # read(options?) { |chunk| ... }
124
+ #
125
+ # Reads from the resource behind this URI. The first form returns the content of the resource,
126
+ # the second form yields to the block with each chunk of content (usually more than one).
127
+ #
128
+ # For options, see URI::read.
129
+ def read(options = nil, &block)
130
+ fail 'This protocol doesn\'t support reading (yet, how about helping by implementing it?)'
131
+ end
132
+
133
+ # :call-seq:
134
+ # download(target, options?)
135
+ #
136
+ # Downloads the resource to the target.
137
+ #
138
+ # The target may be a file name (string or task), in which case the file is created from the resource.
139
+ # The target may also be any object that responds to +write+, e.g. File, StringIO, Pipe.
140
+ #
141
+ # Use the progress bar when running in verbose mode.
142
+ def download(target, options = nil)
143
+ case target
144
+ when Rake::Task
145
+ download target.name, options
146
+ when String
147
+ # If download breaks we end up with a partial file which is
148
+ # worse than not having a file at all, so download to temporary
149
+ # file and then move over.
150
+ modified = File.stat(target).mtime if File.exist?(target)
151
+ temp = nil
152
+ Tempfile.open File.basename(target) do |temp|
153
+ temp.binmode
154
+ read({:progress=>verbose}.merge(options || {}).merge(:modified=>modified)) { |chunk| temp.write chunk }
155
+ end
156
+ mkpath File.dirname(target)
157
+ File.move temp.path, target
158
+ when File
159
+ read({:progress=>verbose}.merge(options || {}).merge(:modified=>target.mtime)) { |chunk| target.write chunk }
160
+ target.flush
161
+ else
162
+ raise ArgumentError, 'Expecting a target that is either a file name (string, task) or object that responds to write (file, pipe).' unless target.respond_to?(:write)
163
+ read({:progress=>verbose}.merge(options || {})) { |chunk| target.write chunk }
164
+ target.flush
165
+ end
166
+ end
167
+
168
+ # :call-seq:
169
+ # write(content, options?)
170
+ # write(options?) { |bytes| .. }
171
+ #
172
+ # Writes to the resource behind the URI. The first form writes the content from a string or an object
173
+ # that responds to +read+ and optionally +size+. The second form writes the content by yielding to the
174
+ # block. Each yield should return up to the specified number of bytes, the last yield returns nil.
175
+ #
176
+ # For options, see URI::write.
177
+ def write(*args, &block)
178
+ options = args.pop if Hash === args.last
179
+ options ||= {}
180
+ if String === args.first
181
+ ios = StringIO.new(args.first, 'r')
182
+ write(options.merge(:size=>args.first.size)) { |bytes| ios.read(bytes) }
183
+ elsif args.first.respond_to?(:read)
184
+ size = args.first.size rescue nil
185
+ write({:size=>size}.merge(options)) { |bytes| args.first.read(bytes) }
186
+ elsif args.empty? && block
187
+ write_internal options, &block
188
+ else
189
+ raise ArgumentError, 'Either give me the content, or pass me a block, otherwise what would I upload?'
190
+ end
191
+ end
192
+
193
+ # :call-seq:
194
+ # upload(source, options?)
195
+ #
196
+ # Uploads from source to the resource.
197
+ #
198
+ # The source may be a file name (string or task), in which case the file is uploaded to the resource.
199
+ # If the source is a directory, uploads all files inside the directory (including nested directories).
200
+ # The source may also be any object that responds to +read+ (and optionally +size+), e.g. File, StringIO, Pipe.
201
+ #
202
+ # Use the progress bar when running in verbose mode.
203
+ def upload(source, options = nil)
204
+ source = source.name if Rake::Task === source
205
+ options ||= {}
206
+ if String === source
207
+ raise NotFoundError, 'No source file/directory to upload.' unless File.exist?(source)
208
+ if File.directory?(source)
209
+ Dir.glob("#{source}/**/*").reject { |file| File.directory?(file) }.each do |file|
210
+ uri = self + (File.join(self.path, file.sub(source, '')))
211
+ uri.upload file, {:digests=>[]}.merge(options)
212
+ end
213
+ else
214
+ File.open(source, 'rb') { |input| upload input, options }
215
+ end
216
+ elsif source.respond_to?(:read)
217
+ digests = (options[:digests] || [:md5, :sha1]).
218
+ inject({}) { |hash, name| hash[name] = Digest.const_get(name.to_s.upcase).new ; hash }
219
+ size = source.size rescue nil
220
+ write (options).merge(:progress=>verbose && size, :size=>size) do |bytes|
221
+ source.read(bytes).tap do |chunk|
222
+ digests.values.each { |digest| digest << chunk } if chunk
223
+ end
224
+ end
225
+ digests.each do |key, digest|
226
+ self.merge("#{self.path}.#{key}").write "#{digest.hexdigest} #{File.basename(path)}",
227
+ (options).merge(:progress=>false)
228
+ end
229
+ else
230
+ raise ArgumentError, 'Expecting source to be a file name (string, task) or any object that responds to read (file, pipe).'
231
+ end
232
+ end
233
+
234
+ protected
235
+
236
+ # :call-seq:
237
+ # with_progress_bar(show, file_name, size) { |progress| ... }
238
+ #
239
+ # Displays a progress bar while executing the block. The first argument must be true for the
240
+ # progress bar to show (TTY output also required), as a convenient for selectively using the
241
+ # progress bar from a single block.
242
+ #
243
+ # The second argument provides a filename to display, the third its size in bytes.
244
+ #
245
+ # The block is yielded with a progress object that implements a single method.
246
+ # Call << for each block of bytes down/uploaded.
247
+ def with_progress_bar(show, file_name, size, &block) #:nodoc:
248
+ options = { :total=>size || 0, :title=>file_name }
249
+ options[:hidden] = true unless show
250
+ ProgressBar.start options, &block
251
+ end
252
+
253
+ # :call-seq:
254
+ # proxy_uri => URI?
255
+ #
256
+ # Returns the proxy server to use. Obtains the proxy from the relevant environment variable (e.g. HTTP_PROXY).
257
+ # Supports exclusions based on host name and port number from environment variable NO_PROXY.
258
+ def proxy_uri
259
+ proxy = ENV["#{scheme.upcase}_PROXY"]
260
+ proxy = URI.parse(proxy) if String === proxy
261
+ excludes = ENV['NO_PROXY'].to_s.split(/\s*,\s*/).compact
262
+ excludes = excludes.map { |exclude| exclude =~ /:\d+$/ ? exclude : "#{exclude}:*" }
263
+ return proxy unless excludes.any? { |exclude| File.fnmatch(exclude, "#{host}:#{port}") }
264
+ end
265
+
266
+ def write_internal(options, &block) #:nodoc:
267
+ fail 'This protocol doesn\'t support writing (yet, how about helping by implementing it?)'
268
+ end
269
+
270
+ end
271
+
272
+
273
+ class HTTP #:nodoc:
274
+
275
+ # See URI::Generic#read
276
+ def read(options = nil, &block)
277
+ options ||= {}
278
+ connect do |http|
279
+ trace "Requesting #{self}"
280
+ headers = { 'If-Modified-Since' => CGI.rfc1123_date(options[:modified].utc) } if options[:modified]
281
+ request = Net::HTTP::Get.new(request_uri.empty? ? '/' : request_uri, headers)
282
+ request.basic_auth self.user, self.password if self.user
283
+ http.request request do |response|
284
+ case response
285
+ when Net::HTTPNotModified
286
+ # No modification, nothing to do.
287
+ trace 'Not modified since last download'
288
+ return nil
289
+ when Net::HTTPRedirection
290
+ # Try to download from the new URI, handle relative redirects.
291
+ trace "Redirected to #{response['Location']}"
292
+ return (self + URI.parse(response['location'])).read(options, &block)
293
+ when Net::HTTPOK
294
+ info "Downloading #{self}"
295
+ result = nil
296
+ with_progress_bar options[:progress], path.split('/').last, response.content_length do |progress|
297
+ if block
298
+ response.read_body do |chunk|
299
+ block.call chunk
300
+ progress << chunk
301
+ end
302
+ else
303
+ result = ''
304
+ response.read_body do |chunk|
305
+ result << chunk
306
+ progress << chunk
307
+ end
308
+ end
309
+ end
310
+ return result
311
+ when Net::HTTPNotFound
312
+ raise NotFoundError, "Looking for #{self} and all I got was a 404!"
313
+ else
314
+ raise RuntimeError, "Failed to download #{self}: #{response.message}"
315
+ end
316
+ end
317
+ end
318
+ end
319
+
320
+ private
321
+
322
+ def write_internal(options, &block) #:nodoc:
323
+ options ||= {}
324
+ connect do |http|
325
+ trace "Uploading to #{path}"
326
+ content = StringIO.new
327
+ while chunk = yield(RW_CHUNK_SIZE)
328
+ content << chunk
329
+ end
330
+ headers = { 'Content-MD5'=>Digest::MD5.hexdigest(content.string) }
331
+ request = Net::HTTP::Put.new(request_uri.empty? ? '/' : request_uri, headers)
332
+ request.basic_auth self.user, self.password if self.user
333
+ response = nil
334
+ with_progress_bar options[:progress], path.split('/').last, content.size do |progress|
335
+ request.content_length = content.size
336
+ content.rewind
337
+ stream = Object.new
338
+ class << stream ; self ;end.send :define_method, :read do |count|
339
+ bytes = content.read(count)
340
+ progress << bytes if bytes
341
+ bytes
342
+ end
343
+ request.body_stream = stream
344
+ response = http.request(request)
345
+ end
346
+
347
+ case response
348
+ when Net::HTTPRedirection
349
+ # Try to download from the new URI, handle relative redirects.
350
+ trace "Redirected to #{response['Location']}"
351
+ content.rewind
352
+ return (self + URI.parse(response['location'])).write_internal(options) { |bytes| content.read(bytes) }
353
+ when Net::HTTPSuccess
354
+ else
355
+ raise RuntimeError, "Failed to upload #{self}: #{response.message}"
356
+ end
357
+ end
358
+ end
359
+
360
+ def connect
361
+ if proxy = proxy_uri
362
+ proxy = URI.parse(proxy) if String === proxy
363
+ http = Net::HTTP.new(host, port, proxy.host, proxy.port, proxy.user, proxy.password)
364
+ else
365
+ http = Net::HTTP.new(host, port)
366
+ end
367
+ http.use_ssl = true if self.instance_of? URI::HTTPS
368
+ yield http
369
+ end
370
+
371
+ end
372
+
373
+
374
+ class SFTP < Generic #:nodoc:
375
+
376
+ DEFAULT_PORT = 22
377
+ COMPONENT = [ :scheme, :userinfo, :host, :port, :path ].freeze
378
+
379
+ class << self
380
+ # Caching of passwords, so we only need to ask once.
381
+ def passwords
382
+ @passwords ||= {}
383
+ end
384
+ end
385
+
386
+ def initialize(*arg)
387
+ super
388
+ end
389
+
390
+ def read(options = {}, &block)
391
+ # SSH options are based on the username/password from the URI.
392
+ ssh_options = { :port=>port, :password=>password }.merge(options[:ssh_options] || {})
393
+ ssh_options[:password] ||= SFTP.passwords[host]
394
+ begin
395
+ trace "Connecting to #{host}"
396
+ result = nil
397
+ Net::SFTP.start(host, user, ssh_options) do |sftp|
398
+ SFTP.passwords[host] = ssh_options[:password]
399
+ trace 'connected'
400
+
401
+ with_progress_bar options[:progress] && options[:size], path.split('/'), options[:size] || 0 do |progress|
402
+ trace "Downloading to #{path}"
403
+ sftp.file.open(path, 'r') do |file|
404
+ if block
405
+ while chunk = file.read(RW_CHUNK_SIZE)
406
+ block.call chunk
407
+ progress << chunk
408
+ end
409
+ else
410
+ result = ''
411
+ while chunk = file.read(RW_CHUNK_SIZE)
412
+ result << chunk
413
+ progress << chunk
414
+ end
415
+ end
416
+ end
417
+ end
418
+ end
419
+ return result
420
+ rescue Net::SSH::AuthenticationFailed=>ex
421
+ # Only if running with console, prompt for password.
422
+ if !ssh_options[:password] && $stdout.isatty
423
+ password = ask("Password for #{host}:") { |q| q.echo = '*' }
424
+ ssh_options[:password] = password
425
+ retry
426
+ end
427
+ raise
428
+ end
429
+ end
430
+
431
+ protected
432
+
433
+ def write_internal(options, &block) #:nodoc:
434
+ # SSH options are based on the username/password from the URI.
435
+ ssh_options = { :port=>port, :password=>password }.merge(options[:ssh_options] || {})
436
+ ssh_options[:password] ||= SFTP.passwords[host]
437
+ begin
438
+ trace "Connecting to #{host}"
439
+ Net::SFTP.start(host, user, ssh_options) do |sftp|
440
+ SFTP.passwords[host] = ssh_options[:password]
441
+ trace 'Connected'
442
+
443
+ # To create a path, we need to create all its parent. We use realpath to determine if
444
+ # the path already exists, otherwise mkdir fails.
445
+ trace "Creating path #{path}"
446
+ File.dirname(path).split('/').reject(&:empty?).inject('/') do |base, part|
447
+ combined = base + part
448
+ sftp.close(sftp.opendir!(combined)) rescue sftp.mkdir! combined, {}
449
+ "#{combined}/"
450
+ end
451
+
452
+ with_progress_bar options[:progress] && options[:size], path.split('/'), options[:size] || 0 do |progress|
453
+ trace "Uploading to #{path}"
454
+ sftp.file.open(path, 'w') do |file|
455
+ while chunk = yield(RW_CHUNK_SIZE)
456
+ file.write chunk
457
+ progress << chunk
458
+ end
459
+ sftp.setstat(path, :permissions => options[:permissions]) if options[:permissions]
460
+ end
461
+ end
462
+ end
463
+ rescue Net::SSH::AuthenticationFailed=>ex
464
+ # Only if running with console, prompt for password.
465
+ if !ssh_options[:password] && $stdout.isatty
466
+ password = ask("Password for #{host}:") { |q| q.echo = '*' }
467
+ ssh_options[:password] = password
468
+ retry
469
+ end
470
+ raise
471
+ end
472
+ end
473
+
474
+ end
475
+
476
+ @@schemes['SFTP'] = SFTP
477
+
478
+
479
+ # File URL. Keep in mind that file URLs take the form of <code>file://host/path</code>, although the host
480
+ # is not used, so typically all you will see are three backslashes. This methods accept common variants,
481
+ # like <code>file:/path</code> but always returns a valid URL.
482
+ class FILE < Generic
483
+
484
+ COMPONENT = [ :host, :path ].freeze
485
+
486
+ def initialize(*args)
487
+ super
488
+ # file:something (opaque) becomes file:///something
489
+ if path.nil?
490
+ set_path "/#{opaque}"
491
+ unless opaque.nil?
492
+ set_opaque nil
493
+ warn "#{caller[2]}: We'll accept this URL, but just so you know, it needs three slashes, as in: #{to_s}"
494
+ end
495
+ end
496
+ # Sadly, file://something really means file://something/ (something being server)
497
+ set_path '/' if path.empty?
498
+
499
+ # On windows, file://c:/something is not a valid URL, but people do it anyway, so if we see a drive-as-host,
500
+ # we'll just be nice enough to fix it. (URI actually strips the colon here)
501
+ if host =~ /^[a-zA-Z]$/
502
+ set_path "/#{host}:#{path}"
503
+ set_host nil
504
+ end
505
+ end
506
+
507
+ # See URI::Generic#read
508
+ def read(options = nil, &block)
509
+ options ||= {}
510
+ raise ArgumentError, 'Either you\'re attempting to read a file from another host (which we don\'t support), or you used two slashes by mistake, where you should have file:///<path>.' if host
511
+
512
+ path = real_path
513
+ # TODO: complain about clunky URLs
514
+ raise NotFoundError, "Looking for #{self} and can't find it." unless File.exists?(path)
515
+ raise NotFoundError, "Looking for the file #{self}, and it happens to be a directory." if File.directory?(path)
516
+ File.open path, 'rb' do |input|
517
+ with_progress_bar options[:progress], path.split('/').last, input.stat.size do |progress|
518
+ block ? block.call(input.read) : input.read
519
+ end
520
+ end
521
+ end
522
+
523
+ def to_s
524
+ "file://#{host}#{path}"
525
+ end
526
+
527
+ # The URL path always starts with a backslash. On most operating systems (Linux, Darwin, BSD) it points
528
+ # to the absolute path on the file system. But on Windows, it comes before the drive letter, creating an
529
+ # unusable path, so real_path fixes that. Ugly but necessary hack.
530
+ def real_path #:nodoc:
531
+ RUBY_PLATFORM =~ /win32/ && path =~ /^\/[a-zA-Z]:\// ? path[1..-1] : path
532
+ end
533
+
534
+ protected
535
+
536
+ def write_internal(options, &block) #:nodoc:
537
+ raise ArgumentError, 'Either you\'re attempting to write a file to another host (which we don\'t support), or you used two slashes by mistake, where you should have file:///<path>.' if host
538
+ temp = nil
539
+ Tempfile.open File.basename(path) do |temp|
540
+ temp.binmode
541
+ with_progress_bar options[:progress] && options[:size], path.split('/'), options[:size] || 0 do |progress|
542
+ while chunk = yield(RW_CHUNK_SIZE)
543
+ temp.write chunk
544
+ progress << chunk
545
+ end
546
+ end
547
+ end
548
+ real_path.tap do |path|
549
+ mkpath File.dirname(path)
550
+ File.move temp.path, path
551
+ end
552
+ end
553
+
554
+ @@schemes['FILE'] = FILE
555
+
556
+ end
557
+
558
+ end