copy_stream coerces based on #to_path
authorEric Wong <normalperson@yhbt.net>
Fri, 13 May 2011 19:59:13 +0000 (13 12:59 -0700)
committerEric Wong <normalperson@yhbt.net>
Fri, 13 May 2011 20:08:28 +0000 (13 13:08 -0700)
This is to be compatible with IO.copy_stream and also
Rack::File since this may be used in web servers.

This is NOT compatible with Ruby 1.8 since File.open
doesn't coerce with #to_path on the given object.

io_splice.gemspec
lib/io/splice.rb
test/test_rack_file_compat.rb [new file with mode: 0644]

index c098a63..2046d11 100644 (file)
@@ -21,6 +21,7 @@ Gem::Specification.new do |s|
   s.rubyforge_project = %q{qrp}
   s.test_files = Dir['test/test_*.rb']
   s.add_development_dependency('wrongdoc', '~> 1.5')
+  s.add_development_dependency('rack', '~> 1.2')
 
   # s.licenses = %w(LGPL) # accessor not compatible with older RubyGems
 end
index 574e681..1ff2f6a 100644 (file)
@@ -25,6 +25,11 @@ module IO::Splice
     n
   end
 
+  def self.need_open?(obj) # :nodoc:
+    return false if obj.respond_to?(:to_io)
+    obj.respond_to?(:to_path) || obj.kind_of?(String)
+  end
+
   # copies the contents of the IO object given by +src+ to +dst+
   # If +len+ is specified, then only +len+ bytes are copied and
   # +EOFError+ is raised if fewer than +len+ bytes could be copied.
@@ -36,10 +41,10 @@ module IO::Splice
   # objects with no underlying file descriptor (e.g. StringIO).
   def self.copy_stream(src, dst, len = nil, src_offset = nil)
     close = []
-    src.kind_of?(String) and close << (src = File.open(src))
-    dst.kind_of?(String) and close << (dst = File.open(dst, "w"))
-    src, dst = src.to_io, dst.to_io
+    need_open?(src) and close << (src = File.open(src))
+    need_open?(dst) and close << (dst = File.open(dst, "w"))
     rv = len
+    src, dst = src.to_io, dst.to_io
 
     if src.stat.pipe? || dst.stat.pipe?
       if len
diff --git a/test/test_rack_file_compat.rb b/test/test_rack_file_compat.rb
new file mode 100644 (file)
index 0000000..5505262
--- /dev/null
@@ -0,0 +1,31 @@
+# -*- encoding: binary -*-
+require "rack"
+require "test/unit"
+require "socket"
+require "io/splice"
+
+class TestRackFileCompat < Test::Unit::TestCase
+  def setup
+    @app = Rack::File.new(File.dirname(__FILE__))
+    @req = Rack::MockRequest.new(@app)
+    @base_file = File.basename(__FILE__)
+    @r, @w = UNIXSocket.pair
+  end
+
+  def teardown
+    [ @r, @w ].each { |io| io.closed? or io.close }
+  end
+
+  def test_get_rack_file
+    env = Rack::MockRequest.env_for "http://example.com/#@base_file"
+    status, headers, body = @app.call(env)
+    assert_equal 200, status.to_i
+    headers.each { |k,v|
+      assert_instance_of String, k.to_str
+      assert_instance_of String, v.to_str
+    }
+    thr = Thread.new { @r.read(File.size(__FILE__)) }
+    assert_equal File.size(__FILE__), IO::Splice.copy_stream(body, @w)
+    assert_equal File.read(__FILE__), thr.value
+  end
+end if IO.respond_to?(:copy_stream)