open4 0.4.0 → 0.5.0

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.
data/README CHANGED
@@ -5,10 +5,45 @@ URIS
5
5
 
6
6
  SYNOPSIS
7
7
 
8
- open child process with handles on pid, stdin, stdout, and stderr
8
+ open child process with handles on pid, stdin, stdout, and stderr: manage
9
+ child processes and their io handles easily.
9
10
 
10
11
  HISTORY
11
12
 
13
+ 0.5.0:
14
+ - on the suggestion of tim pease (thanks tim!), i added timeout features
15
+ to open4. the command run may have an overall timeout and individual
16
+ timeouts set for each of the io handles. for example
17
+
18
+ cmd = 'command_that_produce_out_at_one_second_intervals'
19
+
20
+ open4.spawn cmd, :stdout_timeout => 2
21
+
22
+ or
23
+
24
+ cmd = 'command_that_should_complete_in_about_one_minute'
25
+
26
+ open4.spawn cmd, :timeout => 60
27
+
28
+ or
29
+
30
+ cmd = 'consumes_input_at_one_line_per_second_rate'
31
+
32
+ input = %w( 42 forty-two 42.0 )
33
+
34
+ open4.spawn cmd, :stdin=>input, :stdin_timeout=>1
35
+
36
+ - added 'open4' alias so one can write
37
+
38
+ open4.spawn vs Open4.spawn
39
+
40
+ or even
41
+
42
+ open4(cmd) do |pid,i,o,e|
43
+ end
44
+
45
+ - added signal info to SpawnError
46
+
12
47
  0.4.0:
13
48
  - improved error handling contributed by jordan breeding.
14
49
  - introduction of background/bg method
@@ -185,6 +220,32 @@ SAMPLES
185
220
  ---
186
221
  stdout: "42\n"
187
222
 
223
+ ----------------------------------------------------------------------------
224
+ the timeout methods can be used to ensure execution is preceding at the
225
+ desired interval. note also how to setup a 'pipeline'
226
+ ----------------------------------------------------------------------------
227
+
228
+ harp: > cat sample/stdin_timeout.rb
229
+ require 'open4'
230
+
231
+ producer = 'ruby -e" STDOUT.sync = true; loop{sleep(rand+rand) and puts 42} "'
232
+
233
+ consumer = 'ruby -e" STDOUT.sync = true; STDIN.each{|line| puts line} "'
234
+
235
+ open4(producer) do |pid, i, o, e|
236
+
237
+ open4.spawn consumer, :stdin=>o, :stdout=>STDOUT, :stdin_timeout => 1.4
238
+
239
+ end
240
+
241
+
242
+ harp: > ruby sample/stdin_timeout.rb
243
+ 42
244
+ 42
245
+ 42
246
+ 42
247
+ 42
248
+ /dmsp/reference/ruby-1.8.1//lib/ruby/1.8/timeout.rb:42:in `relay': execution expired (Timeout::Error)
188
249
 
189
250
  AUTHOR
190
251
 
@@ -0,0 +1,311 @@
1
+ require 'fcntl'
2
+ require 'timeout'
3
+ require 'thread'
4
+
5
+ module Open4
6
+ #--{{{
7
+ VERSION = '0.5.0'
8
+ def self.version() VERSION end
9
+
10
+ class Error < ::StandardError; end
11
+
12
+ def popen4(*cmd, &b)
13
+ #--{{{
14
+ pw, pr, pe, ps = IO.pipe, IO.pipe, IO.pipe, IO.pipe
15
+
16
+ verbose = $VERBOSE
17
+ begin
18
+ $VERBOSE = nil
19
+ ps.last.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
20
+
21
+ cid = fork {
22
+ pw.last.close
23
+ STDIN.reopen pw.first
24
+ pw.first.close
25
+
26
+ pr.first.close
27
+ STDOUT.reopen pr.last
28
+ pr.last.close
29
+
30
+ pe.first.close
31
+ STDERR.reopen pe.last
32
+ pe.last.close
33
+
34
+ STDOUT.sync = STDERR.sync = true
35
+
36
+ begin
37
+ exec(*cmd)
38
+ raise 'forty-two'
39
+ rescue Exception => e
40
+ Marshal.dump(e, ps.last)
41
+ ps.last.flush
42
+ end
43
+ ps.last.close unless (ps.last.closed?)
44
+ exit!
45
+ }
46
+ ensure
47
+ $VERBOSE = verbose
48
+ end
49
+
50
+ [pw.first, pr.last, pe.last, ps.last].each{|fd| fd.close}
51
+
52
+ begin
53
+ e = Marshal.load ps.first
54
+ raise(Exception === e ? e : "unknown failure!")
55
+ rescue EOFError # If we get an EOF error, then the exec was successful
56
+ 42
57
+ end
58
+
59
+ pw.last.sync = true
60
+
61
+ pi = [pw.last, pr.first, pe.first]
62
+
63
+ if b
64
+ begin
65
+ b[cid, *pi]
66
+ Process.waitpid2(cid).last
67
+ ensure
68
+ pi.each{|fd| fd.close unless fd.closed?}
69
+ end
70
+ else
71
+ [cid, pw.last, pr.first, pe.first]
72
+ end
73
+ #--}}}
74
+ end
75
+ alias open4 popen4
76
+ module_function :popen4
77
+ module_function :open4
78
+
79
+
80
+
81
+ class SpawnError < Error
82
+ #--{{{
83
+ attr 'cmd'
84
+ attr 'status'
85
+ attr 'signals'
86
+ def exitstatus
87
+ @status.exitstatus
88
+ end
89
+ def initialize cmd, status
90
+ @cmd, @status = cmd, status
91
+ @signals = {}
92
+ if status.signaled?
93
+ @signals['termsig'] = status.termsig
94
+ @signals['stopsig'] = status.stopsig
95
+ end
96
+ sigs = @signals.map{|k,v| "#{ k }:#{ v.inspect }"}.join(' ')
97
+ super "cmd <#{ cmd }> failed with status <#{ exitstatus.inspect }> signals <#{ sigs }>"
98
+ end
99
+ #--}}}
100
+ end
101
+
102
+ class ThreadEnsemble
103
+ #--{{{
104
+ def initialize
105
+ @group = ThreadGroup.new
106
+ @kill = Queue.new
107
+ @killer = Thread.new{ k = @kill.pop and others.map{|t| t.kill unless t == k rescue next}}
108
+ @running = false
109
+ end
110
+
111
+ def add_thread *a, &b
112
+ raise if @running
113
+ @group.add(Thread.new(*a) do |*a|
114
+ Thread.stop
115
+ begin
116
+ b[*a]
117
+ ensure
118
+ kill if $!
119
+ end
120
+ end)
121
+ end
122
+
123
+ def kill
124
+ @kill.push Thread.current
125
+ end
126
+
127
+ def others
128
+ threads - [Thread.current]
129
+ end
130
+
131
+ def threads
132
+ @group.list
133
+ end
134
+
135
+ def value
136
+ others.map{|t| t.value}
137
+ end
138
+
139
+ def run
140
+ @running = true
141
+ others.map{|t| t.wakeup}
142
+ value
143
+ end
144
+ #--}}}
145
+ end
146
+
147
+ def to timeout = nil
148
+ #--{{{
149
+ Timeout.timeout(timeout){ yield }
150
+ #--}}}
151
+ end
152
+ module_function :to
153
+
154
+ def new_thread *a, &b
155
+ #--{{{
156
+ cur = Thread.current
157
+ Thread.new(*a) do |*a|
158
+ begin
159
+ b[*a]
160
+ rescue Exception => e
161
+ cur.raise e
162
+ end
163
+ end
164
+ #--}}}
165
+ end
166
+ module_function :new_thread
167
+
168
+ def getopts opts = {}
169
+ #--{{{
170
+ lambda do |*args|
171
+ keys, default, ignored = args
172
+ catch('opt') do
173
+ [keys].flatten.each do |key|
174
+ [key, key.to_s, key.to_s.intern].each do |key|
175
+ throw 'opt', opts[key] if opts.has_key?(key)
176
+ end
177
+ end
178
+ default
179
+ end
180
+ end
181
+ #--}}}
182
+ end
183
+ module_function :getopts
184
+
185
+ def relay src, dst = nil, t = nil
186
+ #--{{{
187
+ if src.respond_to? :gets
188
+ while buf = to(t){ src.gets }
189
+ dst << buf if dst
190
+ end
191
+
192
+ elsif src.respond_to? :each
193
+ q = Queue.new
194
+ th = nil
195
+
196
+ timer_set = lambda do |t|
197
+ th = new_thread{ to(t){ q.pop } }
198
+ end
199
+
200
+ timer_cancel = lambda do |t|
201
+ th.kill if th rescue nil
202
+ end
203
+
204
+ timer_set[t]
205
+ begin
206
+ src.each do |buf|
207
+ timer_cancel[t]
208
+ dst << buf if dst
209
+ timer_set[t]
210
+ end
211
+ ensure
212
+ timer_cancel[t]
213
+ end
214
+
215
+ elsif src.respond_to? :read
216
+ buf = to(t){ src.read }
217
+ dst << buf if dst
218
+
219
+ else
220
+ buf = to(t){ src.to_s }
221
+ dst << buf if dst
222
+ end
223
+ #--}}}
224
+ end
225
+ module_function :relay
226
+
227
+ def spawn cmd, opts = {}
228
+ #--{{{
229
+ getopt = getopts opts
230
+
231
+ ignore_exit_failure = getopt[ 'ignore_exit_failure', getopt['quiet', false] ]
232
+ ignore_exec_failure = getopt[ 'ignore_exec_failure', !getopt['raise', true] ]
233
+ exitstatus = getopt[ %w( exitstatus exit_status status ), 0 ]
234
+ stdin = getopt[ %w( stdin in i 0 ) << 0 ]
235
+ stdout = getopt[ %w( stdout out o 1 ) << 1 ]
236
+ stderr = getopt[ %w( stderr err e 2 ) << 2 ]
237
+ pid = getopt[ 'pid' ]
238
+ timeout = getopt[ %w( timeout spawn_timeout ) ]
239
+ stdin_timeout = getopt[ %w( stdin_timeout ) ]
240
+ stdout_timeout = getopt[ %w( stdout_timeout io_timeout ) ]
241
+ stderr_timeout = getopt[ %w( stderr_timeout ) ]
242
+
243
+ stdin ||= '' if stdin_timeout
244
+ stdout ||= '' if stdout_timeout
245
+ stderr ||= '' if stderr_timeout
246
+
247
+ started = false
248
+
249
+ status =
250
+ begin
251
+ Timeout::timeout(timeout) do
252
+ popen4(cmd) do |c, i, o, e|
253
+ started = true
254
+
255
+ %w( pid= << push update ).each do |msg|
256
+ break(pid.send(msg, c)) if pid.respond_to? msg
257
+ end
258
+
259
+ te = ThreadEnsemble.new
260
+
261
+ te.add_thread(i, stdin) do |i, stdin|
262
+ relay stdin, i, stdin_timeout if stdin
263
+ i.close rescue nil
264
+ end
265
+
266
+ te.add_thread(o, stdout) do |o, stdout|
267
+ relay o, stdout, stdout_timeout if stdout
268
+ end
269
+
270
+ te.add_thread(e, stderr) do |o, stderr|
271
+ relay e, stderr, stderr_timeout if stderr
272
+ end
273
+
274
+ te.run
275
+ end
276
+ end
277
+ rescue
278
+ raise unless(not started and ignore_exec_failure)
279
+ end
280
+
281
+ raise SpawnError.new(cmd, status) unless
282
+ (ignore_exit_failure or (status.nil? and ignore_exec_failure) or (status.exitstatus == exitstatus))
283
+
284
+ status
285
+ #--}}}
286
+ end
287
+ module_function :spawn
288
+
289
+ def background cmd, opts = {}
290
+ #--{{{
291
+ require 'thread'
292
+ q = Queue.new
293
+ opts['pid'] = opts[:pid] = q
294
+ thread = Thread.new(cmd, opts){|cmd, opts| spawn cmd, opts}
295
+ pid = q.pop
296
+ sc = class << thread; self; end
297
+ sc.module_eval {
298
+ define_method(:pid){ pid }
299
+ define_method(:spawn_status){ @spawn_status ||= value }
300
+ define_method(:exitstatus){ spawn_status.exitstatus }
301
+ }
302
+ thread
303
+ #--}}}
304
+ end
305
+ alias bg background
306
+ module_function :background
307
+ module_function :bg
308
+ #--}}}
309
+ end
310
+
311
+ def open4(*cmd, &b) cmd.size == 0 ? Open4 : Open4::popen4(*cmd, &b) end
data/lib/open4.rb CHANGED
@@ -1,12 +1,15 @@
1
1
  require 'fcntl'
2
+ require 'timeout'
3
+ require 'thread'
2
4
 
3
5
  module Open4
4
6
  #--{{{
5
- def self.version
6
- '0.4.0'
7
- end
7
+ VERSION = '0.5.0'
8
+ def self.version() VERSION end
9
+
10
+ class Error < ::StandardError; end
8
11
 
9
- def popen4(*cmd)
12
+ def popen4(*cmd, &b)
10
13
  #--{{{
11
14
  pw, pr, pe, ps = IO.pipe, IO.pipe, IO.pipe, IO.pipe
12
15
 
@@ -32,7 +35,7 @@ module Open4
32
35
 
33
36
  begin
34
37
  exec(*cmd)
35
- raise "exec failed!"
38
+ raise 'forty-two'
36
39
  rescue Exception => e
37
40
  Marshal.dump(e, ps.last)
38
41
  ps.last.flush
@@ -57,9 +60,9 @@ module Open4
57
60
 
58
61
  pi = [pw.last, pr.first, pe.first]
59
62
 
60
- if defined? yield
63
+ if b
61
64
  begin
62
- yield(cid, *pi)
65
+ b[cid, *pi]
63
66
  Process.waitpid2(cid).last
64
67
  ensure
65
68
  pi.each{|fd| fd.close unless fd.closed?}
@@ -73,23 +76,98 @@ module Open4
73
76
  module_function :popen4
74
77
  module_function :open4
75
78
 
76
- class Error < ::StandardError; end
79
+
80
+
77
81
  class SpawnError < Error
78
82
  #--{{{
79
83
  attr 'cmd'
80
84
  attr 'status'
85
+ attr 'signals'
81
86
  def exitstatus
82
87
  @status.exitstatus
83
88
  end
84
89
  def initialize cmd, status
85
90
  @cmd, @status = cmd, status
86
- super "cmd <#{ cmd }> failed with <#{ exitstatus }>"
91
+ @signals = {}
92
+ if status.signaled?
93
+ @signals['termsig'] = status.termsig
94
+ @signals['stopsig'] = status.stopsig
95
+ end
96
+ sigs = @signals.map{|k,v| "#{ k }:#{ v.inspect }"}.join(' ')
97
+ super "cmd <#{ cmd }> failed with status <#{ exitstatus.inspect }> signals <#{ sigs }>"
87
98
  end
88
99
  #--}}}
89
100
  end
90
- def spawn cmd, opts = {}
101
+
102
+ class ThreadEnsemble
103
+ #--{{{
104
+ def initialize
105
+ @group = ThreadGroup.new
106
+ @kill = Queue.new
107
+ @killer = Thread.new{ k = @kill.pop and others.map{|t| t.kill unless t == k rescue next}}
108
+ @running = false
109
+ end
110
+
111
+ def add_thread *a, &b
112
+ raise if @running
113
+ @group.add(Thread.new(*a) do |*a|
114
+ Thread.stop
115
+ begin
116
+ b[*a]
117
+ ensure
118
+ kill if $!
119
+ end
120
+ end)
121
+ end
122
+
123
+ def kill
124
+ @kill.push Thread.current
125
+ end
126
+
127
+ def others
128
+ threads - [Thread.current]
129
+ end
130
+
131
+ def threads
132
+ @group.list
133
+ end
134
+
135
+ def value
136
+ others.map{|t| t.value}
137
+ end
138
+
139
+ def run
140
+ @running = true
141
+ others.map{|t| t.wakeup}
142
+ value
143
+ end
144
+ #--}}}
145
+ end
146
+
147
+ def to timeout = nil
148
+ #--{{{
149
+ Timeout.timeout(timeout){ yield }
150
+ #--}}}
151
+ end
152
+ module_function :to
153
+
154
+ def new_thread *a, &b
155
+ #--{{{
156
+ cur = Thread.current
157
+ Thread.new(*a) do |*a|
158
+ begin
159
+ b[*a]
160
+ rescue Exception => e
161
+ cur.raise e
162
+ end
163
+ end
164
+ #--}}}
165
+ end
166
+ module_function :new_thread
167
+
168
+ def getopts opts = {}
91
169
  #--{{{
92
- getopt = lambda do |*args|
170
+ lambda do |*args|
93
171
  keys, default, ignored = args
94
172
  catch('opt') do
95
173
  [keys].flatten.each do |key|
@@ -100,45 +178,101 @@ module Open4
100
178
  default
101
179
  end
102
180
  end
181
+ #--}}}
182
+ end
183
+ module_function :getopts
184
+
185
+ def relay src, dst = nil, t = nil
186
+ #--{{{
187
+ if src.respond_to? :gets
188
+ while buf = to(t){ src.gets }
189
+ dst << buf if dst
190
+ end
191
+
192
+ elsif src.respond_to? :each
193
+ q = Queue.new
194
+ th = nil
195
+
196
+ timer_set = lambda do |t|
197
+ th = new_thread{ to(t){ q.pop } }
198
+ end
199
+
200
+ timer_cancel = lambda do |t|
201
+ th.kill if th rescue nil
202
+ end
203
+
204
+ timer_set[t]
205
+ begin
206
+ src.each do |buf|
207
+ timer_cancel[t]
208
+ dst << buf if dst
209
+ timer_set[t]
210
+ end
211
+ ensure
212
+ timer_cancel[t]
213
+ end
214
+
215
+ elsif src.respond_to? :read
216
+ buf = to(t){ src.read }
217
+ dst << buf if dst
218
+
219
+ else
220
+ buf = to(t){ src.to_s }
221
+ dst << buf if dst
222
+ end
223
+ #--}}}
224
+ end
225
+ module_function :relay
226
+
227
+ def spawn cmd, opts = {}
228
+ #--{{{
229
+ getopt = getopts opts
103
230
 
104
231
  ignore_exit_failure = getopt[ 'ignore_exit_failure', getopt['quiet', false] ]
105
232
  ignore_exec_failure = getopt[ 'ignore_exec_failure', !getopt['raise', true] ]
106
233
  exitstatus = getopt[ %w( exitstatus exit_status status ), 0 ]
107
- stdin = getopt[ ['stdin', 'in', '0', 0] ]
108
- stdout = getopt[ ['stdout', 'out', '1', 1] ]
109
- stderr = getopt[ ['stderr', 'err', '2', 2] ]
234
+ stdin = getopt[ %w( stdin in i 0 ) << 0 ]
235
+ stdout = getopt[ %w( stdout out o 1 ) << 1 ]
236
+ stderr = getopt[ %w( stderr err e 2 ) << 2 ]
110
237
  pid = getopt[ 'pid' ]
238
+ timeout = getopt[ %w( timeout spawn_timeout ) ]
239
+ stdin_timeout = getopt[ %w( stdin_timeout ) ]
240
+ stdout_timeout = getopt[ %w( stdout_timeout io_timeout ) ]
241
+ stderr_timeout = getopt[ %w( stderr_timeout ) ]
242
+
243
+ stdin ||= '' if stdin_timeout
244
+ stdout ||= '' if stdout_timeout
245
+ stderr ||= '' if stderr_timeout
111
246
 
112
247
  started = false
113
248
 
114
249
  status =
115
250
  begin
116
- popen4(cmd) do |c, i, o, e|
117
- started = true
251
+ Timeout::timeout(timeout) do
252
+ popen4(cmd) do |c, i, o, e|
253
+ started = true
118
254
 
119
- if pid.respond_to? '<<'
120
- pid << c
121
- end
255
+ %w( pid= << push update ).each do |msg|
256
+ break(pid.send(msg, c)) if pid.respond_to? msg
257
+ end
258
+
259
+ te = ThreadEnsemble.new
122
260
 
123
- it = Thread.new(i,stdin) do |i,stdin|
124
- if stdin
125
- if stdin.respond_to? :each
126
- stdin.each{|buf| i << buf}
127
- elsif stdin.respond_to? :read
128
- i << stdin.read
129
- else
130
- i << stdin.to_s
261
+ te.add_thread(i, stdin) do |i, stdin|
262
+ relay stdin, i, stdin_timeout if stdin
263
+ i.close rescue nil
131
264
  end
132
- end
133
- i.close
134
- end
135
265
 
136
- ot = Thread.new(o,stdout){|o,stdout| o.each{|buf| stdout << buf if stdout}}
137
- et = Thread.new(e,stderr){|e,stderr| e.each{|buf| stderr << buf if stderr}}
266
+ te.add_thread(o, stdout) do |o, stdout|
267
+ relay o, stdout, stdout_timeout if stdout
268
+ end
138
269
 
139
- it.join
140
- ot.join if ot
141
- et.join if et
270
+ te.add_thread(e, stderr) do |o, stderr|
271
+ relay e, stderr, stderr_timeout if stderr
272
+ end
273
+
274
+ te.run
275
+ end
142
276
  end
143
277
  rescue
144
278
  raise unless(not started and ignore_exec_failure)
@@ -173,3 +307,5 @@ module Open4
173
307
  module_function :bg
174
308
  #--}}}
175
309
  end
310
+
311
+ def open4(*cmd, &b) cmd.size == 0 ? Open4 : Open4::popen4(*cmd, &b) end
@@ -0,0 +1,9 @@
1
+ require 'open4'
2
+
3
+ producer = 'ruby -e" STDOUT.sync = true; loop{sleep(rand+rand) and puts 42} " 2>/dev/null'
4
+
5
+ consumer = 'ruby -e" STDOUT.sync = true; STDIN.each{|line| puts line} "'
6
+
7
+ open4(producer) do |pid, i, o, e|
8
+ open4.spawn consumer, 0=>o, 1=>STDOUT, :stdin_timeout => 1.4
9
+ end
data/sample/timeout.rb ADDED
@@ -0,0 +1,37 @@
1
+
2
+ require 'open4'
3
+
4
+ def show_failure
5
+ fork{ yield }
6
+ Process.wait
7
+ puts
8
+ end
9
+
10
+ #
11
+ # command timeout
12
+ #
13
+ show_failure{
14
+ open4.spawn 'sleep 42', 'timeout' => 1
15
+ }
16
+
17
+ #
18
+ # stdin timeout
19
+ #
20
+ show_failure{
21
+
22
+ producer = 'ruby -e" STDOUT.sync = true; loop{sleep(rand+rand) and puts 42} " 2>/dev/null'
23
+
24
+ consumer = 'ruby -e" STDOUT.sync = true; STDIN.each{|line| puts line} "'
25
+
26
+ open4(producer) do |pid, i, o, e|
27
+ open4.spawn consumer, 0=>o, 1=>STDOUT, :stdin_timeout => 1.4
28
+ end
29
+ }
30
+
31
+ #
32
+ # stdout timeout (stderr is similar)
33
+ #
34
+
35
+ show_failure{
36
+ open4.spawn 'ruby -e" sleep 2 and puts 42 "', 'stdout_timeout' => 1
37
+ }
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.8.11
3
3
  specification_version: 1
4
4
  name: open4
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.4.0
7
- date: 2006-04-26 00:00:00.000000 -06:00
6
+ version: 0.5.0
7
+ date: 2006-08-07 00:00:00.000000 -06:00
8
8
  summary: open4
9
9
  require_paths:
10
10
  - lib
@@ -36,14 +36,15 @@ files:
36
36
  - build
37
37
  - install
38
38
  - gemspec.rb
39
- - open4-0.4.0.gem
40
39
  - sample/block.rb
41
40
  - sample/simple.rb
42
41
  - sample/exception.rb
43
42
  - sample/spawn.rb
44
43
  - sample/bg.rb
45
- - lib/open4-0.4.0.rb
44
+ - sample/timeout.rb
45
+ - sample/stdin_timeout.rb
46
46
  - lib/open4.rb
47
+ - lib/open4-0.5.0.rb
47
48
  test_files: []
48
49
  rdoc_options: []
49
50
  extra_rdoc_files: []
data/lib/open4-0.4.0.rb DELETED
@@ -1,175 +0,0 @@
1
- require 'fcntl'
2
-
3
- module Open4
4
- #--{{{
5
- def self.version
6
- '0.4.0'
7
- end
8
-
9
- def popen4(*cmd)
10
- #--{{{
11
- pw, pr, pe, ps = IO.pipe, IO.pipe, IO.pipe, IO.pipe
12
-
13
- verbose = $VERBOSE
14
- begin
15
- $VERBOSE = nil
16
- ps.last.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
17
-
18
- cid = fork {
19
- pw.last.close
20
- STDIN.reopen pw.first
21
- pw.first.close
22
-
23
- pr.first.close
24
- STDOUT.reopen pr.last
25
- pr.last.close
26
-
27
- pe.first.close
28
- STDERR.reopen pe.last
29
- pe.last.close
30
-
31
- STDOUT.sync = STDERR.sync = true
32
-
33
- begin
34
- exec(*cmd)
35
- raise "exec failed!"
36
- rescue Exception => e
37
- Marshal.dump(e, ps.last)
38
- ps.last.flush
39
- end
40
- ps.last.close unless (ps.last.closed?)
41
- exit!
42
- }
43
- ensure
44
- $VERBOSE = verbose
45
- end
46
-
47
- [pw.first, pr.last, pe.last, ps.last].each{|fd| fd.close}
48
-
49
- begin
50
- e = Marshal.load ps.first
51
- raise(Exception === e ? e : "unknown failure!")
52
- rescue EOFError # If we get an EOF error, then the exec was successful
53
- 42
54
- end
55
-
56
- pw.last.sync = true
57
-
58
- pi = [pw.last, pr.first, pe.first]
59
-
60
- if defined? yield
61
- begin
62
- yield(cid, *pi)
63
- Process.waitpid2(cid).last
64
- ensure
65
- pi.each{|fd| fd.close unless fd.closed?}
66
- end
67
- else
68
- [cid, pw.last, pr.first, pe.first]
69
- end
70
- #--}}}
71
- end
72
- alias open4 popen4
73
- module_function :popen4
74
- module_function :open4
75
-
76
- class Error < ::StandardError; end
77
- class SpawnError < Error
78
- #--{{{
79
- attr 'cmd'
80
- attr 'status'
81
- def exitstatus
82
- @status.exitstatus
83
- end
84
- def initialize cmd, status
85
- @cmd, @status = cmd, status
86
- super "cmd <#{ cmd }> failed with <#{ exitstatus }>"
87
- end
88
- #--}}}
89
- end
90
- def spawn cmd, opts = {}
91
- #--{{{
92
- getopt = lambda do |*args|
93
- keys, default, ignored = args
94
- catch('opt') do
95
- [keys].flatten.each do |key|
96
- [key, key.to_s, key.to_s.intern].each do |key|
97
- throw 'opt', opts[key] if opts.has_key?(key)
98
- end
99
- end
100
- default
101
- end
102
- end
103
-
104
- ignore_exit_failure = getopt[ 'ignore_exit_failure', getopt['quiet', false] ]
105
- ignore_exec_failure = getopt[ 'ignore_exec_failure', !getopt['raise', true] ]
106
- exitstatus = getopt[ %w( exitstatus exit_status status ), 0 ]
107
- stdin = getopt[ ['stdin', 'in', '0', 0] ]
108
- stdout = getopt[ ['stdout', 'out', '1', 1] ]
109
- stderr = getopt[ ['stderr', 'err', '2', 2] ]
110
- pid = getopt[ 'pid' ]
111
-
112
- started = false
113
-
114
- status =
115
- begin
116
- popen4(cmd) do |c, i, o, e|
117
- started = true
118
-
119
- if pid.respond_to? '<<'
120
- pid << c
121
- end
122
-
123
- it = Thread.new(i,stdin) do |i,stdin|
124
- if stdin
125
- if stdin.respond_to? :each
126
- stdin.each{|buf| i << buf}
127
- elsif stdin.respond_to? :read
128
- i << stdin.read
129
- else
130
- i << stdin.to_s
131
- end
132
- end
133
- i.close
134
- end
135
-
136
- ot = Thread.new(o,stdout){|o,stdout| o.each{|buf| stdout << buf if stdout}}
137
- et = Thread.new(e,stderr){|e,stderr| e.each{|buf| stderr << buf if stderr}}
138
-
139
- it.join
140
- ot.join if ot
141
- et.join if et
142
- end
143
- rescue
144
- raise unless(not started and ignore_exec_failure)
145
- end
146
-
147
- raise SpawnError.new(cmd, status) unless
148
- (ignore_exit_failure or (status.nil? and ignore_exec_failure) or (status.exitstatus == exitstatus))
149
-
150
- status
151
- #--}}}
152
- end
153
- module_function :spawn
154
-
155
- def background cmd, opts = {}
156
- #--{{{
157
- require 'thread'
158
- q = Queue.new
159
- opts['pid'] = opts[:pid] = q
160
- thread = Thread.new(cmd, opts){|cmd, opts| spawn cmd, opts}
161
- pid = q.pop
162
- sc = class << thread; self; end
163
- sc.module_eval {
164
- define_method(:pid){ pid }
165
- define_method(:spawn_status){ @spawn_status ||= value }
166
- define_method(:exitstatus){ spawn_status.exitstatus }
167
- }
168
- thread
169
- #--}}}
170
- end
171
- alias bg background
172
- module_function :background
173
- module_function :bg
174
- #--}}}
175
- end
data/open4-0.4.0.gem DELETED
File without changes