[前][次][番号順一覧][スレッド一覧]

ruby-changes:8912

From: akr <ko1@a...>
Date: Tue, 2 Dec 2008 19:24:01 +0900 (JST)
Subject: [ruby-changes:8912] Ruby:r20448 (trunk): * lib/open3.rb (Open3.popen3): merge hash options if given.

akr	2008-12-02 19:23:48 +0900 (Tue, 02 Dec 2008)

  New Revision: 20448

  http://svn.ruby-lang.org/cgi-bin/viewvc.cgi?view=rev&revision=20448

  Log:
    * lib/open3.rb (Open3.popen3): merge hash options if given.

  Added files:
    trunk/test/test_open3.rb
  Modified files:
    trunk/ChangeLog
    trunk/lib/open3.rb

Index: ChangeLog
===================================================================
--- ChangeLog	(revision 20447)
+++ ChangeLog	(revision 20448)
@@ -1,3 +1,7 @@
+Tue Dec  2 19:22:13 2008  Tanaka Akira  <akr@f...>
+
+	* lib/open3.rb (Open3.popen3): merge hash options if given. 
+
 Tue Dec  2 15:31:42 2008  Yukihiro Matsumoto  <matz@r...>
 
 	* lib/net/protocol.rb (Net::BufferedIO#rbuf_fill): use
Index: lib/open3.rb
===================================================================
--- lib/open3.rb	(revision 20447)
+++ lib/open3.rb	(revision 20448)
@@ -32,15 +32,23 @@
 #
 
 module Open3
-  # 
+
   # Open stdin, stdout, and stderr streams and start external executable.
   # In addition, a thread for waiting the started process is noticed.
-  # The thread has a thread variable :pid which is the pid of the started
-  # process.
+  # The thread has a pid method and thread variable :pid which is the pid of
+  # the started process.
   #
+  # Block form:
+  #
+  #   Open3.popen3(cmd... [, opts]) {|stdin, stdout, stderr, wait_thr|
+  #     pid = wait_thr.pid # pid of the started process.
+  #     ...
+  #     exit_status = wait_thr.value # Process::Status object returned.
+  #   }
+  #
   # Non-block form:
   #   
-  #   stdin, stdout, stderr, wait_thr = Open3.popen3(cmd)
+  #   stdin, stdout, stderr, wait_thr = Open3.popen3(cmd... [, opts])
   #   pid = wait_thr[:pid]  # pid of the started process.
   #   ...
   #   stdin.close  # stdin, stdout and stderr should be closed in this form.
@@ -48,38 +56,100 @@
   #   stderr.close
   #   exit_status = wait_thr.value  # Process::Status object returned.
   #
-  # Block form:
+  # The parameters +cmd...+ is passed to Kernel#spawn.
+  # So a commandline string and list of argument strings can be accepted as follows.
   #
-  #   Open3.popen3(cmd) { |stdin, stdout, stderr, wait_thr| ... }
+  #   Open3.popen3("echo a") {|i, o, e, t| ... }
+  #   Open3.popen3("echo", "a") {|i, o, e, t| ... }
   #
-  # The parameter +cmd+ is passed directly to Kernel#spawn.
+  # If the last parameter, opts, is a Hash, it is recognized as an option for Kernel#spawn.
   #
+  #   Open3.popen3("pwd", :chdir=>"/") {|i,o,e,t|
+  #     p o.read.chomp #=> "/"
+  #   }
+  #
+  # opts[STDIN], opts[STDOUT] and opts[STDERR] in the option are set for redirection.
+  #
+  # If some of the three elements in opts are specified,
+  # pipes for them are not created.
+  # In that case, block arugments for the block form and
+  # return values for the non-block form are decreased.
+  #
+  #   # No pipe "e" for stderr
+  #   Open3.popen3("echo a", STDERR=>nil) {|i,o,t| ... }
+  #   i,o,t = Open3.popen3("echo a", STDERR=>nil)
+  #
+  # If the value is nil as above, the elements of opts are removed.
+  # So standard input/output/error of current process are inherited.
+  #
+  # If the value is not nil, it is passed as is to Kernel#spawn.
+  # So pipeline of commands can be constracted as follows.
+  #
+  #   Open3.popen3("yes", STDIN=>nil, STDERR=>nil) {|o1,t1|
+  #     Open3.popen3("head -10", STDIN=>o1, STDERR=>nil) {|o2,t2|
+  #       o1.close
+  #       p o2.read     #=> "y\ny\ny\ny\ny\ny\ny\ny\ny\ny\n"
+  #       p t1.value    #=> #<Process::Status: pid 13508 SIGPIPE (signal 13)>
+  #       p t2.value    #=> #<Process::Status: pid 13510 exit 0>
+  #     } 
+  #   }
+  #
   # wait_thr.value waits the termination of the process.
   # The block form also waits the process when it returns.
   #
   # Closing stdin, stdout and stderr does not wait the process.
   #
   def popen3(*cmd)
-    pw = IO::pipe   # pipe[0] for read, pipe[1] for write
-    pr = IO::pipe
-    pe = IO::pipe
+    if Hash === cmd.last
+      opts = cmd.pop.dup
+    else
+      opts = {}
+    end
 
-    pid = spawn(*cmd, STDIN=>pw[0], STDOUT=>pr[1], STDERR=>pe[1])
+    child_io = []
+    parent_io = []
+
+    if !opts.include?(STDIN)
+      pw = IO.pipe   # pipe[0] for read, pipe[1] for write
+      opts[STDIN] = pw[0]
+      pw[1].sync = true
+      child_io << pw[0]
+      parent_io << pw[1]
+    elsif opts[STDIN] == nil
+      opts.delete(STDIN)
+    end
+
+    if !opts.include?(STDOUT)
+      pr = IO.pipe
+      opts[STDOUT] = pr[1]
+      child_io << pr[1]
+      parent_io << pr[0]
+    elsif opts[STDOUT] == nil
+      opts.delete(STDOUT)
+    end
+
+    if !opts.include?(STDERR)
+      pe = IO.pipe
+      opts[STDERR] = pe[1]
+      child_io << pe[1]
+      parent_io << pe[0]
+    elsif opts[STDERR] == nil
+      opts.delete(STDERR)
+    end
+
+    pid = spawn(*cmd, opts)
     wait_thr = Process.detach(pid)
-    pw[0].close
-    pr[1].close
-    pe[1].close
-    pi = [pw[1], pr[0], pe[0], wait_thr]
-    pw[1].sync = true
+    child_io.each {|io| io.close }
+    result = [*parent_io, wait_thr]
     if defined? yield
       begin
-	return yield(*pi)
+	return yield(*result)
       ensure
-	[pw[1], pr[0], pe[0]].each{|p| p.close unless p.closed?}
+	parent_io.each{|io| io.close unless io.closed?}
         wait_thr.join
       end
     end
-    pi
+    result
   end
   module_function :popen3
 end
Index: test/test_open3.rb
===================================================================
--- test/test_open3.rb	(revision 0)
+++ test/test_open3.rb	(revision 20448)
@@ -0,0 +1,205 @@
+require 'test/unit'
+require 'open3'
+require 'shellwords'
+require_relative 'ruby/envutil'
+
+class TestOpen3 < Test::Unit::TestCase
+  RUBY = EnvUtil.rubybin
+
+  def test_exit_status
+    Open3.popen3(RUBY, '-e', 'exit true') {|i,o,e,t|
+      assert_equal(true, t.value.success?)
+    }
+    Open3.popen3(RUBY, '-e', 'exit false') {|i,o,e,t|
+      assert_equal(false, t.value.success?)
+    }
+  end
+
+  def test_stdin
+    Open3.popen3(RUBY, '-e', 'exit STDIN.gets.chomp == "t"') {|i,o,e,t|
+      i.puts 't'
+      assert_equal(true, t.value.success?)
+    }
+    Open3.popen3(RUBY, '-e', 'exit STDIN.gets.chomp == "t"') {|i,o,e,t|
+      i.puts 'f'
+      assert_equal(false, t.value.success?)
+    }
+  end
+
+  def test_stdout
+    Open3.popen3(RUBY, '-e', 'STDOUT.print "foo"') {|i,o,e,t|
+      assert_equal("foo", o.read)
+    }
+  end
+
+  def test_stderr
+    Open3.popen3(RUBY, '-e', 'STDERR.print "bar"') {|i,o,e,t|
+      assert_equal("bar", e.read)
+    }
+  end
+
+  def test_block
+    r = Open3.popen3(RUBY, '-e', 'STDOUT.print STDIN.read') {|i,o,e,t|
+      i.print "baz"
+      i.close
+      assert_equal("baz", o.read)
+      "qux"
+    }
+    assert_equal("qux", r)
+  end
+
+  def test_noblock
+    i,o,e,t = Open3.popen3(RUBY, '-e', 'STDOUT.print STDIN.read')
+    i.print "baz"
+    i.close
+    assert_equal("baz", o.read)
+  ensure
+    i.close if !i.closed?
+    o.close if !o.closed?
+    e.close if !e.closed?
+  end
+
+  def test_commandline
+    commandline = Shellwords.join([RUBY, '-e', 'print "quux"'])
+    Open3.popen3(commandline) {|i,o,e,t|
+      assert_equal("quux", o.read)
+    }
+  end
+
+  def test_pid
+    Open3.popen3(RUBY, '-e', 'print $$') {|i,o,e,t|
+      pid = o.read.to_i
+      assert_equal(pid, t[:pid])
+      assert_equal(pid, t.pid)
+    }
+  end
+
+  def test_disable
+    Open3.popen3(RUBY, '-e', '', STDIN=>nil) {|o,e,t|
+      assert_kind_of(Thread, t)
+    }
+    Open3.popen3(RUBY, '-e', '', STDOUT=>nil) {|i,e,t|
+      assert_kind_of(Thread, t)
+    }
+    Open3.popen3(RUBY, '-e', '', STDERR=>nil) {|i,o,t|
+      assert_kind_of(Thread, t)
+    }
+    Open3.popen3(RUBY, '-e', '', STDIN=>nil, STDOUT=>nil, STDERR=>nil) {|t|
+      assert_kind_of(Thread, t)
+    }
+  end
+
+  def with_pipe
+    r, w = IO.pipe
+    yield r, w
+  ensure
+    r.close if !r.closed?
+    w.close if !w.closed?
+  end
+
+  def with_reopen(io, arg)
+    old = io.dup
+    io.reopen(arg)
+    yield old
+  ensure
+    io.reopen(old)
+    old.close if old && !old.closed?
+  end
+
+  def test_disable_stdin
+    with_pipe {|r, w|
+      with_reopen(STDIN, r) {|old|
+        Open3.popen3(RUBY, '-e', 's=STDIN.read; STDOUT.print s+"o"; STDERR.print s+"e"', STDIN=>nil) {|o,e,t|
+          assert_kind_of(Thread, t)
+          w.print "x"
+          w.close
+          assert_equal("xo", o.read)
+          assert_equal("xe", e.read)
+        }
+      }
+    }
+  end
+
+  def test_disable_stdout
+    with_pipe {|r, w|
+      with_reopen(STDOUT, w) {|old|
+        w.close
+        Open3.popen3(RUBY, '-e', 's=STDIN.read; STDOUT.print s+"o"; STDERR.print s+"e"', STDOUT=>nil) {|i,e,t|
+          assert_kind_of(Thread, t)
+          i.print "y"
+          i.close
+          STDOUT.reopen(old)
+          assert_equal("yo", r.read)
+          assert_equal("ye", e.read)
+        }
+      }
+    }
+  end
+
+  def test_disable_stderr
+    with_pipe {|r, w|
+      with_reopen(STDERR, w) {|old|
+        w.close
+        Open3.popen3(RUBY, '-e', 's=STDIN.read; STDOUT.print s+"o"; STDERR.print s+"e"', STDERR=>nil) {|i,o,t|
+          assert_kind_of(Thread, t)
+          i.print "y"
+          i.close
+          STDERR.reopen(old)
+          assert_equal("yo", o.read)
+          assert_equal("ye", r.read)
+        }
+      }
+    }
+  end
+
+  def test_plug_pipe
+    Open3.popen3(RUBY, '-e', 'STDOUT.print "1"') {|i1,o1,e1,t1|
+      Open3.popen3(RUBY, '-e', 'STDOUT.print STDIN.read+"2"', STDIN=>o1) {|o2,e2,t2|
+        assert_equal("12", o2.read)
+      }
+    }
+  end
+
+  def test_tree_pipe
+    ia,oa,ea,ta = Open3.popen3(RUBY, '-e', 'i=STDIN.read; STDOUT.print i+"a"; STDERR.print i+"A"')
+    ob,eb,tb = Open3.popen3(RUBY, '-e', 'i=STDIN.read; STDOUT.print i+"b"; STDERR.print i+"B"', STDIN=>oa)
+    oc,ec,tc = Open3.popen3(RUBY, '-e', 'i=STDIN.read; STDOUT.print i+"c"; STDERR.print i+"C"', STDIN=>ob)
+    od,ed,td = Open3.popen3(RUBY, '-e', 'i=STDIN.read; STDOUT.print i+"d"; STDERR.print i+"D"', STDIN=>eb)
+    oe,ee,te = Open3.popen3(RUBY, '-e', 'i=STDIN.read; STDOUT.print i+"e"; STDERR.print i+"E"', STDIN=>ea)
+    of,ef,tf = Open3.popen3(RUBY, '-e', 'i=STDIN.read; STDOUT.print i+"f"; STDERR.print i+"F"', STDIN=>oe)
+    og,eg,tg = Open3.popen3(RUBY, '-e', 'i=STDIN.read; STDOUT.print i+"g"; STDERR.print i+"G"', STDIN=>ee)
+    oa.close
+    ea.close
+    ob.close
+    eb.close
+    oe.close
+    ee.close
+
+    ia.print "0"
+    ia.close
+    assert_equal("0abc", oc.read)
+    assert_equal("0abC", ec.read)
+    assert_equal("0aBd", od.read)
+    assert_equal("0aBD", ed.read)
+    assert_equal("0Aef", of.read)
+    assert_equal("0AeF", ef.read)
+    assert_equal("0AEg", og.read)
+    assert_equal("0AEG", eg.read)
+  ensure
+    ia.close if !ia.closed?
+    oa.close if !oa.closed?
+    ea.close if !ea.closed?
+    ob.close if !ob.closed?
+    eb.close if !eb.closed?
+    oc.close if !oc.closed?
+    ec.close if !ec.closed?
+    od.close if !od.closed?
+    ed.close if !ed.closed?
+    oe.close if !oe.closed?
+    ee.close if !ee.closed?
+    of.close if !of.closed?
+    ef.close if !ef.closed?
+    og.close if !og.closed?
+    eg.close if !eg.closed?
+  end
+end

--
ML: ruby-changes@q...
Info: http://www.atdot.net/~ko1/quickml/

[前][次][番号順一覧][スレッド一覧]