open3 0.1.2 → 0.2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b3ab5dfcb825c74af61129ee82254609322af777421f454eb90053875bdbc851
4
- data.tar.gz: fdf3f7c0783de3763390a6e62a7ec984f262d61ca5d8906e186ad7e7deeff5d2
3
+ metadata.gz: d381ba178ac3fc0539b0f526890e2951b8e6f0332d294cd4930b895837643b3b
4
+ data.tar.gz: 7435b46d29c7e24231af4adb8106a32cde9850a896e61153a08fe4b519d5645b
5
5
  SHA512:
6
- metadata.gz: 3e3508856456efd5c0700447c9e122163ea490b32a7bd4cc09d2d4fa79639b53baac5b8f45d983e5185dd439c93cfba2b546c302569e1dccf6d4086c0a3b0858
7
- data.tar.gz: a79fd57d1a714b793ecc3849231cf3d58fd07bcc962ffad144b98ec7443d8da1642e97ab10e6f251c720cc9d0cfe72a809fb71c0c915226fcbed0b2a1c607839
6
+ metadata.gz: e8770606eb547891a3c87fd8636c7a911189d940aa332909098fff948660978c717bb3bd2727fb13378e6b10ee207b7cee17f91a1b02ef18a20cded2bf76f3a9
7
+ data.tar.gz: b12409782b071af20bc628579d5a03ea89023c5e3dd1acbae90a67bafbb748d5c7a4de46ecc5d9ddc2da020de5ff53b89448719025706fc08dfe33b87563df83
@@ -11,7 +11,7 @@ jobs:
11
11
  os: [ ubuntu-latest, macos-latest ]
12
12
  runs-on: ${{ matrix.os }}
13
13
  steps:
14
- - uses: actions/checkout@v3
14
+ - uses: actions/checkout@v4
15
15
  - name: Set up Ruby
16
16
  uses: ruby/setup-ruby@v1
17
17
  with:
@@ -3,15 +3,22 @@ name: test
3
3
  on: [push, pull_request]
4
4
 
5
5
  jobs:
6
- build:
6
+ ruby-versions:
7
+ uses: ruby/actions/.github/workflows/ruby_versions.yml@master
8
+ with:
9
+ engine: cruby
10
+ min_version: 2.6
11
+
12
+ test:
13
+ needs: ruby-versions
7
14
  name: build (${{ matrix.ruby }} / ${{ matrix.os }})
8
15
  strategy:
9
16
  matrix:
10
- ruby: [ '3.0', 2.7, 2.6, head ]
17
+ ruby: ${{ fromJson(needs.ruby-versions.outputs.versions) }}
11
18
  os: [ ubuntu-latest, macos-latest ]
12
19
  runs-on: ${{ matrix.os }}
13
20
  steps:
14
- - uses: actions/checkout@v3
21
+ - uses: actions/checkout@v4
15
22
  - name: Set up Ruby
16
23
  uses: ruby/setup-ruby@v1
17
24
  with:
data/Gemfile CHANGED
@@ -6,4 +6,5 @@ group :development do
6
6
  gem "bundler"
7
7
  gem "rake"
8
8
  gem "test-unit"
9
+ gem "test-unit-ruby-core"
9
10
  end
data/Rakefile CHANGED
@@ -7,11 +7,4 @@ Rake::TestTask.new(:test) do |t|
7
7
  t.test_files = FileList["test/**/test_*.rb"]
8
8
  end
9
9
 
10
- task :sync_tool do
11
- require 'fileutils'
12
- FileUtils.cp "../ruby/tool/lib/core_assertions.rb", "./test/lib"
13
- FileUtils.cp "../ruby/tool/lib/envutil.rb", "./test/lib"
14
- FileUtils.cp "../ruby/tool/lib/find_executable.rb", "./test/lib"
15
- end
16
-
17
10
  task :default => :test
data/lib/open3/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Open3
2
- VERSION = "0.1.2"
2
+ VERSION = "0.2.0"
3
3
  end
data/lib/open3.rb CHANGED
@@ -33,55 +33,133 @@ require 'open3/version'
33
33
 
34
34
  module Open3
35
35
 
36
- # Open stdin, stdout, and stderr streams and start external executable.
37
- # In addition, a thread to wait for the started process is created.
38
- # The thread has a pid method and a thread variable :pid which is the pid of
39
- # the started process.
36
+ # :call-seq:
37
+ # Open3.popen3([env, ] command_line, options = {}) -> [stdin, stdout, stderr, wait_thread]
38
+ # Open3.popen3([env, ] exe_path, *args, options = {}) -> [stdin, stdout, stderr, wait_thread]
39
+ # Open3.popen3([env, ] command_line, options = {}) {|stdin, stdout, stderr, wait_thread| ... } -> object
40
+ # Open3.popen3([env, ] exe_path, *args, options = {}) {|stdin, stdout, stderr, wait_thread| ... } -> object
41
+ #
42
+ # Basically a wrapper for Process.spawn that:
43
+ #
44
+ # - Creates a child process, by calling Process.spawn with the given arguments.
45
+ # - Creates streams +stdin+, +stdout+, and +stderr+,
46
+ # which are the standard input, standard output, and standard error streams
47
+ # in the child process.
48
+ # - Creates thread +wait_thread+ that waits for the child process to exit;
49
+ # the thread has method +pid+, which returns the process ID
50
+ # of the child process.
51
+ #
52
+ # With no block given, returns the array
53
+ # <tt>[stdin, stdout, stderr, wait_thread]</tt>.
54
+ # The caller should close each of the three returned streams.
55
+ #
56
+ # stdin, stdout, stderr, wait_thread = Open3.popen3('echo')
57
+ # # => [#<IO:fd 8>, #<IO:fd 10>, #<IO:fd 12>, #<Process::Waiter:0x00007f58d5428f58 run>]
58
+ # stdin.close
59
+ # stdout.close
60
+ # stderr.close
61
+ # wait_thread.pid # => 2210481
62
+ # wait_thread.value # => #<Process::Status: pid 2210481 exit 0>
63
+ #
64
+ # With a block given, calls the block with the four variables
65
+ # (three streams and the wait thread)
66
+ # and returns the block's return value.
67
+ # The caller need not close the streams:
68
+ #
69
+ # Open3.popen3('echo') do |stdin, stdout, stderr, wait_thread|
70
+ # p stdin
71
+ # p stdout
72
+ # p stderr
73
+ # p wait_thread
74
+ # p wait_thread.pid
75
+ # p wait_thread.value
76
+ # end
40
77
  #
41
- # Block form:
78
+ # Output:
42
79
  #
43
- # Open3.popen3([env,] cmd... [, opts]) {|stdin, stdout, stderr, wait_thr|
44
- # pid = wait_thr.pid # pid of the started process.
45
- # ...
46
- # exit_status = wait_thr.value # Process::Status object returned.
47
- # }
80
+ # #<IO:fd 6>
81
+ # #<IO:fd 7>
82
+ # #<IO:fd 9>
83
+ # #<Process::Waiter:0x00007f58d53606e8 sleep>
84
+ # 2211047
85
+ # #<Process::Status: pid 2211047 exit 0>
48
86
  #
49
- # Non-block form:
87
+ # Like Process.spawn, this method has potential security vulnerabilities
88
+ # if called with untrusted input;
89
+ # see {Command Injection}[rdoc-ref:command_injection.rdoc].
50
90
  #
51
- # stdin, stdout, stderr, wait_thr = Open3.popen3([env,] cmd... [, opts])
52
- # pid = wait_thr[:pid] # pid of the started process
53
- # ...
54
- # stdin.close # stdin, stdout and stderr should be closed explicitly in this form.
55
- # stdout.close
56
- # stderr.close
57
- # exit_status = wait_thr.value # Process::Status object returned.
91
+ # Unlike Process.spawn, this method waits for the child process to exit
92
+ # before returning, so the caller need not do so.
58
93
  #
59
- # The parameters env, cmd, and opts are passed to Process.spawn.
60
- # A commandline string and a list of argument strings can be accepted as follows:
94
+ # Argument +options+ is a hash of options for the new process;
95
+ # see {Execution Options}[rdoc-ref:Process@Execution+Options].
61
96
  #
62
- # Open3.popen3("echo abc") {|i, o, e, t| ... }
63
- # Open3.popen3("echo", "abc") {|i, o, e, t| ... }
64
- # Open3.popen3(["echo", "argv0"], "abc") {|i, o, e, t| ... }
97
+ # The single required argument is one of the following:
65
98
  #
66
- # If the last parameter, opts, is a Hash, it is recognized as an option for Process.spawn.
99
+ # - +command_line+ if it is a string,
100
+ # and if it begins with a shell reserved word or special built-in,
101
+ # or if it contains one or more metacharacters.
102
+ # - +exe_path+ otherwise.
67
103
  #
68
- # Open3.popen3("pwd", :chdir=>"/") {|i,o,e,t|
69
- # p o.read.chomp #=> "/"
70
- # }
104
+ # <b>Argument +command_line+</b>
105
+ #
106
+ # \String argument +command_line+ is a command line to be passed to a shell;
107
+ # it must begin with a shell reserved word, begin with a special built-in,
108
+ # or contain meta characters:
109
+ #
110
+ # Open3.popen3('if true; then echo "Foo"; fi') {|*args| p args } # Shell reserved word.
111
+ # Open3.popen3('echo') {|*args| p args } # Built-in.
112
+ # Open3.popen3('date > date.tmp') {|*args| p args } # Contains meta character.
113
+ #
114
+ # Output (similar for each call above):
115
+ #
116
+ # [#<IO:(closed)>, #<IO:(closed)>, #<IO:(closed)>, #<Process::Waiter:0x00007f58d52f28c8 dead>]
117
+ #
118
+ # The command line may also contain arguments and options for the command:
119
+ #
120
+ # Open3.popen3('echo "Foo"') { |i, o, e, t| o.gets }
121
+ # "Foo\n"
122
+ #
123
+ # <b>Argument +exe_path+</b>
124
+ #
125
+ # Argument +exe_path+ is one of the following:
126
+ #
127
+ # - The string path to an executable to be called.
128
+ # - A 2-element array containing the path to an executable
129
+ # and the string to be used as the name of the executing process.
130
+ #
131
+ # Example:
132
+ #
133
+ # Open3.popen3('/usr/bin/date') { |i, o, e, t| o.gets }
134
+ # # => "Wed Sep 27 02:56:44 PM CDT 2023\n"
135
+ #
136
+ # Ruby invokes the executable directly, with no shell and no shell expansion:
137
+ #
138
+ # Open3.popen3('doesnt_exist') { |i, o, e, t| o.gets } # Raises Errno::ENOENT
139
+ #
140
+ # If one or more +args+ is given, each is an argument or option
141
+ # to be passed to the executable:
71
142
  #
72
- # wait_thr.value waits for the termination of the process.
73
- # The block form also waits for the process when it returns.
143
+ # Open3.popen3('echo', 'C #') { |i, o, e, t| o.gets }
144
+ # # => "C #\n"
145
+ # Open3.popen3('echo', 'hello', 'world') { |i, o, e, t| o.gets }
146
+ # # => "hello world\n"
74
147
  #
75
- # Closing stdin, stdout and stderr does not wait for the process to complete.
148
+ # Take care to avoid deadlocks.
149
+ # Output streams +stdout+ and +stderr+ have fixed-size buffers,
150
+ # so reading extensively from one but not the other can cause a deadlock
151
+ # when the unread buffer fills.
152
+ # To avoid that, +stdout+ and +stderr+ should be read simultaneously
153
+ # (using threads or IO.select).
76
154
  #
77
- # You should be careful to avoid deadlocks.
78
- # Since pipes are fixed length buffers,
79
- # Open3.popen3("prog") {|i, o, e, t| o.read } deadlocks if
80
- # the program generates too much output on stderr.
81
- # You should read stdout and stderr simultaneously (using threads or IO.select).
82
- # However, if you don't need stderr output, you can use Open3.popen2.
83
- # If merged stdout and stderr output is not a problem, you can use Open3.popen2e.
84
- # If you really need stdout and stderr output as separate strings, you can consider Open3.capture3.
155
+ # Related:
156
+ #
157
+ # - Open3.popen2: Makes the standard input and standard output streams
158
+ # of the child process available as separate streams,
159
+ # with no access to the standard error stream.
160
+ # - Open3.popen2e: Makes the standard input and the merge
161
+ # of the standard output and standard error streams
162
+ # of the child process available as separate streams.
85
163
  #
86
164
  def popen3(*cmd, &block)
87
165
  if Hash === cmd.last
@@ -104,45 +182,124 @@ module Open3
104
182
  end
105
183
  module_function :popen3
106
184
 
107
- # Open3.popen2 is similar to Open3.popen3 except that it doesn't create a pipe for
108
- # the standard error stream.
185
+ # :call-seq:
186
+ # Open3.popen2([env, ] command_line, options = {}) -> [stdin, stdout, wait_thread]
187
+ # Open3.popen2([env, ] exe_path, *args, options = {}) -> [stdin, stdout, wait_thread]
188
+ # Open3.popen2([env, ] command_line, options = {}) {|stdin, stdout, wait_thread| ... } -> object
189
+ # Open3.popen2([env, ] exe_path, *args, options = {}) {|stdin, stdout, wait_thread| ... } -> object
190
+ #
191
+ # Basically a wrapper for Process.spawn that:
192
+ #
193
+ # - Creates a child process, by calling Process.spawn with the given arguments.
194
+ # - Creates streams +stdin+ and +stdout+,
195
+ # which are the standard input and standard output streams
196
+ # in the child process.
197
+ # - Creates thread +wait_thread+ that waits for the child process to exit;
198
+ # the thread has method +pid+, which returns the process ID
199
+ # of the child process.
200
+ #
201
+ # With no block given, returns the array
202
+ # <tt>[stdin, stdout, wait_thread]</tt>.
203
+ # The caller should close each of the two returned streams.
204
+ #
205
+ # stdin, stdout, wait_thread = Open3.popen2('echo')
206
+ # # => [#<IO:fd 6>, #<IO:fd 7>, #<Process::Waiter:0x00007f58d52dbe98 run>]
207
+ # stdin.close
208
+ # stdout.close
209
+ # wait_thread.pid # => 2263572
210
+ # wait_thread.value # => #<Process::Status: pid 2263572 exit 0>
211
+ #
212
+ # With a block given, calls the block with the three variables
213
+ # (two streams and the wait thread)
214
+ # and returns the block's return value.
215
+ # The caller need not close the streams:
216
+ #
217
+ # Open3.popen2('echo') do |stdin, stdout, wait_thread|
218
+ # p stdin
219
+ # p stdout
220
+ # p wait_thread
221
+ # p wait_thread.pid
222
+ # p wait_thread.value
223
+ # end
109
224
  #
110
- # Block form:
225
+ # Output:
111
226
  #
112
- # Open3.popen2([env,] cmd... [, opts]) {|stdin, stdout, wait_thr|
113
- # pid = wait_thr.pid # pid of the started process.
114
- # ...
115
- # exit_status = wait_thr.value # Process::Status object returned.
116
- # }
227
+ # #<IO:fd 6>
228
+ # #<IO:fd 7>
229
+ # #<Process::Waiter:0x00007f58d59a34b0 sleep>
230
+ # 2263636
231
+ # #<Process::Status: pid 2263636 exit 0>
117
232
  #
118
- # Non-block form:
233
+ # Like Process.spawn, this method has potential security vulnerabilities
234
+ # if called with untrusted input;
235
+ # see {Command Injection}[rdoc-ref:command_injection.rdoc].
119
236
  #
120
- # stdin, stdout, wait_thr = Open3.popen2([env,] cmd... [, opts])
121
- # ...
122
- # stdin.close # stdin and stdout should be closed explicitly in this form.
123
- # stdout.close
237
+ # Unlike Process.spawn, this method waits for the child process to exit
238
+ # before returning, so the caller need not do so.
239
+ #
240
+ # Argument +options+ is a hash of options for the new process;
241
+ # see {Execution Options}[rdoc-ref:Process@Execution+Options].
242
+ #
243
+ # The single required argument is one of the following:
244
+ #
245
+ # - +command_line+ if it is a string,
246
+ # and if it begins with a shell reserved word or special built-in,
247
+ # or if it contains one or more metacharacters.
248
+ # - +exe_path+ otherwise.
249
+ #
250
+ # <b>Argument +command_line+</b>
251
+ #
252
+ # \String argument +command_line+ is a command line to be passed to a shell;
253
+ # it must begin with a shell reserved word, begin with a special built-in,
254
+ # or contain meta characters:
255
+ #
256
+ # Open3.popen2('if true; then echo "Foo"; fi') {|*args| p args } # Shell reserved word.
257
+ # Open3.popen2('echo') {|*args| p args } # Built-in.
258
+ # Open3.popen2('date > date.tmp') {|*args| p args } # Contains meta character.
259
+ #
260
+ # Output (similar for each call above):
124
261
  #
125
- # See Process.spawn for the optional hash arguments _env_ and _opts_.
262
+ # # => [#<IO:(closed)>, #<IO:(closed)>, #<Process::Waiter:0x00007f7577dfe410 dead>]
263
+ #
264
+ # The command line may also contain arguments and options for the command:
265
+ #
266
+ # Open3.popen2('echo "Foo"') { |i, o, t| o.gets }
267
+ # "Foo\n"
268
+ #
269
+ # <b>Argument +exe_path+</b>
270
+ #
271
+ # Argument +exe_path+ is one of the following:
272
+ #
273
+ # - The string path to an executable to be called.
274
+ # - A 2-element array containing the path to an executable
275
+ # and the string to be used as the name of the executing process.
126
276
  #
127
277
  # Example:
128
278
  #
129
- # Open3.popen2("wc -c") {|i,o,t|
130
- # i.print "answer to life the universe and everything"
131
- # i.close
132
- # p o.gets #=> "42\n"
133
- # }
279
+ # Open3.popen2('/usr/bin/date') { |i, o, t| o.gets }
280
+ # # => "Thu Sep 28 09:41:06 AM CDT 2023\n"
134
281
  #
135
- # Open3.popen2("bc -q") {|i,o,t|
136
- # i.puts "obase=13"
137
- # i.puts "6 * 9"
138
- # p o.gets #=> "42\n"
139
- # }
282
+ # Ruby invokes the executable directly, with no shell and no shell expansion:
283
+ #
284
+ # Open3.popen2('doesnt_exist') { |i, o, t| o.gets } # Raises Errno::ENOENT
285
+ #
286
+ # If one or more +args+ is given, each is an argument or option
287
+ # to be passed to the executable:
288
+ #
289
+ # Open3.popen2('echo', 'C #') { |i, o, t| o.gets }
290
+ # # => "C #\n"
291
+ # Open3.popen2('echo', 'hello', 'world') { |i, o, t| o.gets }
292
+ # # => "hello world\n"
140
293
  #
141
- # Open3.popen2("dc") {|i,o,t|
142
- # i.print "42P"
143
- # i.close
144
- # p o.read #=> "*"
145
- # }
294
+ #
295
+ # Related:
296
+ #
297
+ # - Open3.popen2e: Makes the standard input and the merge
298
+ # of the standard output and standard error streams
299
+ # of the child process available as separate streams.
300
+ # - Open3.popen3: Makes the standard input, standard output,
301
+ # and standard error streams
302
+ # of the child process available as separate streams.
146
303
  #
147
304
  def popen2(*cmd, &block)
148
305
  if Hash === cmd.last
@@ -162,36 +319,123 @@ module Open3
162
319
  end
163
320
  module_function :popen2
164
321
 
165
- # Open3.popen2e is similar to Open3.popen3 except that it merges
166
- # the standard output stream and the standard error stream.
322
+ # :call-seq:
323
+ # Open3.popen2e([env, ] command_line, options = {}) -> [stdin, stdout_and_stderr, wait_thread]
324
+ # Open3.popen2e([env, ] exe_path, *args, options = {}) -> [stdin, stdout_and_stderr, wait_thread]
325
+ # Open3.popen2e([env, ] command_line, options = {}) {|stdin, stdout_and_stderr, wait_thread| ... } -> object
326
+ # Open3.popen2e([env, ] exe_path, *args, options = {}) {|stdin, stdout_and_stderr, wait_thread| ... } -> object
327
+ #
328
+ # Basically a wrapper for Process.spawn that:
329
+ #
330
+ # - Creates a child process, by calling Process.spawn with the given arguments.
331
+ # - Creates streams +stdin+, +stdout_and_stderr+,
332
+ # which are the standard input and the merge of the standard output
333
+ # and standard error streams in the child process.
334
+ # - Creates thread +wait_thread+ that waits for the child process to exit;
335
+ # the thread has method +pid+, which returns the process ID
336
+ # of the child process.
337
+ #
338
+ # With no block given, returns the array
339
+ # <tt>[stdin, stdout_and_stderr, wait_thread]</tt>.
340
+ # The caller should close each of the two returned streams.
341
+ #
342
+ # stdin, stdout_and_stderr, wait_thread = Open3.popen2e('echo')
343
+ # # => [#<IO:fd 6>, #<IO:fd 7>, #<Process::Waiter:0x00007f7577da4398 run>]
344
+ # stdin.close
345
+ # stdout_and_stderr.close
346
+ # wait_thread.pid # => 2274600
347
+ # wait_thread.value # => #<Process::Status: pid 2274600 exit 0>
348
+ #
349
+ # With a block given, calls the block with the three variables
350
+ # (two streams and the wait thread)
351
+ # and returns the block's return value.
352
+ # The caller need not close the streams:
353
+ #
354
+ # Open3.popen2e('echo') do |stdin, stdout_and_stderr, wait_thread|
355
+ # p stdin
356
+ # p stdout_and_stderr
357
+ # p wait_thread
358
+ # p wait_thread.pid
359
+ # p wait_thread.value
360
+ # end
167
361
  #
168
- # Block form:
362
+ # Output:
169
363
  #
170
- # Open3.popen2e([env,] cmd... [, opts]) {|stdin, stdout_and_stderr, wait_thr|
171
- # pid = wait_thr.pid # pid of the started process.
172
- # ...
173
- # exit_status = wait_thr.value # Process::Status object returned.
174
- # }
364
+ # #<IO:fd 6>
365
+ # #<IO:fd 7>
366
+ # #<Process::Waiter:0x00007f75777578c8 sleep>
367
+ # 2274763
368
+ # #<Process::Status: pid 2274763 exit 0>
175
369
  #
176
- # Non-block form:
370
+ # Like Process.spawn, this method has potential security vulnerabilities
371
+ # if called with untrusted input;
372
+ # see {Command Injection}[rdoc-ref:command_injection.rdoc].
177
373
  #
178
- # stdin, stdout_and_stderr, wait_thr = Open3.popen2e([env,] cmd... [, opts])
179
- # ...
180
- # stdin.close # stdin and stdout_and_stderr should be closed explicitly in this form.
181
- # stdout_and_stderr.close
374
+ # Unlike Process.spawn, this method waits for the child process to exit
375
+ # before returning, so the caller need not do so.
376
+ #
377
+ # Argument +options+ is a hash of options for the new process;
378
+ # see {Execution Options}[rdoc-ref:Process@Execution+Options].
379
+ #
380
+ # The single required argument is one of the following:
381
+ #
382
+ # - +command_line+ if it is a string,
383
+ # and if it begins with a shell reserved word or special built-in,
384
+ # or if it contains one or more metacharacters.
385
+ # - +exe_path+ otherwise.
386
+ #
387
+ # <b>Argument +command_line+</b>
388
+ #
389
+ # \String argument +command_line+ is a command line to be passed to a shell;
390
+ # it must begin with a shell reserved word, begin with a special built-in,
391
+ # or contain meta characters:
182
392
  #
183
- # See Process.spawn for the optional hash arguments _env_ and _opts_.
393
+ # Open3.popen2e('if true; then echo "Foo"; fi') {|*args| p args } # Shell reserved word.
394
+ # Open3.popen2e('echo') {|*args| p args } # Built-in.
395
+ # Open3.popen2e('date > date.tmp') {|*args| p args } # Contains meta character.
396
+ #
397
+ # Output (similar for each call above):
398
+ #
399
+ # # => [#<IO:(closed)>, #<IO:(closed)>, #<Process::Waiter:0x00007f7577d8a1f0 dead>]
400
+ #
401
+ # The command line may also contain arguments and options for the command:
402
+ #
403
+ # Open3.popen2e('echo "Foo"') { |i, o_and_e, t| o_and_e.gets }
404
+ # "Foo\n"
405
+ #
406
+ # <b>Argument +exe_path+</b>
407
+ #
408
+ # Argument +exe_path+ is one of the following:
409
+ #
410
+ # - The string path to an executable to be called.
411
+ # - A 2-element array containing the path to an executable
412
+ # and the string to be used as the name of the executing process.
184
413
  #
185
414
  # Example:
186
- # # check gcc warnings
187
- # source = "foo.c"
188
- # Open3.popen2e("gcc", "-Wall", source) {|i,oe,t|
189
- # oe.each {|line|
190
- # if /warning/ =~ line
191
- # ...
192
- # end
193
- # }
194
- # }
415
+ #
416
+ # Open3.popen2e('/usr/bin/date') { |i, o_and_e, t| o_and_e.gets }
417
+ # # => "Thu Sep 28 01:58:45 PM CDT 2023\n"
418
+ #
419
+ # Ruby invokes the executable directly, with no shell and no shell expansion:
420
+ #
421
+ # Open3.popen2e('doesnt_exist') { |i, o_and_e, t| o_and_e.gets } # Raises Errno::ENOENT
422
+ #
423
+ # If one or more +args+ is given, each is an argument or option
424
+ # to be passed to the executable:
425
+ #
426
+ # Open3.popen2e('echo', 'C #') { |i, o_and_e, t| o_and_e.gets }
427
+ # # => "C #\n"
428
+ # Open3.popen2e('echo', 'hello', 'world') { |i, o_and_e, t| o_and_e.gets }
429
+ # # => "hello world\n"
430
+ #
431
+ # Related:
432
+ #
433
+ # - Open3.popen2: Makes the standard input and standard output streams
434
+ # of the child process available as separate streams,
435
+ # with no access to the standard error stream.
436
+ # - Open3.popen3: Makes the standard input, standard output,
437
+ # and standard error streams
438
+ # of the child process available as separate streams.
195
439
  #
196
440
  def popen2e(*cmd, &block)
197
441
  if Hash === cmd.last
@@ -238,44 +482,95 @@ module Open3
238
482
  private :popen_run
239
483
  end
240
484
 
241
- # Open3.capture3 captures the standard output and the standard error of a command.
485
+ # :call-seq:
486
+ # Open3.capture3([env, ] command_line, options = {}) -> [stdout_s, stderr_s, status]
487
+ # Open3.capture3([env, ] exe_path, *args, options = {}) -> [stdout_s, stderr_s, status]
242
488
  #
243
- # stdout_str, stderr_str, status = Open3.capture3([env,] cmd... [, opts])
489
+ # Basically a wrapper for Open3.popen3 that:
244
490
  #
245
- # The arguments env, cmd and opts are passed to Open3.popen3 except
246
- # <code>opts[:stdin_data]</code> and <code>opts[:binmode]</code>. See Process.spawn.
491
+ # - Creates a child process, by calling Open3.popen3 with the given arguments
492
+ # (except for certain entries in hash +options+; see below).
493
+ # - Returns as strings +stdout_s+ and +stderr_s+ the standard output
494
+ # and standard error of the child process.
495
+ # - Returns as +status+ a <tt>Process::Status</tt> object
496
+ # that represents the exit status of the child process.
247
497
  #
248
- # If <code>opts[:stdin_data]</code> is specified, it is sent to the command's standard input.
498
+ # Returns the array <tt>[stdout_s, stderr_s, status]</tt>:
249
499
  #
250
- # If <code>opts[:binmode]</code> is true, internal pipes are set to binary mode.
500
+ # stdout_s, stderr_s, status = Open3.capture3('echo "Foo"')
501
+ # # => ["Foo\n", "", #<Process::Status: pid 2281954 exit 0>]
251
502
  #
252
- # Examples:
503
+ # Like Process.spawn, this method has potential security vulnerabilities
504
+ # if called with untrusted input;
505
+ # see {Command Injection}[rdoc-ref:command_injection.rdoc].
253
506
  #
254
- # # dot is a command of graphviz.
255
- # graph = <<'End'
256
- # digraph g {
257
- # a -> b
258
- # }
259
- # End
260
- # drawn_graph, dot_log = Open3.capture3("dot -v", :stdin_data=>graph)
261
- #
262
- # o, e, s = Open3.capture3("echo abc; sort >&2", :stdin_data=>"foo\nbar\nbaz\n")
263
- # p o #=> "abc\n"
264
- # p e #=> "bar\nbaz\nfoo\n"
265
- # p s #=> #<Process::Status: pid 32682 exit 0>
266
- #
267
- # # generate a thumbnail image using the convert command of ImageMagick.
268
- # # However, if the image is really stored in a file,
269
- # # system("convert", "-thumbnail", "80", "png:#{filename}", "png:-") is better
270
- # # because of reduced memory consumption.
271
- # # But if the image is stored in a DB or generated by the gnuplot Open3.capture2 example,
272
- # # Open3.capture3 should be considered.
273
- # #
274
- # image = File.read("/usr/share/openclipart/png/animals/mammals/sheep-md-v0.1.png", :binmode=>true)
275
- # thumbnail, err, s = Open3.capture3("convert -thumbnail 80 png:- png:-", :stdin_data=>image, :binmode=>true)
276
- # if s.success?
277
- # STDOUT.binmode; print thumbnail
278
- # end
507
+ # Unlike Process.spawn, this method waits for the child process to exit
508
+ # before returning, so the caller need not do so.
509
+ #
510
+ # Argument +options+ is a hash of options for the new process;
511
+ # see {Execution Options}[rdoc-ref:Process@Execution+Options].
512
+ #
513
+ # The hash +options+ is passed to method Open3.popen3;
514
+ # two options have local effect in method Open3.capture3:
515
+ #
516
+ # - If entry <tt>options[:stdin_data]</tt> exists, the entry is removed
517
+ # and its string value is sent to the command's standard input:
518
+ #
519
+ # Open3.capture3('tee', stdin_data: 'Foo')
520
+ # # => ["Foo", "", #<Process::Status: pid 2319575 exit 0>]
521
+ #
522
+ # - If entry <tt>options[:binmode]</tt> exists, the entry is removed
523
+ # the internal streams are set to binary mode.
524
+ #
525
+ # The single required argument is one of the following:
526
+ #
527
+ # - +command_line+ if it is a string,
528
+ # and if it begins with a shell reserved word or special built-in,
529
+ # or if it contains one or more metacharacters.
530
+ # - +exe_path+ otherwise.
531
+ #
532
+ # <b>Argument +command_line+</b>
533
+ #
534
+ # \String argument +command_line+ is a command line to be passed to a shell;
535
+ # it must begin with a shell reserved word, begin with a special built-in,
536
+ # or contain meta characters:
537
+ #
538
+ # Open3.capture3('if true; then echo "Foo"; fi') # Shell reserved word.
539
+ # # => ["Foo\n", "", #<Process::Status: pid 2282025 exit 0>]
540
+ # Open3.capture3('echo') # Built-in.
541
+ # # => ["\n", "", #<Process::Status: pid 2282092 exit 0>]
542
+ # Open3.capture3('date > date.tmp') # Contains meta character.
543
+ # # => ["", "", #<Process::Status: pid 2282110 exit 0>]
544
+ #
545
+ # The command line may also contain arguments and options for the command:
546
+ #
547
+ # Open3.capture3('echo "Foo"')
548
+ # # => ["Foo\n", "", #<Process::Status: pid 2282092 exit 0>]
549
+ #
550
+ # <b>Argument +exe_path+</b>
551
+ #
552
+ # Argument +exe_path+ is one of the following:
553
+ #
554
+ # - The string path to an executable to be called.
555
+ # - A 2-element array containing the path to an executable
556
+ # and the string to be used as the name of the executing process.
557
+ #
558
+ # Example:
559
+ #
560
+ # Open3.capture3('/usr/bin/date')
561
+ # # => ["Thu Sep 28 05:03:51 PM CDT 2023\n", "", #<Process::Status: pid 2282300 exit 0>]
562
+ #
563
+ # Ruby invokes the executable directly, with no shell and no shell expansion:
564
+ #
565
+ # Open3.capture3('doesnt_exist') # Raises Errno::ENOENT
566
+ #
567
+ # If one or more +args+ is given, each is an argument or option
568
+ # to be passed to the executable:
569
+ #
570
+ # Open3.capture3('echo', 'C #')
571
+ # # => ["C #\n", "", #<Process::Status: pid 2282368 exit 0>]
572
+ # Open3.capture3('echo', 'hello', 'world')
573
+ # # => ["hello world\n", "", #<Process::Status: pid 2282372 exit 0>]
279
574
  #
280
575
  def capture3(*cmd)
281
576
  if Hash === cmd.last
@@ -309,34 +604,94 @@ module Open3
309
604
  end
310
605
  module_function :capture3
311
606
 
312
- # Open3.capture2 captures the standard output of a command.
607
+ # :call-seq:
608
+ # Open3.capture2([env, ] command_line, options = {}) -> [stdout_s, status]
609
+ # Open3.capture2([env, ] exe_path, *args, options = {}) -> [stdout_s, status]
610
+ #
611
+ # Basically a wrapper for Open3.popen3 that:
612
+ #
613
+ # - Creates a child process, by calling Open3.popen3 with the given arguments
614
+ # (except for certain entries in hash +options+; see below).
615
+ # - Returns as string +stdout_s+ the standard output of the child process.
616
+ # - Returns as +status+ a <tt>Process::Status</tt> object
617
+ # that represents the exit status of the child process.
618
+ #
619
+ # Returns the array <tt>[stdout_s, status]</tt>:
620
+ #
621
+ # stdout_s, status = Open3.capture2('echo "Foo"')
622
+ # # => ["Foo\n", #<Process::Status: pid 2326047 exit 0>]
623
+ #
624
+ # Like Process.spawn, this method has potential security vulnerabilities
625
+ # if called with untrusted input;
626
+ # see {Command Injection}[rdoc-ref:command_injection.rdoc].
627
+ #
628
+ # Unlike Process.spawn, this method waits for the child process to exit
629
+ # before returning, so the caller need not do so.
313
630
  #
314
- # stdout_str, status = Open3.capture2([env,] cmd... [, opts])
631
+ # Argument +options+ is a hash of options for the new process;
632
+ # see {Execution Options}[rdoc-ref:Process@Execution+Options].
315
633
  #
316
- # The arguments env, cmd and opts are passed to Open3.popen3 except
317
- # <code>opts[:stdin_data]</code> and <code>opts[:binmode]</code>. See Process.spawn.
634
+ # The hash +options+ is passed to method Open3.popen3;
635
+ # two options have local effect in method Open3.capture2:
318
636
  #
319
- # If <code>opts[:stdin_data]</code> is specified, it is sent to the command's standard input.
637
+ # - If entry <tt>options[:stdin_data]</tt> exists, the entry is removed
638
+ # and its string value is sent to the command's standard input:
320
639
  #
321
- # If <code>opts[:binmode]</code> is true, internal pipes are set to binary mode.
640
+ # Open3.capture2('tee', stdin_data: 'Foo')
641
+ #
642
+ # # => ["Foo", #<Process::Status: pid 2326087 exit 0>]
643
+ #
644
+ # the internal streams are set to binary mode.
645
+ #
646
+ # The single required argument is one of the following:
647
+ #
648
+ # - +command_line+ if it is a string,
649
+ # and if it begins with a shell reserved word or special built-in,
650
+ # or if it contains one or more metacharacters.
651
+ # - +exe_path+ otherwise.
652
+ #
653
+ # <b>Argument +command_line+</b>
654
+ #
655
+ # \String argument +command_line+ is a command line to be passed to a shell;
656
+ # it must begin with a shell reserved word, begin with a special built-in,
657
+ # or contain meta characters:
658
+ #
659
+ # Open3.capture2('if true; then echo "Foo"; fi') # Shell reserved word.
660
+ # # => ["Foo\n", #<Process::Status: pid 2326131 exit 0>]
661
+ # Open3.capture2('echo') # Built-in.
662
+ # # => ["\n", #<Process::Status: pid 2326139 exit 0>]
663
+ # Open3.capture2('date > date.tmp') # Contains meta character.
664
+ # # => ["", #<Process::Status: pid 2326174 exit 0>]
665
+ #
666
+ # The command line may also contain arguments and options for the command:
667
+ #
668
+ # Open3.capture2('echo "Foo"')
669
+ # # => ["Foo\n", #<Process::Status: pid 2326183 exit 0>]
670
+ #
671
+ # <b>Argument +exe_path+</b>
672
+ #
673
+ # Argument +exe_path+ is one of the following:
674
+ #
675
+ # - The string path to an executable to be called.
676
+ # - A 2-element array containing the path to an executable
677
+ # and the string to be used as the name of the executing process.
322
678
  #
323
679
  # Example:
324
680
  #
325
- # # factor is a command for integer factorization.
326
- # o, s = Open3.capture2("factor", :stdin_data=>"42")
327
- # p o #=> "42: 2 3 7\n"
328
- #
329
- # # generate x**2 graph in png using gnuplot.
330
- # gnuplot_commands = <<"End"
331
- # set terminal png
332
- # plot x**2, "-" with lines
333
- # 1 14
334
- # 2 1
335
- # 3 8
336
- # 4 5
337
- # e
338
- # End
339
- # image, s = Open3.capture2("gnuplot", :stdin_data=>gnuplot_commands, :binmode=>true)
681
+ # Open3.capture2('/usr/bin/date')
682
+ # # => ["Fri Sep 29 01:00:39 PM CDT 2023\n", #<Process::Status: pid 2326222 exit 0>]
683
+ #
684
+ # Ruby invokes the executable directly, with no shell and no shell expansion:
685
+ #
686
+ # Open3.capture2('doesnt_exist') # Raises Errno::ENOENT
687
+ #
688
+ # If one or more +args+ is given, each is an argument or option
689
+ # to be passed to the executable:
690
+ #
691
+ # Open3.capture2('echo', 'C #')
692
+ # # => ["C #\n", #<Process::Status: pid 2326267 exit 0>]
693
+ # Open3.capture2('echo', 'hello', 'world')
694
+ # # => ["hello world\n", #<Process::Status: pid 2326299 exit 0>]
340
695
  #
341
696
  def capture2(*cmd)
342
697
  if Hash === cmd.last
@@ -370,21 +725,94 @@ module Open3
370
725
  end
371
726
  module_function :capture2
372
727
 
373
- # Open3.capture2e captures the standard output and the standard error of a command.
728
+ # :call-seq:
729
+ # Open3.capture2e([env, ] command_line, options = {}) -> [stdout_and_stderr_s, status]
730
+ # Open3.capture2e([env, ] exe_path, *args, options = {}) -> [stdout_and_stderr_s, status]
731
+ #
732
+ # Basically a wrapper for Open3.popen3 that:
733
+ #
734
+ # - Creates a child process, by calling Open3.popen3 with the given arguments
735
+ # (except for certain entries in hash +options+; see below).
736
+ # - Returns as string +stdout_and_stderr_s+ the merged standard output
737
+ # and standard error of the child process.
738
+ # - Returns as +status+ a <tt>Process::Status</tt> object
739
+ # that represents the exit status of the child process.
740
+ #
741
+ # Returns the array <tt>[stdout_and_stderr_s, status]</tt>:
374
742
  #
375
- # stdout_and_stderr_str, status = Open3.capture2e([env,] cmd... [, opts])
743
+ # stdout_and_stderr_s, status = Open3.capture2e('echo "Foo"')
744
+ # # => ["Foo\n", #<Process::Status: pid 2371692 exit 0>]
376
745
  #
377
- # The arguments env, cmd and opts are passed to Open3.popen3 except
378
- # <code>opts[:stdin_data]</code> and <code>opts[:binmode]</code>. See Process.spawn.
746
+ # Like Process.spawn, this method has potential security vulnerabilities
747
+ # if called with untrusted input;
748
+ # see {Command Injection}[rdoc-ref:command_injection.rdoc].
379
749
  #
380
- # If <code>opts[:stdin_data]</code> is specified, it is sent to the command's standard input.
750
+ # Unlike Process.spawn, this method waits for the child process to exit
751
+ # before returning, so the caller need not do so.
381
752
  #
382
- # If <code>opts[:binmode]</code> is true, internal pipes are set to binary mode.
753
+ # Argument +options+ is a hash of options for the new process;
754
+ # see {Execution Options}[rdoc-ref:Process@Execution+Options].
755
+ #
756
+ # The hash +options+ is passed to method Open3.popen3;
757
+ # two options have local effect in method Open3.capture2e:
758
+ #
759
+ # - If entry <tt>options[:stdin_data]</tt> exists, the entry is removed
760
+ # and its string value is sent to the command's standard input:
761
+ #
762
+ # Open3.capture2e('tee', stdin_data: 'Foo')
763
+ # # => ["Foo", #<Process::Status: pid 2371732 exit 0>]
764
+ #
765
+ # the internal streams are set to binary mode.
766
+ #
767
+ # The single required argument is one of the following:
768
+ #
769
+ # - +command_line+ if it is a string,
770
+ # and if it begins with a shell reserved word or special built-in,
771
+ # or if it contains one or more metacharacters.
772
+ # - +exe_path+ otherwise.
773
+ #
774
+ # <b>Argument +command_line+</b>
775
+ #
776
+ # \String argument +command_line+ is a command line to be passed to a shell;
777
+ # it must begin with a shell reserved word, begin with a special built-in,
778
+ # or contain meta characters:
779
+ #
780
+ # Open3.capture2e('if true; then echo "Foo"; fi') # Shell reserved word.
781
+ # # => ["Foo\n", #<Process::Status: pid 2371740 exit 0>]
782
+ # Open3.capture2e('echo') # Built-in.
783
+ # # => ["\n", #<Process::Status: pid 2371774 exit 0>]
784
+ # Open3.capture2e('date > date.tmp') # Contains meta character.
785
+ # # => ["", #<Process::Status: pid 2371812 exit 0>]
786
+ #
787
+ # The command line may also contain arguments and options for the command:
788
+ #
789
+ # Open3.capture2e('echo "Foo"')
790
+ # # => ["Foo\n", #<Process::Status: pid 2326183 exit 0>]
791
+ #
792
+ # <b>Argument +exe_path+</b>
793
+ #
794
+ # Argument +exe_path+ is one of the following:
795
+ #
796
+ # - The string path to an executable to be called.
797
+ # - A 2-element array containing the path to an executable
798
+ # and the string to be used as the name of the executing process.
383
799
  #
384
800
  # Example:
385
801
  #
386
- # # capture make log
387
- # make_log, s = Open3.capture2e("make")
802
+ # Open3.capture2e('/usr/bin/date')
803
+ # # => ["Sat Sep 30 09:01:46 AM CDT 2023\n", #<Process::Status: pid 2371820 exit 0>]
804
+ #
805
+ # Ruby invokes the executable directly, with no shell and no shell expansion:
806
+ #
807
+ # Open3.capture2e('doesnt_exist') # Raises Errno::ENOENT
808
+ #
809
+ # If one or more +args+ is given, each is an argument or option
810
+ # to be passed to the executable:
811
+ #
812
+ # Open3.capture2e('echo', 'C #')
813
+ # # => ["C #\n", #<Process::Status: pid 2371856 exit 0>]
814
+ # Open3.capture2e('echo', 'hello', 'world')
815
+ # # => ["hello world\n", #<Process::Status: pid 2371894 exit 0>]
388
816
  #
389
817
  def capture2e(*cmd)
390
818
  if Hash === cmd.last
data/open3.gemspec CHANGED
@@ -25,7 +25,7 @@ Gem::Specification.new do |spec|
25
25
  # Specify which files should be added to the gem when it is released.
26
26
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
27
27
  spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
28
- `git ls-files -z 2>/dev/null`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
28
+ `git ls-files -z 2>#{IO::NULL}`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
29
29
  end
30
30
  spec.bindir = "exe"
31
31
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: open3
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yukihiro Matsumoto
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-12-14 00:00:00.000000000 Z
11
+ date: 2023-11-07 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Popen, but with stderr, too
14
14
  email:
@@ -53,7 +53,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
55
  requirements: []
56
- rubygems_version: 3.4.0.dev
56
+ rubygems_version: 3.5.0.dev
57
57
  signing_key:
58
58
  specification_version: 4
59
59
  summary: Popen, but with stderr, too