open3_backport 0.0.1

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/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm 1.8.7@open3_backport --create
data/BSDL ADDED
@@ -0,0 +1,23 @@
1
+ Copyright (C) 1993-2010 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.
23
+
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in open3_backport.gemspec
4
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,13 @@
1
+ # See https://github.com/guard/guard#readme
2
+
3
+ guard 'bundler' do
4
+ watch('Gemfile')
5
+ watch(%r{.+\.gemspec})
6
+ end
7
+
8
+ guard 'rspec', :cli => '-c --format documentation -r ./spec/spec_helper.rb' do
9
+ watch(%r{^spec/.+_spec\.rb})
10
+ watch(%r{^lib/(.+)\.rb}) { |m| "spec/lib/#{m[1]}_spec.rb" }
11
+ watch('spec/spec_helper.rb') { "spec/" }
12
+ watch('lib/open3_backport.rb') { "spec/" }
13
+ end
data/LICENSE ADDED
@@ -0,0 +1,65 @@
1
+ The important bits of open3_backport are pulled directly from Ruby source,
2
+ available at http://www.ruby-lang.org/. Copyrights of all other code in
3
+ the gem are assigned to the copyright owner of Ruby (Yukihiro Matsumoto).
4
+
5
+ This gem is available under the same license(s) as Ruby itself, reproduced
6
+ here for your convenience:
7
+
8
+ ======
9
+
10
+ Ruby is copyrighted free software by Yukihiro Matsumoto <matz@netlab.jp>.
11
+ You can redistribute it and/or modify it under either the terms of the
12
+ 2-clause BSDL (see the file BSDL), or the conditions below:
13
+
14
+ 1. You may make and give away verbatim copies of the source form of the
15
+ software without restriction, provided that you duplicate all of the
16
+ original copyright notices and associated disclaimers.
17
+
18
+ 2. You may modify your copy of the software in any way, provided that
19
+ you do at least ONE of the following:
20
+
21
+ a) place your modifications in the Public Domain or otherwise
22
+ make them Freely Available, such as by posting said
23
+ modifications to Usenet or an equivalent medium, or by allowing
24
+ the author to include your modifications in the software.
25
+
26
+ b) use the modified software only within your corporation or
27
+ organization.
28
+
29
+ c) give non-standard binaries non-standard names, with
30
+ instructions on where to get the original software distribution.
31
+
32
+ d) make other distribution arrangements with the author.
33
+
34
+ 3. You may distribute the software in object code or binary form,
35
+ provided that you do at least ONE of the following:
36
+
37
+ a) distribute the binaries and library files of the software,
38
+ together with instructions (in the manual page or equivalent)
39
+ on where to get the original distribution.
40
+
41
+ b) accompany the distribution with the machine-readable source of
42
+ the software.
43
+
44
+ c) give non-standard binaries non-standard names, with
45
+ instructions on where to get the original software distribution.
46
+
47
+ d) make other distribution arrangements with the author.
48
+
49
+ 4. You may modify and include the part of the software into any other
50
+ software (possibly commercial). But some files in the distribution
51
+ are not written by the author, so that they are not under these terms.
52
+
53
+ For the list of those files and their copying conditions, see the
54
+ file LEGAL.
55
+
56
+ 5. The scripts and library files supplied as input to or produced as
57
+ output from the software do not automatically fall under the
58
+ copyright of the software, but belong to whomever generated them,
59
+ and may be sold commercially, and may be aggregated with this
60
+ software.
61
+
62
+ 6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
63
+ IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
64
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
65
+ PURPOSE.
data/README.md ADDED
@@ -0,0 +1,228 @@
1
+ # Open3Backport
2
+
3
+ Backport of Ruby 1.9's Open3 methods, for use in Ruby 1.8.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'open3_backport'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install open3_backport
18
+
19
+ ## Usage
20
+
21
+ For the most part, you can just use Open3 methods the same way you would in
22
+ Ruby 1.9. However, there is currently no support for setting environment nor
23
+ passing any of the special options that Process.spawn supports in Ruby 1.9.
24
+
25
+ Here are some examples that should work fine...
26
+
27
+ # Block form:
28
+ Open3.popen3("echo", "a") do |stdin, stdout, stderr, wait_thr|
29
+ pid = wait_thr.pid # pid of the started process.
30
+ exit_status = wait_thr.value # Process::Status object returned.
31
+ end
32
+
33
+ # Non-block form:
34
+ stdin, stdout, stderr, wait_thr = Open3.popen3("echo", "a")
35
+ pid = wait_thr[:pid] # pid of the started process.
36
+ stdin.close # stdin, stdout and stderr should be closed explicitly in this form.
37
+ stdout.close
38
+ stderr.close
39
+ exit_status = wait_thr.value # Process::Status object returned.
40
+
41
+ Open3.popen3("echo a") {|i, o, e, t| ... }
42
+
43
+ Open3.popen3("echo", "a") {|i, o, e, t| ... }
44
+
45
+ Open3.popen2("wc -c") do |i, o, t|
46
+ i.print "answer to life the universe and everything"
47
+ i.close
48
+ p o.gets #=> "42\n"
49
+ end
50
+
51
+ Open3.popen2("bc -q") do |i, o, t|
52
+ i.puts "obase=13"
53
+ i.puts "6 * 9"
54
+ p o.gets #=> "42\n"
55
+ end
56
+
57
+ Open3.popen2("dc") do |i, o, t|
58
+ i.print "42P"
59
+ i.close
60
+ p o.read #=> "*"
61
+ end
62
+
63
+ # dot is a command of graphviz.
64
+ graph = <<'End'
65
+ digraph g {
66
+ a -> b
67
+ }
68
+ End
69
+ layouted_graph, dot_log = Open3.capture3("dot -v", :stdin_data=>graph)
70
+
71
+ o, e, s = Open3.capture3("echo a; sort >&2", :stdin_data=>"foo\nbar\nbaz\n")
72
+ p o #=> "a\n"
73
+ p e #=> "bar\nbaz\nfoo\n"
74
+ p s #=> #<Process::Status: pid 32682 exit 0>
75
+
76
+ image = File.read("/usr/share/openclipart/png/animals/mammals/sheep-md-v0.1.png", :binmode=>true)
77
+ thumnail, err, s = Open3.capture3("convert -thumbnail 80 png:- png:-", :stdin_data=>image, :binmode=>true)
78
+ if s.success?
79
+ STDOUT.binmode
80
+ print thumnail
81
+ end
82
+
83
+ # factor is a command for integer factorization.
84
+ o, s = Open3.capture2("factor", :stdin_data=>"42")
85
+ p o #=> "42: 2 3 7\n"
86
+
87
+ # generate x**2 graph in png using gnuplot.
88
+ gnuplot_commands = <<"End"
89
+ set terminal png
90
+ plot x**2, "-" with lines
91
+ 1 14
92
+ 2 1
93
+ 3 8
94
+ 4 5
95
+ e
96
+ End
97
+ image, s = Open3.capture2("gnuplot", :stdin_data=>gnuplot_commands, :binmode=>true)
98
+
99
+ # capture make log
100
+ make_log, s = Open3.capture2e("make")
101
+
102
+
103
+ The following examples do not work yet. Pull requests are welcome!
104
+
105
+ source = "foo.c"
106
+ Open3.popen2e("gcc", "-Wall", source) do |i, oe, t|
107
+ oe.each do |line|
108
+ if /warning/ =~ line
109
+ # ...
110
+ end
111
+ end
112
+ end
113
+
114
+ Open3.pipeline_rw(["tr", "-dc", "A-Za-z"], ["wc", "-c"]) do |i, o, ts|
115
+ i.puts "All persons more than a mile high to leave the court."
116
+ i.close
117
+ p o.gets #=> "42\n"
118
+ end
119
+
120
+ Open3.pipeline_rw("sort", "cat -n") do |stdin, stdout, wait_thrs|
121
+ stdin.puts "foo"
122
+ stdin.puts "bar"
123
+ stdin.puts "baz"
124
+ stdin.close # send EOF to sort.
125
+ p stdout.read #=> " 1\tbar\n 2\tbaz\n 3\tfoo\n"
126
+ end
127
+
128
+ Open3.pipeline_r("zcat /var/log/apache2/access.log.*.gz",
129
+ [{"LANG"=>"C"}, "grep", "GET /favicon.ico"],
130
+ "logresolve") {|o, ts|
131
+ o.each_line {|line|
132
+ # ...
133
+ }
134
+ }
135
+
136
+ Open3.pipeline_r("yes", "head -10") {|o, ts|
137
+ p o.read #=> "y\ny\ny\ny\ny\ny\ny\ny\ny\ny\n"
138
+ p ts[0].value #=> #<Process::Status: pid 24910 SIGPIPE (signal 13)>
139
+ p ts[1].value #=> #<Process::Status: pid 24913 exit 0>
140
+ }
141
+
142
+ Open3.pipeline_w("bzip2 -c", :out=>"/tmp/hello.bz2") {|i, ts|
143
+ i.puts "hello"
144
+ }
145
+
146
+ # run xeyes in 10 seconds.
147
+ Open3.pipeline_start("xeyes") {|ts|
148
+ sleep 10
149
+ t = ts[0]
150
+ Process.kill("TERM", t.pid)
151
+ p t.value #=> #<Process::Status: pid 911 SIGTERM (signal 15)>
152
+ }
153
+
154
+ # convert pdf to ps and send it to a printer.
155
+ # collect error message of pdftops and lpr.
156
+ pdf_file = "paper.pdf"
157
+ printer = "printer-name"
158
+ err_r, err_w = IO.pipe
159
+ Open3.pipeline_start(["pdftops", pdf_file, "-"],
160
+ ["lpr", "-P#{printer}"],
161
+ :err=>err_w) {|ts|
162
+ err_w.close
163
+ p err_r.read # error messages of pdftops and lpr.
164
+ }
165
+
166
+ fname = "/usr/share/man/man1/ruby.1.gz"
167
+ p Open3.pipeline(["zcat", fname], "nroff -man", "less")
168
+ #=> [#<Process::Status: pid 11817 exit 0>,
169
+ # #<Process::Status: pid 11820 exit 0>,
170
+ # #<Process::Status: pid 11828 exit 0>]
171
+
172
+ fname = "/usr/share/man/man1/ls.1.gz"
173
+ Open3.pipeline(["zcat", fname], "nroff -man", "colcrt")
174
+
175
+ # convert PDF to PS and send to a printer by lpr
176
+ pdf_file = "paper.pdf"
177
+ printer = "printer-name"
178
+ Open3.pipeline(["pdftops", pdf_file, "-"],
179
+ ["lpr", "-P#{printer}"])
180
+
181
+ # count lines
182
+ Open3.pipeline("sort", "uniq -c", :in=>"names.txt", :out=>"count")
183
+
184
+ # cyclic pipeline
185
+ r,w = IO.pipe
186
+ w.print "ibase=14\n10\n"
187
+ Open3.pipeline("bc", "tee /dev/tty", :in=>r, :out=>w)
188
+ #=> 14
189
+ # 18
190
+ # 22
191
+ # 30
192
+ # 42
193
+ # 58
194
+ # 78
195
+ # 106
196
+ # 202
197
+
198
+ ## Version History
199
+
200
+ 0.0.1 - Initial release. No support for setting environment nor
201
+ passing any of the special options that Process.spawn supports in Ruby 1.9.
202
+ These methods are not yet implemented: popen2e, pipeline, pipeline_start,
203
+ pipeline_r, pipeline_w, pipeline_rw.
204
+
205
+ ## Credits
206
+
207
+ This gem was written by Chris Johnson for Crossroads Systems, Inc. The code
208
+ and documentation was copied from the Ruby 1.9.3 stdlib, and then key method
209
+ implementations were re-written and tested to work in Ruby 1.8.7, while aiming
210
+ to maintain maximum compatibility with the Ruby 1.9.3 method interfaces. The
211
+ replacement implementation makes heavy use of the open4 gem.
212
+
213
+ ## License
214
+
215
+ The important bits of open3_backport are pulled directly from Ruby source,
216
+ available at http://www.ruby-lang.org/. Copyrights of all other code in
217
+ the gem are assigned to the copyright owner of Ruby (Yukihiro Matsumoto).
218
+
219
+ This gem is available under the same license(s) as Ruby itself (See LICENSE
220
+ file).
221
+
222
+ ## Contributing
223
+
224
+ 1. Fork it
225
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
226
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
227
+ 4. Push to the branch (`git push origin my-new-feature`)
228
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+
4
+ Dir['tasks/*.rake'].each { |task| load task }
@@ -0,0 +1,6 @@
1
+ if RUBY_VERSION < "1.9"
2
+ require 'rubygems' rescue nil
3
+ require 'open4'
4
+ require "open3_backport/version"
5
+ require "open3_backport/open3"
6
+ end
@@ -0,0 +1,676 @@
1
+ #
2
+ # = open3.rb: Popen, but with stderr, too
3
+ #
4
+ # Author:: Yukihiro Matsumoto, backport by Chris Johnson
5
+ # Documentation:: Konrad Meyer, backport by Chris Johnson
6
+ #
7
+ # Open3 gives you access to stdin, stdout, and stderr when running other
8
+ # programs.
9
+ #
10
+
11
+ #
12
+ # Open3 grants you access to stdin, stdout, stderr and a thread to wait the
13
+ # child process when running another program.
14
+ #
15
+ # - Open3.popen3 : pipes for stdin, stdout, stderr
16
+ # - Open3.popen2 : pipes for stdin, stdout
17
+ # - Open3.popen2e : pipes for stdin, merged stdout and stderr
18
+ # - Open3.capture3 : give a string for stdin. get strings for stdout, stderr
19
+ # - Open3.capture2 : give a string for stdin. get a string for stdout
20
+ # - Open3.capture2e : give a string for stdin. get a string for merged stdout and stderr
21
+ # - Open3.pipeline_rw : pipes for first stdin and last stdout of a pipeline
22
+ # - Open3.pipeline_r : pipe for last stdout of a pipeline
23
+ # - Open3.pipeline_w : pipe for first stdin of a pipeline
24
+ # - Open3.pipeline_start : a pipeline
25
+ # - Open3.pipeline : run a pipline and wait
26
+ #
27
+
28
+ module Open3
29
+
30
+ def detach(pid) # :nodoc:
31
+ thread = Process.detach(pid)
32
+ thread[:pid] = pid
33
+ def thread.pid
34
+ self[:pid]
35
+ end
36
+ thread
37
+ end
38
+ module_function :detach
39
+ class << self
40
+ private :detach
41
+ end
42
+
43
+ #
44
+ # Open stdin, stdout, and stderr streams and start external executable.
45
+ # In addition, a thread for waiting the started process is noticed.
46
+ # The thread has a pid method and thread variable :pid which is the pid of
47
+ # the started process.
48
+ #
49
+ # Block form:
50
+ #
51
+ # Open3.popen3(cmd...) {|stdin, stdout, stderr, wait_thr|
52
+ # pid = wait_thr.pid # pid of the started process.
53
+ # ...
54
+ # exit_status = wait_thr.value # Process::Status object returned.
55
+ # }
56
+ #
57
+ # Non-block form:
58
+ #
59
+ # stdin, stdout, stderr, wait_thr = Open3.popen3(cmd...)
60
+ # pid = wait_thr[:pid] # pid of the started process.
61
+ # ...
62
+ # stdin.close # stdin, stdout and stderr should be closed explicitly in this form.
63
+ # stdout.close
64
+ # stderr.close
65
+ # exit_status = wait_thr.value # Process::Status object returned.
66
+ #
67
+ # So a commandline string and list of argument strings can be accepted as follows.
68
+ #
69
+ # Open3.popen3("echo", "a") {|i, o, e, t| ... }
70
+ #
71
+ # wait_thr.value waits the termination of the process.
72
+ # The block form also waits the process when it returns.
73
+ #
74
+ # Closing stdin, stdout and stderr does not wait the process.
75
+ #
76
+ def popen3(*cmd)
77
+ if block_given?
78
+ begin
79
+ pid, stdin, stdout, stderr = Open4::popen4(*cmd)
80
+ wait_thr = detach(pid)
81
+ stdin.sync = true
82
+ return yield(stdin, stdout, stderr, wait_thr)
83
+ ensure
84
+ stdin.close unless stdin.nil? || stdin.closed?
85
+ stdout.close unless stdout.nil? || stdout.closed?
86
+ stderr.close unless stderr.nil? || stderr.closed?
87
+ wait_thr.value unless wait_thr.nil?
88
+ end
89
+ else
90
+ pid, stdin, stdout, stderr = Open4::popen4(*cmd)
91
+ stdin.sync = true
92
+ return [stdin, stdout, stderr, detach(pid)]
93
+ end
94
+ end
95
+ module_function :popen3
96
+
97
+ # Open3.popen2 is similer to Open3.popen3 except it doesn't make a pipe for
98
+ # the standard error stream.
99
+ #
100
+ # Block form:
101
+ #
102
+ # Open3.popen2(cmd...) {|stdin, stdout, wait_thr|
103
+ # pid = wait_thr.pid # pid of the started process.
104
+ # ...
105
+ # exit_status = wait_thr.value # Process::Status object returned.
106
+ # }
107
+ #
108
+ # Non-block form:
109
+ #
110
+ # stdin, stdout, wait_thr = Open3.popen2(cmd...)
111
+ # ...
112
+ # stdin.close # stdin and stdout should be closed explicitly in this form.
113
+ # stdout.close
114
+ #
115
+ # Example:
116
+ #
117
+ # Open3.popen2("wc -c") {|i,o,t|
118
+ # i.print "answer to life the universe and everything"
119
+ # i.close
120
+ # p o.gets #=> "42\n"
121
+ # }
122
+ #
123
+ # Open3.popen2("bc -q") {|i,o,t|
124
+ # i.puts "obase=13"
125
+ # i.puts "6 * 9"
126
+ # p o.gets #=> "42\n"
127
+ # }
128
+ #
129
+ # Open3.popen2("dc") {|i,o,t|
130
+ # i.print "42P"
131
+ # i.close
132
+ # p o.read #=> "*"
133
+ # }
134
+ #
135
+ def popen2(*cmd)
136
+ if block_given?
137
+ popen3(*cmd) do |i, o, e, t|
138
+ e.close
139
+ yield(i, o, t)
140
+ end
141
+ else
142
+ i, o, e, t = popen3(*cmd)
143
+ e.close
144
+ return [i, o, t]
145
+ end
146
+ end
147
+ module_function :popen2
148
+
149
+ # Open3.popen2e is similer to Open3.popen3 except it merges
150
+ # the standard output stream and the standard error stream.
151
+ #
152
+ # Block form:
153
+ #
154
+ # Open3.popen2e(cmd...) {|stdin, stdout_and_stderr, wait_thr|
155
+ # pid = wait_thr.pid # pid of the started process.
156
+ # ...
157
+ # exit_status = wait_thr.value # Process::Status object returned.
158
+ # }
159
+ #
160
+ # Non-block form:
161
+ #
162
+ # stdin, stdout_and_stderr, wait_thr = Open3.popen2e(cmd...)
163
+ # ...
164
+ # stdin.close # stdin and stdout_and_stderr should be closed explicitly in this form.
165
+ # stdout_and_stderr.close
166
+ #
167
+ # Example:
168
+ # # check gcc warnings
169
+ # source = "foo.c"
170
+ # Open3.popen2e("gcc", "-Wall", source) {|i,oe,t|
171
+ # oe.each {|line|
172
+ # if /warning/ =~ line
173
+ # ...
174
+ # end
175
+ # }
176
+ # }
177
+ #
178
+ def popen2e(*cmd)
179
+ if block_given?
180
+ popen3(*cmd) do |i, o, e, t|
181
+ yield(i, merged_read_stream(o, e), t)
182
+ end
183
+ else
184
+ i, o, e, t = popen3(*cmd)
185
+ return [i, merged_read_stream(o, e), t]
186
+ end
187
+ end
188
+ module_function :popen2e
189
+
190
+ def merged_read_stream(*streams) # :nodoc:
191
+ raise NotImplementedError
192
+ end
193
+
194
+ # Open3.capture3 captures the standard output and the standard error of a command.
195
+ #
196
+ # stdout_str, stderr_str, status = Open3.capture3(cmd... [, opts])
197
+ #
198
+ # The cmd arguments are passed to Open3.popen3.
199
+ #
200
+ # If opts[:stdin_data] is specified, it is sent to the command's standard input.
201
+ #
202
+ # If opts[:binmode] is true, internal pipes are set to binary mode.
203
+ #
204
+ # Example:
205
+ #
206
+ # # dot is a command of graphviz.
207
+ # graph = <<'End'
208
+ # digraph g {
209
+ # a -> b
210
+ # }
211
+ # End
212
+ # layouted_graph, dot_log = Open3.capture3("dot -v", :stdin_data=>graph)
213
+ #
214
+ # o, e, s = Open3.capture3("echo a; sort >&2", :stdin_data=>"foo\nbar\nbaz\n")
215
+ # p o #=> "a\n"
216
+ # p e #=> "bar\nbaz\nfoo\n"
217
+ # p s #=> #<Process::Status: pid 32682 exit 0>
218
+ #
219
+ # # generate a thumnail image using the convert command of ImageMagick.
220
+ # # However, if the image stored really in a file,
221
+ # # system("convert", "-thumbnail", "80", "png:#{filename}", "png:-") is better
222
+ # # because memory consumption.
223
+ # # But if the image is stored in a DB or generated by gnuplot Open3.capture2 example,
224
+ # # Open3.capture3 is considerable.
225
+ # #
226
+ # image = File.read("/usr/share/openclipart/png/animals/mammals/sheep-md-v0.1.png", :binmode=>true)
227
+ # thumnail, err, s = Open3.capture3("convert -thumbnail 80 png:- png:-", :stdin_data=>image, :binmode=>true)
228
+ # if s.success?
229
+ # STDOUT.binmode; print thumnail
230
+ # end
231
+ #
232
+ def capture3(*cmd)
233
+ if Hash === cmd.last
234
+ opts = cmd.pop.dup
235
+ else
236
+ opts = {}
237
+ end
238
+
239
+ binmode = opts[:binmode]
240
+ i_data = (opts[:stdin_data] || '').to_s
241
+ o_data = ''
242
+ e_data = ''
243
+
244
+ popen3(*cmd) do |i, o, e, t|
245
+ if binmode
246
+ i.binmode
247
+ o.binmode
248
+ e.binmode
249
+ end
250
+
251
+ i_complete = i_data.empty?
252
+ o_complete = false
253
+ e_complete = false
254
+
255
+ until i_complete && o_complete && e_complete
256
+ i_blocked = false
257
+ o_blocked = false
258
+ e_blocked = false
259
+
260
+ unless i_complete
261
+ begin
262
+ bytes_written = i.write_nonblock(i_data)
263
+ if bytes_written == i_data.length
264
+ i.close
265
+ i_complete = true
266
+ else
267
+ i_data = i_data[bytes_written .. -1]
268
+ end
269
+ rescue Errno::EWOULDBLOCK, Errno::EAGAIN, Errno::EINTR
270
+ i_blocked = true
271
+ end
272
+ end
273
+
274
+ unless o_complete
275
+ begin
276
+ o_data << o.read_nonblock(CAPTURE_BUFFER_SIZE)
277
+ rescue Errno::EWOULDBLOCK, Errno::EAGAIN
278
+ o_blocked = true
279
+ rescue EOFError
280
+ raise unless i_complete
281
+ o.close
282
+ o_complete = true
283
+ end
284
+ end
285
+
286
+ unless e_complete
287
+ begin
288
+ e_data << e.read_nonblock(CAPTURE_BUFFER_SIZE)
289
+ rescue Errno::EWOULDBLOCK, Errno::EAGAIN
290
+ e_blocked = true
291
+ rescue EOFError
292
+ raise unless i_complete
293
+ e.close
294
+ e_complete = true
295
+ end
296
+ end
297
+
298
+ if i_blocked && o_blocked && e_blocked
299
+ IO.select([o, e], [i], [o,e,i])
300
+ end
301
+ end
302
+ return [o_data, e_data, t.value]
303
+ end
304
+ end
305
+ module_function :capture3
306
+
307
+ # Open3.capture2 captures the standard output of a command.
308
+ #
309
+ # stdout_str, status = Open3.capture2(cmd... [, opts])
310
+ #
311
+ # The cmd arguments are passed to Open3.popen3.
312
+ #
313
+ # If opts[:stdin_data] is specified, it is sent to the command's standard input.
314
+ #
315
+ # If opts[:binmode] is true, internal pipes are set to binary mode.
316
+ #
317
+ # Example:
318
+ #
319
+ # # factor is a command for integer factorization.
320
+ # o, s = Open3.capture2("factor", :stdin_data=>"42")
321
+ # p o #=> "42: 2 3 7\n"
322
+ #
323
+ # # generate x**2 graph in png using gnuplot.
324
+ # gnuplot_commands = <<"End"
325
+ # set terminal png
326
+ # plot x**2, "-" with lines
327
+ # 1 14
328
+ # 2 1
329
+ # 3 8
330
+ # 4 5
331
+ # e
332
+ # End
333
+ # image, s = Open3.capture2("gnuplot", :stdin_data=>gnuplot_commands, :binmode=>true)
334
+ #
335
+ def capture2(*cmd)
336
+ if Hash === cmd.last
337
+ opts = cmd.pop.dup
338
+ else
339
+ opts = {}
340
+ end
341
+
342
+ binmode = opts[:binmode]
343
+ i_data = (opts[:stdin_data] || '').to_s
344
+ o_data = ''
345
+
346
+ popen3(*cmd) do |i, o, e, t|
347
+ e.close
348
+ if binmode
349
+ i.binmode
350
+ o.binmode
351
+ e.binmode
352
+ end
353
+
354
+ i_complete = i_data.empty?
355
+ o_complete = false
356
+
357
+ until i_complete && o_complete
358
+ i_blocked = false
359
+ o_blocked = false
360
+
361
+ unless i_complete
362
+ begin
363
+ bytes_written = i.write_nonblock(i_data)
364
+ if bytes_written == i_data.length
365
+ i.close
366
+ i_complete = true
367
+ else
368
+ i_data = i_data[bytes_written .. -1]
369
+ end
370
+ rescue Errno::EWOULDBLOCK, Errno::EAGAIN, Errno::EINTR
371
+ i_blocked = true
372
+ end
373
+ end
374
+
375
+ unless o_complete
376
+ begin
377
+ o_data << o.read_nonblock(CAPTURE_BUFFER_SIZE)
378
+ rescue Errno::EWOULDBLOCK, Errno::EAGAIN
379
+ o_blocked = true
380
+ rescue EOFError
381
+ raise unless i_complete
382
+ o.close
383
+ o_complete = true
384
+ end
385
+ end
386
+
387
+ if i_blocked && o_blocked && e_blocked
388
+ IO.select([o], [i], [o,i])
389
+ end
390
+ end
391
+ return [o_data, t.value]
392
+ end
393
+ end
394
+ module_function :capture2
395
+
396
+ # Open3.capture2e captures the standard output and the standard error of a command.
397
+ #
398
+ # stdout_and_stderr_str, status = Open3.capture2e(cmd... [, opts])
399
+ #
400
+ # The cmd arguments are passed to Open3.popen3.
401
+ #
402
+ # If opts[:stdin_data] is specified, it is sent to the command's standard input.
403
+ #
404
+ # If opts[:binmode] is true, internal pipes are set to binary mode.
405
+ #
406
+ # Example:
407
+ #
408
+ # # capture make log
409
+ # make_log, s = Open3.capture2e("make")
410
+ #
411
+ def capture2e(*cmd)
412
+ if Hash === cmd.last
413
+ opts = cmd.pop.dup
414
+ else
415
+ opts = {}
416
+ end
417
+
418
+ binmode = opts[:binmode]
419
+ i_data = (opts[:stdin_data] || '').to_s
420
+ oe_data = ''
421
+
422
+ popen3(*cmd) do |i, o, e, t|
423
+ if binmode
424
+ i.binmode
425
+ o.binmode
426
+ e.binmode
427
+ end
428
+
429
+ i_complete = i_data.empty?
430
+ o_complete = false
431
+ e_complete = false
432
+
433
+ until i_complete && o_complete && e_complete
434
+ i_blocked = false
435
+ o_blocked = false
436
+ e_blocked = false
437
+
438
+ unless i_complete
439
+ begin
440
+ bytes_written = i.write_nonblock(i_data)
441
+ if bytes_written == i_data.length
442
+ i.close
443
+ i_complete = true
444
+ else
445
+ i_data = i_data[bytes_written .. -1]
446
+ end
447
+ rescue Errno::EWOULDBLOCK, Errno::EAGAIN, Errno::EINTR
448
+ i_blocked = true
449
+ end
450
+ end
451
+
452
+ unless o_complete
453
+ begin
454
+ oe_data << o.read_nonblock(CAPTURE_BUFFER_SIZE)
455
+ rescue Errno::EWOULDBLOCK, Errno::EAGAIN
456
+ o_blocked = true
457
+ rescue EOFError
458
+ raise unless i_complete
459
+ o.close
460
+ o_complete = true
461
+ end
462
+ end
463
+
464
+ unless e_complete
465
+ begin
466
+ oe_data << e.read_nonblock(CAPTURE_BUFFER_SIZE)
467
+ rescue Errno::EWOULDBLOCK, Errno::EAGAIN
468
+ e_blocked = true
469
+ rescue EOFError
470
+ raise unless i_complete
471
+ e.close
472
+ e_complete = true
473
+ end
474
+ end
475
+
476
+ if i_blocked && o_blocked && e_blocked
477
+ IO.select([o, e], [i], [o,e,i])
478
+ end
479
+ end
480
+ return [oe_data, t.value]
481
+ end
482
+ end
483
+ module_function :capture2e
484
+
485
+ CAPTURE_BUFFER_SIZE = 65536
486
+
487
+ # Open3.pipeline_rw starts a list of commands as a pipeline with pipes
488
+ # which connects stdin of the first command and stdout of the last command.
489
+ #
490
+ # Open3.pipeline_rw(cmd1, cmd2, ...) {|first_stdin, last_stdout, wait_threads|
491
+ # ...
492
+ # }
493
+ #
494
+ # first_stdin, last_stdout, wait_threads = Open3.pipeline_rw(cmd1, cmd2, ...)
495
+ # ...
496
+ # first_stdin.close
497
+ # last_stdout.close
498
+ #
499
+ # Each cmd is a string or an array.
500
+ # If it is an array, the first element is the command name, and the remaining
501
+ # elements are arguments passed (without parsing) to the command.
502
+ #
503
+ # Example:
504
+ #
505
+ # Open3.pipeline_rw(["tr", "-dc", "A-Za-z"], ["wc", "-c"]) {|i,o,ts|
506
+ # i.puts "All persons more than a mile high to leave the court."
507
+ # i.close
508
+ # p o.gets #=> "42\n"
509
+ # }
510
+ #
511
+ # Open3.pipeline_rw("sort", "cat -n") {|stdin, stdout, wait_thrs|
512
+ # stdin.puts "foo"
513
+ # stdin.puts "bar"
514
+ # stdin.puts "baz"
515
+ # stdin.close # send EOF to sort.
516
+ # p stdout.read #=> " 1\tbar\n 2\tbaz\n 3\tfoo\n"
517
+ # }
518
+ def pipeline_rw(*cmds, &block)
519
+ raise NotImplementedError
520
+ end
521
+ module_function :pipeline_rw
522
+
523
+ # Open3.pipeline_r starts a list of commands as a pipeline with a pipe
524
+ # which connects stdout of the last command.
525
+ #
526
+ # Open3.pipeline_r(cmd1, cmd2, ...) {|last_stdout, wait_threads|
527
+ # ...
528
+ # }
529
+ #
530
+ # last_stdout, wait_threads = Open3.pipeline_r(cmd1, cmd2, ...)
531
+ # ...
532
+ # last_stdout.close
533
+ #
534
+ # Each cmd is a string or an array.
535
+ # If it is an array, the first element is the command name, and the remaining
536
+ # elements are arguments passed (without parsing) to the command.
537
+ #
538
+ # Example:
539
+ #
540
+ # Open3.pipeline_r("zcat /var/log/apache2/access.log.*.gz",
541
+ # [{"LANG"=>"C"}, "grep", "GET /favicon.ico"],
542
+ # "logresolve") {|o, ts|
543
+ # o.each_line {|line|
544
+ # ...
545
+ # }
546
+ # }
547
+ #
548
+ # Open3.pipeline_r("yes", "head -10") {|o, ts|
549
+ # p o.read #=> "y\ny\ny\ny\ny\ny\ny\ny\ny\ny\n"
550
+ # p ts[0].value #=> #<Process::Status: pid 24910 SIGPIPE (signal 13)>
551
+ # p ts[1].value #=> #<Process::Status: pid 24913 exit 0>
552
+ # }
553
+ #
554
+ def pipeline_r(*cmds, &block)
555
+ raise NotImplementedError
556
+ end
557
+ module_function :pipeline_r
558
+
559
+ # Open3.pipeline_w starts a list of commands as a pipeline with a pipe
560
+ # which connects stdin of the first command.
561
+ #
562
+ # Open3.pipeline_w(cmd1, cmd2, ...) {|first_stdin, wait_threads|
563
+ # ...
564
+ # }
565
+ #
566
+ # first_stdin, wait_threads = Open3.pipeline_w(cmd1, cmd2, ...)
567
+ # ...
568
+ # first_stdin.close
569
+ #
570
+ # Each cmd is a string or an array.
571
+ # If it is an array, the first element is the command name, and the remaining
572
+ # elements are arguments passed (without parsing) to the command.
573
+ #
574
+ # Example:
575
+ #
576
+ # Open3.pipeline_w("bzip2 -c", :out=>"/tmp/hello.bz2") {|i, ts|
577
+ # i.puts "hello"
578
+ # }
579
+ #
580
+ def pipeline_w(*cmds, &block)
581
+ raise NotImplementedError
582
+ end
583
+ module_function :pipeline_w
584
+
585
+ # Open3.pipeline_start starts a list of commands as a pipeline.
586
+ # No pipe made for stdin of the first command and
587
+ # stdout of the last command.
588
+ #
589
+ # Open3.pipeline_start(cmd1, cmd2, ...) {|wait_threads|
590
+ # ...
591
+ # }
592
+ #
593
+ # wait_threads = Open3.pipeline_start(cmd1, cmd2, ...)
594
+ # ...
595
+ #
596
+ # Each cmd is a string or an array.
597
+ # If it is an array, the first element is the command name, and the remaining
598
+ # elements are arguments passed (without parsing) to the command.
599
+ #
600
+ # Example:
601
+ #
602
+ # # run xeyes in 10 seconds.
603
+ # Open3.pipeline_start("xeyes") {|ts|
604
+ # sleep 10
605
+ # t = ts[0]
606
+ # Process.kill("TERM", t.pid)
607
+ # p t.value #=> #<Process::Status: pid 911 SIGTERM (signal 15)>
608
+ # }
609
+ #
610
+ # # convert pdf to ps and send it to a printer.
611
+ # # collect error message of pdftops and lpr.
612
+ # pdf_file = "paper.pdf"
613
+ # printer = "printer-name"
614
+ # err_r, err_w = IO.pipe
615
+ # Open3.pipeline_start(["pdftops", pdf_file, "-"],
616
+ # ["lpr", "-P#{printer}"],
617
+ # :err=>err_w) {|ts|
618
+ # err_w.close
619
+ # p err_r.read # error messages of pdftops and lpr.
620
+ # }
621
+ #
622
+ def pipeline_start(*cmds, &block)
623
+ raise NotImplementedError
624
+ end
625
+ module_function :pipeline_start
626
+
627
+ # Open3.pipeline starts a list of commands as a pipeline.
628
+ # It waits the finish of the commands.
629
+ # No pipe made for stdin of the first command and
630
+ # stdout of the last command.
631
+ #
632
+ # status_list = Open3.pipeline(cmd1, cmd2, ...)
633
+ #
634
+ # Each cmd is a string or an array.
635
+ # If it is an array, the first element is the command name, and the remaining
636
+ # elements are arguments passed (without parsing) to the command.
637
+ #
638
+ # Example:
639
+ #
640
+ # fname = "/usr/share/man/man1/ruby.1.gz"
641
+ # p Open3.pipeline(["zcat", fname], "nroff -man", "less")
642
+ # #=> [#<Process::Status: pid 11817 exit 0>,
643
+ # # #<Process::Status: pid 11820 exit 0>,
644
+ # # #<Process::Status: pid 11828 exit 0>]
645
+ #
646
+ # fname = "/usr/share/man/man1/ls.1.gz"
647
+ # Open3.pipeline(["zcat", fname], "nroff -man", "colcrt")
648
+ #
649
+ # # convert PDF to PS and send to a printer by lpr
650
+ # pdf_file = "paper.pdf"
651
+ # printer = "printer-name"
652
+ # Open3.pipeline(["pdftops", pdf_file, "-"],
653
+ # ["lpr", "-P#{printer}"])
654
+ #
655
+ # # count lines
656
+ # Open3.pipeline("sort", "uniq -c", :in=>"names.txt", :out=>"count")
657
+ #
658
+ # # cyclic pipeline
659
+ # r,w = IO.pipe
660
+ # w.print "ibase=14\n10\n"
661
+ # Open3.pipeline("bc", "tee /dev/tty", :in=>r, :out=>w)
662
+ # #=> 14
663
+ # # 18
664
+ # # 22
665
+ # # 30
666
+ # # 42
667
+ # # 58
668
+ # # 78
669
+ # # 106
670
+ # # 202
671
+ #
672
+ def pipeline(*cmds)
673
+ raise NotImplementedError
674
+ end
675
+ module_function :pipeline
676
+ end