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