open3 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 6f1dc26c8d711ed9394d746ea2fad6222de3cce813178dfb2d1aa52eb4fc0432
4
+ data.tar.gz: 121bb5a4ee245b344004afdf1bdabf0af421c5acaa306b08de5ff62865a896c3
5
+ SHA512:
6
+ metadata.gz: ccb106824f5e6a47f6cf4324bc7638af0ea27fda57b87da181fb32c60cec309590a6d828635bb27afbfb13b3113e857a1d23cf88665a1630eeb5ea4e4e2dfe2c
7
+ data.tar.gz: '0918ee950ed53eeed0b64b569034496953b5e788d937bac2fb30c1ee1608c645adc5cd1c76c1982e263a3ae999af23e0783648c48a08e7f3a14d9fe1f3331924'
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ Gemfile.lock
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ cache: bundler
3
+ rvm:
4
+ - 2.6.3
5
+ - ruby-head
6
+ script: rake test
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ group :development do
6
+ gem "bundler"
7
+ gem "rake"
8
+ gem "test-unit"
9
+ end
@@ -0,0 +1,22 @@
1
+ Copyright (C) 1993-2013 Yukihiro Matsumoto. All rights reserved.
2
+
3
+ Redistribution and use in source and binary forms, with or without
4
+ modification, are permitted provided that the following conditions
5
+ are met:
6
+ 1. Redistributions of source code must retain the above copyright
7
+ notice, this list of conditions and the following disclaimer.
8
+ 2. Redistributions in binary form must reproduce the above copyright
9
+ notice, this list of conditions and the following disclaimer in the
10
+ documentation and/or other materials provided with the distribution.
11
+
12
+ THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
13
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
14
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
15
+ ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
16
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
17
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
18
+ OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
19
+ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
20
+ LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
21
+ OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
22
+ SUCH DAMAGE.
@@ -0,0 +1,47 @@
1
+ # Open3
2
+
3
+ Open3 gives you access to stdin, stdout, and stderr when running other
4
+ programs.
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ ```ruby
11
+ gem 'open3'
12
+ ```
13
+
14
+ And then execute:
15
+
16
+ $ bundle
17
+
18
+ Or install it yourself as:
19
+
20
+ $ gem install open3
21
+
22
+ ## Usage
23
+
24
+ Open3 grants you access to stdin, stdout, stderr and a thread to wait for the child process when running another program.
25
+ You can specify various attributes, redirections, current directory, etc., of the program in the same way as for Process.spawn.
26
+
27
+ - Open3.popen3 : pipes for stdin, stdout, stderr
28
+ - Open3.popen2 : pipes for stdin, stdout
29
+ - Open3.popen2e : pipes for stdin, merged stdout and stderr
30
+ - Open3.capture3 : give a string for stdin; get strings for stdout, stderr
31
+ - Open3.capture2 : give a string for stdin; get a string for stdout
32
+ - Open3.capture2e : give a string for stdin; get a string for merged stdout and stderr
33
+ - Open3.pipeline_rw : pipes for first stdin and last stdout of a pipeline
34
+ - Open3.pipeline_r : pipe for last stdout of a pipeline
35
+ - Open3.pipeline_w : pipe for first stdin of a pipeline
36
+ - Open3.pipeline_start : run a pipeline without waiting
37
+ - Open3.pipeline : run a pipeline and wait for its completion
38
+
39
+ ## Development
40
+
41
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
42
+
43
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
44
+
45
+ ## Contributing
46
+
47
+ Bug reports and pull requests are welcome on GitHub at https://github.com/ruby/open3.
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test/lib"
6
+ t.ruby_opts << "-rhelper"
7
+ t.test_files = FileList["test/**/test_*.rb"]
8
+ end
9
+
10
+ task :default => :test
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "open3"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,752 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # = open3.rb: Popen, but with stderr, too
5
+ #
6
+ # Author:: Yukihiro Matsumoto
7
+ # Documentation:: Konrad Meyer
8
+ #
9
+ # Open3 gives you access to stdin, stdout, and stderr when running other
10
+ # programs.
11
+ #
12
+
13
+ #
14
+ # Open3 grants you access to stdin, stdout, stderr and a thread to wait for the
15
+ # child process when running another program.
16
+ # You can specify various attributes, redirections, current directory, etc., of
17
+ # the program in the same way as for Process.spawn.
18
+ #
19
+ # - Open3.popen3 : pipes for stdin, stdout, stderr
20
+ # - Open3.popen2 : pipes for stdin, stdout
21
+ # - Open3.popen2e : pipes for stdin, merged stdout and stderr
22
+ # - Open3.capture3 : give a string for stdin; get strings for stdout, stderr
23
+ # - Open3.capture2 : give a string for stdin; get a string for stdout
24
+ # - Open3.capture2e : give a string for stdin; get a string for merged stdout and stderr
25
+ # - Open3.pipeline_rw : pipes for first stdin and last stdout of a pipeline
26
+ # - Open3.pipeline_r : pipe for last stdout of a pipeline
27
+ # - Open3.pipeline_w : pipe for first stdin of a pipeline
28
+ # - Open3.pipeline_start : run a pipeline without waiting
29
+ # - Open3.pipeline : run a pipeline and wait for its completion
30
+ #
31
+
32
+ module Open3
33
+
34
+ # Open stdin, stdout, and stderr streams and start external executable.
35
+ # In addition, a thread to wait for the started process is created.
36
+ # The thread has a pid method and a thread variable :pid which is the pid of
37
+ # the started process.
38
+ #
39
+ # Block form:
40
+ #
41
+ # Open3.popen3([env,] cmd... [, opts]) {|stdin, stdout, stderr, wait_thr|
42
+ # pid = wait_thr.pid # pid of the started process.
43
+ # ...
44
+ # exit_status = wait_thr.value # Process::Status object returned.
45
+ # }
46
+ #
47
+ # Non-block form:
48
+ #
49
+ # stdin, stdout, stderr, wait_thr = Open3.popen3([env,] cmd... [, opts])
50
+ # pid = wait_thr[:pid] # pid of the started process
51
+ # ...
52
+ # stdin.close # stdin, stdout and stderr should be closed explicitly in this form.
53
+ # stdout.close
54
+ # stderr.close
55
+ # exit_status = wait_thr.value # Process::Status object returned.
56
+ #
57
+ # The parameters env, cmd, and opts are passed to Process.spawn.
58
+ # A commandline string and a list of argument strings can be accepted as follows:
59
+ #
60
+ # Open3.popen3("echo abc") {|i, o, e, t| ... }
61
+ # Open3.popen3("echo", "abc") {|i, o, e, t| ... }
62
+ # Open3.popen3(["echo", "argv0"], "abc") {|i, o, e, t| ... }
63
+ #
64
+ # If the last parameter, opts, is a Hash, it is recognized as an option for Process.spawn.
65
+ #
66
+ # Open3.popen3("pwd", :chdir=>"/") {|i,o,e,t|
67
+ # p o.read.chomp #=> "/"
68
+ # }
69
+ #
70
+ # wait_thr.value waits for the termination of the process.
71
+ # The block form also waits for the process when it returns.
72
+ #
73
+ # Closing stdin, stdout and stderr does not wait for the process to complete.
74
+ #
75
+ # You should be careful to avoid deadlocks.
76
+ # Since pipes are fixed length buffers,
77
+ # Open3.popen3("prog") {|i, o, e, t| o.read } deadlocks if
78
+ # the program generates too much output on stderr.
79
+ # You should read stdout and stderr simultaneously (using threads or IO.select).
80
+ # However, if you don't need stderr output, you can use Open3.popen2.
81
+ # If merged stdout and stderr output is not a problem, you can use Open3.popen2e.
82
+ # If you really need stdout and stderr output as separate strings, you can consider Open3.capture3.
83
+ #
84
+ def popen3(*cmd, &block)
85
+ if Hash === cmd.last
86
+ opts = cmd.pop.dup
87
+ else
88
+ opts = {}
89
+ end
90
+
91
+ in_r, in_w = IO.pipe
92
+ opts[:in] = in_r
93
+ in_w.sync = true
94
+
95
+ out_r, out_w = IO.pipe
96
+ opts[:out] = out_w
97
+
98
+ err_r, err_w = IO.pipe
99
+ opts[:err] = err_w
100
+
101
+ popen_run(cmd, opts, [in_r, out_w, err_w], [in_w, out_r, err_r], &block)
102
+ end
103
+ module_function :popen3
104
+
105
+ # Open3.popen2 is similar to Open3.popen3 except that it doesn't create a pipe for
106
+ # the standard error stream.
107
+ #
108
+ # Block form:
109
+ #
110
+ # Open3.popen2([env,] cmd... [, opts]) {|stdin, stdout, wait_thr|
111
+ # pid = wait_thr.pid # pid of the started process.
112
+ # ...
113
+ # exit_status = wait_thr.value # Process::Status object returned.
114
+ # }
115
+ #
116
+ # Non-block form:
117
+ #
118
+ # stdin, stdout, wait_thr = Open3.popen2([env,] cmd... [, opts])
119
+ # ...
120
+ # stdin.close # stdin and stdout should be closed explicitly in this form.
121
+ # stdout.close
122
+ #
123
+ # See Process.spawn for the optional hash arguments _env_ and _opts_.
124
+ #
125
+ # Example:
126
+ #
127
+ # Open3.popen2("wc -c") {|i,o,t|
128
+ # i.print "answer to life the universe and everything"
129
+ # i.close
130
+ # p o.gets #=> "42\n"
131
+ # }
132
+ #
133
+ # Open3.popen2("bc -q") {|i,o,t|
134
+ # i.puts "obase=13"
135
+ # i.puts "6 * 9"
136
+ # p o.gets #=> "42\n"
137
+ # }
138
+ #
139
+ # Open3.popen2("dc") {|i,o,t|
140
+ # i.print "42P"
141
+ # i.close
142
+ # p o.read #=> "*"
143
+ # }
144
+ #
145
+ def popen2(*cmd, &block)
146
+ if Hash === cmd.last
147
+ opts = cmd.pop.dup
148
+ else
149
+ opts = {}
150
+ end
151
+
152
+ in_r, in_w = IO.pipe
153
+ opts[:in] = in_r
154
+ in_w.sync = true
155
+
156
+ out_r, out_w = IO.pipe
157
+ opts[:out] = out_w
158
+
159
+ popen_run(cmd, opts, [in_r, out_w], [in_w, out_r], &block)
160
+ end
161
+ module_function :popen2
162
+
163
+ # Open3.popen2e is similar to Open3.popen3 except that it merges
164
+ # the standard output stream and the standard error stream.
165
+ #
166
+ # Block form:
167
+ #
168
+ # Open3.popen2e([env,] cmd... [, opts]) {|stdin, stdout_and_stderr, wait_thr|
169
+ # pid = wait_thr.pid # pid of the started process.
170
+ # ...
171
+ # exit_status = wait_thr.value # Process::Status object returned.
172
+ # }
173
+ #
174
+ # Non-block form:
175
+ #
176
+ # stdin, stdout_and_stderr, wait_thr = Open3.popen2e([env,] cmd... [, opts])
177
+ # ...
178
+ # stdin.close # stdin and stdout_and_stderr should be closed explicitly in this form.
179
+ # stdout_and_stderr.close
180
+ #
181
+ # See Process.spawn for the optional hash arguments _env_ and _opts_.
182
+ #
183
+ # Example:
184
+ # # check gcc warnings
185
+ # source = "foo.c"
186
+ # Open3.popen2e("gcc", "-Wall", source) {|i,oe,t|
187
+ # oe.each {|line|
188
+ # if /warning/ =~ line
189
+ # ...
190
+ # end
191
+ # }
192
+ # }
193
+ #
194
+ def popen2e(*cmd, &block)
195
+ if Hash === cmd.last
196
+ opts = cmd.pop.dup
197
+ else
198
+ opts = {}
199
+ end
200
+
201
+ in_r, in_w = IO.pipe
202
+ opts[:in] = in_r
203
+ in_w.sync = true
204
+
205
+ out_r, out_w = IO.pipe
206
+ opts[[:out, :err]] = out_w
207
+
208
+ popen_run(cmd, opts, [in_r, out_w], [in_w, out_r], &block)
209
+ end
210
+ module_function :popen2e
211
+
212
+ def popen_run(cmd, opts, child_io, parent_io) # :nodoc:
213
+ pid = spawn(*cmd, opts)
214
+ wait_thr = Process.detach(pid)
215
+ child_io.each(&:close)
216
+ result = [*parent_io, wait_thr]
217
+ if defined? yield
218
+ begin
219
+ return yield(*result)
220
+ ensure
221
+ parent_io.each(&:close)
222
+ wait_thr.join
223
+ end
224
+ end
225
+ result
226
+ end
227
+ module_function :popen_run
228
+ class << self
229
+ private :popen_run
230
+ end
231
+
232
+ # Open3.capture3 captures the standard output and the standard error of a command.
233
+ #
234
+ # stdout_str, stderr_str, status = Open3.capture3([env,] cmd... [, opts])
235
+ #
236
+ # The arguments env, cmd and opts are passed to Open3.popen3 except
237
+ # <code>opts[:stdin_data]</code> and <code>opts[:binmode]</code>. See Process.spawn.
238
+ #
239
+ # If <code>opts[:stdin_data]</code> is specified, it is sent to the command's standard input.
240
+ #
241
+ # If <code>opts[:binmode]</code> is true, internal pipes are set to binary mode.
242
+ #
243
+ # Examples:
244
+ #
245
+ # # dot is a command of graphviz.
246
+ # graph = <<'End'
247
+ # digraph g {
248
+ # a -> b
249
+ # }
250
+ # End
251
+ # drawn_graph, dot_log = Open3.capture3("dot -v", :stdin_data=>graph)
252
+ #
253
+ # o, e, s = Open3.capture3("echo abc; sort >&2", :stdin_data=>"foo\nbar\nbaz\n")
254
+ # p o #=> "abc\n"
255
+ # p e #=> "bar\nbaz\nfoo\n"
256
+ # p s #=> #<Process::Status: pid 32682 exit 0>
257
+ #
258
+ # # generate a thumbnail image using the convert command of ImageMagick.
259
+ # # However, if the image is really stored in a file,
260
+ # # system("convert", "-thumbnail", "80", "png:#{filename}", "png:-") is better
261
+ # # because of reduced memory consumption.
262
+ # # But if the image is stored in a DB or generated by the gnuplot Open3.capture2 example,
263
+ # # Open3.capture3 should be considered.
264
+ # #
265
+ # image = File.read("/usr/share/openclipart/png/animals/mammals/sheep-md-v0.1.png", :binmode=>true)
266
+ # thumbnail, err, s = Open3.capture3("convert -thumbnail 80 png:- png:-", :stdin_data=>image, :binmode=>true)
267
+ # if s.success?
268
+ # STDOUT.binmode; print thumbnail
269
+ # end
270
+ #
271
+ def capture3(*cmd)
272
+ if Hash === cmd.last
273
+ opts = cmd.pop.dup
274
+ else
275
+ opts = {}
276
+ end
277
+
278
+ stdin_data = opts.delete(:stdin_data) || ''
279
+ binmode = opts.delete(:binmode)
280
+
281
+ popen3(*cmd, opts) {|i, o, e, t|
282
+ if binmode
283
+ i.binmode
284
+ o.binmode
285
+ e.binmode
286
+ end
287
+ out_reader = Thread.new { o.read }
288
+ err_reader = Thread.new { e.read }
289
+ begin
290
+ if stdin_data.respond_to? :readpartial
291
+ IO.copy_stream(stdin_data, i)
292
+ else
293
+ i.write stdin_data
294
+ end
295
+ rescue Errno::EPIPE
296
+ end
297
+ i.close
298
+ [out_reader.value, err_reader.value, t.value]
299
+ }
300
+ end
301
+ module_function :capture3
302
+
303
+ # Open3.capture2 captures the standard output of a command.
304
+ #
305
+ # stdout_str, status = Open3.capture2([env,] cmd... [, opts])
306
+ #
307
+ # The arguments env, cmd and opts are passed to Open3.popen3 except
308
+ # <code>opts[:stdin_data]</code> and <code>opts[:binmode]</code>. See Process.spawn.
309
+ #
310
+ # If <code>opts[:stdin_data]</code> is specified, it is sent to the command's standard input.
311
+ #
312
+ # If <code>opts[:binmode]</code> is true, internal pipes are set to binary mode.
313
+ #
314
+ # Example:
315
+ #
316
+ # # factor is a command for integer factorization.
317
+ # o, s = Open3.capture2("factor", :stdin_data=>"42")
318
+ # p o #=> "42: 2 3 7\n"
319
+ #
320
+ # # generate x**2 graph in png using gnuplot.
321
+ # gnuplot_commands = <<"End"
322
+ # set terminal png
323
+ # plot x**2, "-" with lines
324
+ # 1 14
325
+ # 2 1
326
+ # 3 8
327
+ # 4 5
328
+ # e
329
+ # End
330
+ # image, s = Open3.capture2("gnuplot", :stdin_data=>gnuplot_commands, :binmode=>true)
331
+ #
332
+ def capture2(*cmd)
333
+ if Hash === cmd.last
334
+ opts = cmd.pop.dup
335
+ else
336
+ opts = {}
337
+ end
338
+
339
+ stdin_data = opts.delete(:stdin_data)
340
+ binmode = opts.delete(:binmode)
341
+
342
+ popen2(*cmd, opts) {|i, o, t|
343
+ if binmode
344
+ i.binmode
345
+ o.binmode
346
+ end
347
+ out_reader = Thread.new { o.read }
348
+ if stdin_data
349
+ begin
350
+ if stdin_data.respond_to? :readpartial
351
+ IO.copy_stream(stdin_data, i)
352
+ else
353
+ i.write stdin_data
354
+ end
355
+ rescue Errno::EPIPE
356
+ end
357
+ end
358
+ i.close
359
+ [out_reader.value, t.value]
360
+ }
361
+ end
362
+ module_function :capture2
363
+
364
+ # Open3.capture2e captures the standard output and the standard error of a command.
365
+ #
366
+ # stdout_and_stderr_str, status = Open3.capture2e([env,] cmd... [, opts])
367
+ #
368
+ # The arguments env, cmd and opts are passed to Open3.popen3 except
369
+ # <code>opts[:stdin_data]</code> and <code>opts[:binmode]</code>. See Process.spawn.
370
+ #
371
+ # If <code>opts[:stdin_data]</code> is specified, it is sent to the command's standard input.
372
+ #
373
+ # If <code>opts[:binmode]</code> is true, internal pipes are set to binary mode.
374
+ #
375
+ # Example:
376
+ #
377
+ # # capture make log
378
+ # make_log, s = Open3.capture2e("make")
379
+ #
380
+ def capture2e(*cmd)
381
+ if Hash === cmd.last
382
+ opts = cmd.pop.dup
383
+ else
384
+ opts = {}
385
+ end
386
+
387
+ stdin_data = opts.delete(:stdin_data)
388
+ binmode = opts.delete(:binmode)
389
+
390
+ popen2e(*cmd, opts) {|i, oe, t|
391
+ if binmode
392
+ i.binmode
393
+ oe.binmode
394
+ end
395
+ outerr_reader = Thread.new { oe.read }
396
+ if stdin_data
397
+ begin
398
+ if stdin_data.respond_to? :readpartial
399
+ IO.copy_stream(stdin_data, i)
400
+ else
401
+ i.write stdin_data
402
+ end
403
+ rescue Errno::EPIPE
404
+ end
405
+ end
406
+ i.close
407
+ [outerr_reader.value, t.value]
408
+ }
409
+ end
410
+ module_function :capture2e
411
+
412
+ # Open3.pipeline_rw starts a list of commands as a pipeline with pipes
413
+ # which connect to stdin of the first command and stdout of the last command.
414
+ #
415
+ # Open3.pipeline_rw(cmd1, cmd2, ... [, opts]) {|first_stdin, last_stdout, wait_threads|
416
+ # ...
417
+ # }
418
+ #
419
+ # first_stdin, last_stdout, wait_threads = Open3.pipeline_rw(cmd1, cmd2, ... [, opts])
420
+ # ...
421
+ # first_stdin.close
422
+ # last_stdout.close
423
+ #
424
+ # Each cmd is a string or an array.
425
+ # If it is an array, the elements are passed to Process.spawn.
426
+ #
427
+ # cmd:
428
+ # commandline command line string which is passed to a shell
429
+ # [env, commandline, opts] command line string which is passed to a shell
430
+ # [env, cmdname, arg1, ..., opts] command name and one or more arguments (no shell)
431
+ # [env, [cmdname, argv0], arg1, ..., opts] command name and arguments including argv[0] (no shell)
432
+ #
433
+ # Note that env and opts are optional, as for Process.spawn.
434
+ #
435
+ # The options to pass to Process.spawn are constructed by merging
436
+ # +opts+, the last hash element of the array, and
437
+ # specifications for the pipes between each of the commands.
438
+ #
439
+ # Example:
440
+ #
441
+ # Open3.pipeline_rw("tr -dc A-Za-z", "wc -c") {|i, o, ts|
442
+ # i.puts "All persons more than a mile high to leave the court."
443
+ # i.close
444
+ # p o.gets #=> "42\n"
445
+ # }
446
+ #
447
+ # Open3.pipeline_rw("sort", "cat -n") {|stdin, stdout, wait_thrs|
448
+ # stdin.puts "foo"
449
+ # stdin.puts "bar"
450
+ # stdin.puts "baz"
451
+ # stdin.close # send EOF to sort.
452
+ # p stdout.read #=> " 1\tbar\n 2\tbaz\n 3\tfoo\n"
453
+ # }
454
+ def pipeline_rw(*cmds, &block)
455
+ if Hash === cmds.last
456
+ opts = cmds.pop.dup
457
+ else
458
+ opts = {}
459
+ end
460
+
461
+ in_r, in_w = IO.pipe
462
+ opts[:in] = in_r
463
+ in_w.sync = true
464
+
465
+ out_r, out_w = IO.pipe
466
+ opts[:out] = out_w
467
+
468
+ pipeline_run(cmds, opts, [in_r, out_w], [in_w, out_r], &block)
469
+ end
470
+ module_function :pipeline_rw
471
+
472
+ # Open3.pipeline_r starts a list of commands as a pipeline with a pipe
473
+ # which connects to stdout of the last command.
474
+ #
475
+ # Open3.pipeline_r(cmd1, cmd2, ... [, opts]) {|last_stdout, wait_threads|
476
+ # ...
477
+ # }
478
+ #
479
+ # last_stdout, wait_threads = Open3.pipeline_r(cmd1, cmd2, ... [, opts])
480
+ # ...
481
+ # last_stdout.close
482
+ #
483
+ # Each cmd is a string or an array.
484
+ # If it is an array, the elements are passed to Process.spawn.
485
+ #
486
+ # cmd:
487
+ # commandline command line string which is passed to a shell
488
+ # [env, commandline, opts] command line string which is passed to a shell
489
+ # [env, cmdname, arg1, ..., opts] command name and one or more arguments (no shell)
490
+ # [env, [cmdname, argv0], arg1, ..., opts] command name and arguments including argv[0] (no shell)
491
+ #
492
+ # Note that env and opts are optional, as for Process.spawn.
493
+ #
494
+ # Example:
495
+ #
496
+ # Open3.pipeline_r("zcat /var/log/apache2/access.log.*.gz",
497
+ # [{"LANG"=>"C"}, "grep", "GET /favicon.ico"],
498
+ # "logresolve") {|o, ts|
499
+ # o.each_line {|line|
500
+ # ...
501
+ # }
502
+ # }
503
+ #
504
+ # Open3.pipeline_r("yes", "head -10") {|o, ts|
505
+ # p o.read #=> "y\ny\ny\ny\ny\ny\ny\ny\ny\ny\n"
506
+ # p ts[0].value #=> #<Process::Status: pid 24910 SIGPIPE (signal 13)>
507
+ # p ts[1].value #=> #<Process::Status: pid 24913 exit 0>
508
+ # }
509
+ #
510
+ def pipeline_r(*cmds, &block)
511
+ if Hash === cmds.last
512
+ opts = cmds.pop.dup
513
+ else
514
+ opts = {}
515
+ end
516
+
517
+ out_r, out_w = IO.pipe
518
+ opts[:out] = out_w
519
+
520
+ pipeline_run(cmds, opts, [out_w], [out_r], &block)
521
+ end
522
+ module_function :pipeline_r
523
+
524
+ # Open3.pipeline_w starts a list of commands as a pipeline with a pipe
525
+ # which connects to stdin of the first command.
526
+ #
527
+ # Open3.pipeline_w(cmd1, cmd2, ... [, opts]) {|first_stdin, wait_threads|
528
+ # ...
529
+ # }
530
+ #
531
+ # first_stdin, wait_threads = Open3.pipeline_w(cmd1, cmd2, ... [, opts])
532
+ # ...
533
+ # first_stdin.close
534
+ #
535
+ # Each cmd is a string or an array.
536
+ # If it is an array, the elements are passed to Process.spawn.
537
+ #
538
+ # cmd:
539
+ # commandline command line string which is passed to a shell
540
+ # [env, commandline, opts] command line string which is passed to a shell
541
+ # [env, cmdname, arg1, ..., opts] command name and one or more arguments (no shell)
542
+ # [env, [cmdname, argv0], arg1, ..., opts] command name and arguments including argv[0] (no shell)
543
+ #
544
+ # Note that env and opts are optional, as for Process.spawn.
545
+ #
546
+ # Example:
547
+ #
548
+ # Open3.pipeline_w("bzip2 -c", :out=>"/tmp/hello.bz2") {|i, ts|
549
+ # i.puts "hello"
550
+ # }
551
+ #
552
+ def pipeline_w(*cmds, &block)
553
+ if Hash === cmds.last
554
+ opts = cmds.pop.dup
555
+ else
556
+ opts = {}
557
+ end
558
+
559
+ in_r, in_w = IO.pipe
560
+ opts[:in] = in_r
561
+ in_w.sync = true
562
+
563
+ pipeline_run(cmds, opts, [in_r], [in_w], &block)
564
+ end
565
+ module_function :pipeline_w
566
+
567
+ # Open3.pipeline_start starts a list of commands as a pipeline.
568
+ # No pipes are created for stdin of the first command and
569
+ # stdout of the last command.
570
+ #
571
+ # Open3.pipeline_start(cmd1, cmd2, ... [, opts]) {|wait_threads|
572
+ # ...
573
+ # }
574
+ #
575
+ # wait_threads = Open3.pipeline_start(cmd1, cmd2, ... [, opts])
576
+ # ...
577
+ #
578
+ # Each cmd is a string or an array.
579
+ # If it is an array, the elements are passed to Process.spawn.
580
+ #
581
+ # cmd:
582
+ # commandline command line string which is passed to a shell
583
+ # [env, commandline, opts] command line string which is passed to a shell
584
+ # [env, cmdname, arg1, ..., opts] command name and one or more arguments (no shell)
585
+ # [env, [cmdname, argv0], arg1, ..., opts] command name and arguments including argv[0] (no shell)
586
+ #
587
+ # Note that env and opts are optional, as for Process.spawn.
588
+ #
589
+ # Example:
590
+ #
591
+ # # Run xeyes in 10 seconds.
592
+ # Open3.pipeline_start("xeyes") {|ts|
593
+ # sleep 10
594
+ # t = ts[0]
595
+ # Process.kill("TERM", t.pid)
596
+ # p t.value #=> #<Process::Status: pid 911 SIGTERM (signal 15)>
597
+ # }
598
+ #
599
+ # # Convert pdf to ps and send it to a printer.
600
+ # # Collect error message of pdftops and lpr.
601
+ # pdf_file = "paper.pdf"
602
+ # printer = "printer-name"
603
+ # err_r, err_w = IO.pipe
604
+ # Open3.pipeline_start(["pdftops", pdf_file, "-"],
605
+ # ["lpr", "-P#{printer}"],
606
+ # :err=>err_w) {|ts|
607
+ # err_w.close
608
+ # p err_r.read # error messages of pdftops and lpr.
609
+ # }
610
+ #
611
+ def pipeline_start(*cmds, &block)
612
+ if Hash === cmds.last
613
+ opts = cmds.pop.dup
614
+ else
615
+ opts = {}
616
+ end
617
+
618
+ if block
619
+ pipeline_run(cmds, opts, [], [], &block)
620
+ else
621
+ ts, = pipeline_run(cmds, opts, [], [])
622
+ ts
623
+ end
624
+ end
625
+ module_function :pipeline_start
626
+
627
+ # Open3.pipeline starts a list of commands as a pipeline.
628
+ # It waits for the completion of the commands.
629
+ # No pipes are created for stdin of the first command and
630
+ # stdout of the last command.
631
+ #
632
+ # status_list = Open3.pipeline(cmd1, cmd2, ... [, opts])
633
+ #
634
+ # Each cmd is a string or an array.
635
+ # If it is an array, the elements are passed to Process.spawn.
636
+ #
637
+ # cmd:
638
+ # commandline command line string which is passed to a shell
639
+ # [env, commandline, opts] command line string which is passed to a shell
640
+ # [env, cmdname, arg1, ..., opts] command name and one or more arguments (no shell)
641
+ # [env, [cmdname, argv0], arg1, ..., opts] command name and arguments including argv[0] (no shell)
642
+ #
643
+ # Note that env and opts are optional, as Process.spawn.
644
+ #
645
+ # Example:
646
+ #
647
+ # fname = "/usr/share/man/man1/ruby.1.gz"
648
+ # p Open3.pipeline(["zcat", fname], "nroff -man", "less")
649
+ # #=> [#<Process::Status: pid 11817 exit 0>,
650
+ # # #<Process::Status: pid 11820 exit 0>,
651
+ # # #<Process::Status: pid 11828 exit 0>]
652
+ #
653
+ # fname = "/usr/share/man/man1/ls.1.gz"
654
+ # Open3.pipeline(["zcat", fname], "nroff -man", "colcrt")
655
+ #
656
+ # # convert PDF to PS and send to a printer by lpr
657
+ # pdf_file = "paper.pdf"
658
+ # printer = "printer-name"
659
+ # Open3.pipeline(["pdftops", pdf_file, "-"],
660
+ # ["lpr", "-P#{printer}"])
661
+ #
662
+ # # count lines
663
+ # Open3.pipeline("sort", "uniq -c", :in=>"names.txt", :out=>"count")
664
+ #
665
+ # # cyclic pipeline
666
+ # r,w = IO.pipe
667
+ # w.print "ibase=14\n10\n"
668
+ # Open3.pipeline("bc", "tee /dev/tty", :in=>r, :out=>w)
669
+ # #=> 14
670
+ # # 18
671
+ # # 22
672
+ # # 30
673
+ # # 42
674
+ # # 58
675
+ # # 78
676
+ # # 106
677
+ # # 202
678
+ #
679
+ def pipeline(*cmds)
680
+ if Hash === cmds.last
681
+ opts = cmds.pop.dup
682
+ else
683
+ opts = {}
684
+ end
685
+
686
+ pipeline_run(cmds, opts, [], []) {|ts|
687
+ ts.map(&:value)
688
+ }
689
+ end
690
+ module_function :pipeline
691
+
692
+ def pipeline_run(cmds, pipeline_opts, child_io, parent_io) # :nodoc:
693
+ if cmds.empty?
694
+ raise ArgumentError, "no commands"
695
+ end
696
+
697
+ opts_base = pipeline_opts.dup
698
+ opts_base.delete :in
699
+ opts_base.delete :out
700
+
701
+ wait_thrs = []
702
+ r = nil
703
+ cmds.each_with_index {|cmd, i|
704
+ cmd_opts = opts_base.dup
705
+ if String === cmd
706
+ cmd = [cmd]
707
+ else
708
+ cmd_opts.update cmd.pop if Hash === cmd.last
709
+ end
710
+ if i == 0
711
+ if !cmd_opts.include?(:in)
712
+ if pipeline_opts.include?(:in)
713
+ cmd_opts[:in] = pipeline_opts[:in]
714
+ end
715
+ end
716
+ else
717
+ cmd_opts[:in] = r
718
+ end
719
+ if i != cmds.length - 1
720
+ r2, w2 = IO.pipe
721
+ cmd_opts[:out] = w2
722
+ else
723
+ if !cmd_opts.include?(:out)
724
+ if pipeline_opts.include?(:out)
725
+ cmd_opts[:out] = pipeline_opts[:out]
726
+ end
727
+ end
728
+ end
729
+ pid = spawn(*cmd, cmd_opts)
730
+ wait_thrs << Process.detach(pid)
731
+ r&.close
732
+ w2&.close
733
+ r = r2
734
+ }
735
+ result = parent_io + [wait_thrs]
736
+ child_io.each(&:close)
737
+ if defined? yield
738
+ begin
739
+ return yield(*result)
740
+ ensure
741
+ parent_io.each(&:close)
742
+ wait_thrs.each(&:join)
743
+ end
744
+ end
745
+ result
746
+ end
747
+ module_function :pipeline_run
748
+ class << self
749
+ private :pipeline_run
750
+ end
751
+
752
+ end
@@ -0,0 +1,3 @@
1
+ module Open3
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,27 @@
1
+ lib = File.expand_path("lib", __dir__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require "open3/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "open3"
7
+ spec.version = Open3::VERSION
8
+ spec.authors = ["Hiroshi SHIBATA"]
9
+ spec.email = ["hsbt@ruby-lang.org"]
10
+
11
+ spec.summary = %q{Popen, but with stderr, too}
12
+ spec.description = spec.summary
13
+ spec.homepage = "https://github.com/ruby/open3"
14
+ spec.license = "BSD-2-Clause"
15
+
16
+ spec.metadata["homepage_uri"] = spec.homepage
17
+ spec.metadata["source_code_uri"] = spec.homepage
18
+
19
+ # Specify which files should be added to the gem when it is released.
20
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
21
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
22
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
23
+ end
24
+ spec.bindir = "exe"
25
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
26
+ spec.require_paths = ["lib"]
27
+ end
metadata ADDED
@@ -0,0 +1,56 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: open3
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Hiroshi SHIBATA
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2019-11-06 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Popen, but with stderr, too
14
+ email:
15
+ - hsbt@ruby-lang.org
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - ".gitignore"
21
+ - ".travis.yml"
22
+ - Gemfile
23
+ - LICENSE.txt
24
+ - README.md
25
+ - Rakefile
26
+ - bin/console
27
+ - bin/setup
28
+ - lib/open3.rb
29
+ - lib/open3/version.rb
30
+ - open3.gemspec
31
+ homepage: https://github.com/ruby/open3
32
+ licenses:
33
+ - BSD-2-Clause
34
+ metadata:
35
+ homepage_uri: https://github.com/ruby/open3
36
+ source_code_uri: https://github.com/ruby/open3
37
+ post_install_message:
38
+ rdoc_options: []
39
+ require_paths:
40
+ - lib
41
+ required_ruby_version: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ required_rubygems_version: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: '0'
51
+ requirements: []
52
+ rubygems_version: 3.0.3
53
+ signing_key:
54
+ specification_version: 4
55
+ summary: Popen, but with stderr, too
56
+ test_files: []