I think IO#popen could stand to be changed, and I wanted to run my ideas by
this list. Currently the problems with popen are:

* can't bypass the shell
* takes a "mode string" arg for what really should be a separate function
* takes a "-" as a magic value for what really should be a separate function
* generally kludgy

While looking around for ways to improve on it I came across the Open3
module in the stdlib, which has a much more "ruby" interface. Based mostly on
that, I redid some methods that could replace IO.popen (and put popen3 in IO
at the same time, which is where it probably belongs)

The only problem I've identified with what I have below is that I close
stderr, and this may be wrong. I also have a version that doesn't close
stderr, but I don't know which is the Right Way (Perl's IPC::Open2 doesn't
close stderr).

Can I get some opinions? There's quite a few core ruby methods that seem to
exist simply because there's some C function by that name, and that leaves
the language with a definate non-ruby feel. This is intended to replace the
current popen, and make all the popen calls consistant in operation. There's
also IO.fork_open, which is my rendition of IO.popen('-').

Thanks in advance for your input!

# Shamelessly copied from the Open3 module

def IO.popen3(cmd)
  pw = IO::pipe   # pipe[0] for read, pipe[1] for write
  pr = IO::pipe
  pe = IO::pipe
  
  pid = fork{
    # child
    fork{
      # grandchild
      pw[1].close
      STDIN.reopen(pw[0])
      pw[0].close
      
      pr[0].close
      STDOUT.reopen(pr[1])
      pr[1].close
      
      pe[0].close
      STDERR.reopen(pe[1])
      pe[1].close
      
      exec(*cmd)
    }
    exit!
  }
  
  pw[0].close
  pr[1].close
  pe[1].close
  Process.waitpid(pid)
  pi = [pw[1], pr[0], pe[0]]
  pw[1].sync = true
  if block_given?
    begin
      return yield(*pi)
    ensure
      pi.each{|p| p.close unless p.closed?}
    end
  end
  pi
end 

def IO.popen2(cmd, &block)
  ret = IO.popen3(cmd, &block)
  if block_given?
    return ret
  else
    ret[2].close
    return [ret[0], ret[1]]
  end
end

def IO.popen(cmd, &block)
  ret = IO.popen3(cmd, &block)
  if block_given?
    return ret
  else
    ret[2].close
    ret[0].close
    return ret[1]
  end
end 

def IO.fork_open
  if block_given?
    rd,wr = IO::pipe
    begin
      if pid=fork
        yield(rd,pid)
      else
        yield(wr,pid)
        exit!
      end
      Process.waitpid(pid)
    ensure
      rd.close
      wr.close
    end
  end
end