open3 0.1.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.
@@ -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: []