public
Description: A process execution library which doesn't suck.
Clone URL: git://github.com/codahale/ropen.git
Search Repo:
commit  0c2fa7886f32f108f8a4dfefedbbab2e17361596
tree    bb0994710d2eb510ad500d66ac87a416e9283b63
parent  0c3f82c6448d804018252b8ef6b996977f9a446d
ropen / lib / ropen / command.rb
100644 89 lines (75 sloc) 2.004 kb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
require "ropen"
require "ropen/events"
require "ropen/pipe"
require "ropen/spool"
 
# TODO: document me
# TODO: add timeouts for processes stalling for lack of stdin
 
class Ropen::Command
  attr_reader :executable, :arguments, :exit_status
  
  def initialize(executable, *arguments)
    @executable = File.expand_path(executable)
    @arguments = arguments
    check_executable(executable)
    @stdout_events = Ropen::Events.new
    @stderr_events = Ropen::Events.new
    @stdin_spool = Ropen::Spool.new
    yield self if block_given?
  end
  
  def run
    initialize_streams
    pid = fork do
      @stdin.bind_input(STDIN)
      @stdout.bind_output(STDOUT)
      @stderr.bind_output(STDERR)
      exec(@executable, *@arguments)
    end
    stdin, stdout, stderr = open_streams(pid)
    @stdin_io = stdin
    @stdout_events.run(stdout)
    @stderr_events.run(stderr)
    @stdin_spool.replay(stdin)
    [@stdout_events, @stderr_events].each { |e| e.finish }
    Process.waitpid(pid)
    return @exit_status = $?
  ensure
    finalize_streams
  end
  
  def stdin
    @stdin_io || @stdin_spool
  end
  
  def stdout
    @stdout_events
  end
  
  def stderr
    @stderr_events
  end
  
private
  
  def check_executable(executable)
    unless File.exist?(@executable)
      raise Ropen::InvalidExecutableError.new("#{executable} does not exist")
    end
    
    unless File.executable?(@executable)
      raise Ropen::InvalidExecutableError.new("#{executable} is not executable")
    end
  end
  
  def initialize_streams
    @stdin = Ropen::Pipe.new
    @stdout = Ropen::Pipe.new
    @stderr = Ropen::Pipe.new
  end
  
  def open_streams(pid)
    @stdin.close_reader
    @stdout.close_writer
    @stderr.close_writer
# Process.waitpid(pid)
    @stdin.writer.sync = true
    return [@stdin.writer, @stdout.reader, @stderr.reader]
  end
  
  def finalize_streams
    [@stdin, @stdout, @stderr].each { |s| s.close }
    @stdin = nil
    @stdin_io = nil
    @stdout = nil
    @stderr = nil
  end
  
end