subprocess 1.3.0 → 1.5.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/README.md +46 -19
- data/lib/subprocess.rb +265 -94
- data/lib/subprocess/version.rb +1 -1
- data/rbi/subprocess.rbi +312 -0
- metadata +18 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: ca7ff75142ac9cdc919053f61071f8ff54d21193754239da85b810d0dce7886d
|
4
|
+
data.tar.gz: ae5ec4dedac5fd3042e6006ae798bf286c5705b6aca0da37c398fbd52b16d100
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d0e375e45a5ed1089690a90abe9bf3fcc83754a4bd490047465ff0b83bd82966a5cb73fcd6738cb215c6351784582b90a341ba7ad7315738ef426f532215ed9b
|
7
|
+
data.tar.gz: 87b16802e5534b86c4f2d0ab7b7f5bd0cd885b9375af520341a936c017e5c23a8a33a76bcd7a50566cb9a2631e77fc8c9797779e78e03c0e75afe916d680efb1
|
data/README.md
CHANGED
@@ -1,14 +1,8 @@
|
|
1
|
-
# Subprocess
|
1
|
+
# Subprocess ![Ruby](https://github.com/stripe/subprocess/workflows/Ruby/badge.svg) [![Yard Docs](http://img.shields.io/badge/yard-docs-blue.svg)](http://rubydoc.info/github/stripe/subprocess/Subprocess)
|
2
2
|
|
3
3
|
![Jacques Cousteau Submarine](http://i.imgur.com/lmej24F.jpg)
|
4
4
|
|
5
|
-
A
|
6
|
-
|
7
|
-
Many thanks to [Bram Swenson][bram], the author of the old [subprocess][old]
|
8
|
-
gem, for graciously letting us use the name.
|
9
|
-
|
10
|
-
[bram]: https://github.com/bramswenson
|
11
|
-
[old]: https://github.com/bramswenson/subprocess
|
5
|
+
A solid subprocess library for ruby, inspired by python's.
|
12
6
|
|
13
7
|
Installation
|
14
8
|
------------
|
@@ -21,20 +15,10 @@ You can also build `subprocess` from source by running:
|
|
21
15
|
|
22
16
|
$ gem build subprocess.gemspec
|
23
17
|
|
24
|
-
|
25
18
|
Usage
|
26
19
|
-----
|
27
20
|
|
28
|
-
|
29
|
-
equally well to this gem as well. While there are a few places when our
|
30
|
-
semantics differs from Python's, users of the Python module should largely feel
|
31
|
-
at home using `subprocess`. We have attempted to [document][rubydoc] all of the
|
32
|
-
differences, but if we have missed something, please file an issue.
|
33
|
-
|
34
|
-
[python]: http://docs.python.org/library/subprocess.html
|
35
|
-
[rubydoc]: http://rubydoc.info/github/stripe/subprocess
|
36
|
-
|
37
|
-
A few examples:
|
21
|
+
Full documentation is on [RubyDoc][rubydoc]. A few examples:
|
38
22
|
|
39
23
|
```ruby
|
40
24
|
require 'subprocess'
|
@@ -71,3 +55,46 @@ http://upload.wikimedia.org/wikipedia/commons/3/3e/Unshorn_alpaca_grazing.jpg
|
|
71
55
|
EMAIL
|
72
56
|
end
|
73
57
|
```
|
58
|
+
|
59
|
+
Most of the documentation for Python's [subprocess][python] module applies
|
60
|
+
equally well to this gem as well. While there are a few places when our
|
61
|
+
semantics differs from Python's, users of the Python module should largely feel
|
62
|
+
at home using `subprocess`. We have attempted to [document][rubydoc] all of the
|
63
|
+
differences, but if we have missed something, please file an issue.
|
64
|
+
|
65
|
+
[python]: http://docs.python.org/library/subprocess.html
|
66
|
+
[rubydoc]: http://rubydoc.info/github/stripe/subprocess/Subprocess
|
67
|
+
|
68
|
+
Maintenance
|
69
|
+
-----------
|
70
|
+
|
71
|
+
Steps to release a new version:
|
72
|
+
|
73
|
+
```bash
|
74
|
+
# Work directly on master
|
75
|
+
git checkout master
|
76
|
+
|
77
|
+
# -- edit version in lib/subprocess/version.rb --
|
78
|
+
|
79
|
+
# Update RBI files
|
80
|
+
bundle exec rake sord
|
81
|
+
|
82
|
+
# Subsequent commands reference the version
|
83
|
+
VERSION=1.5.5
|
84
|
+
git commit -am "Bump version to $VERSION"
|
85
|
+
git tag "v$VERSION"
|
86
|
+
git push origin master --tags
|
87
|
+
bundle exec rake publish
|
88
|
+
```
|
89
|
+
|
90
|
+
If you get errors, ask someone to add you to `bindings/rubygems-api-key` in
|
91
|
+
Vault or ask someone who already has permissions. See <http://go/password-vault>
|
92
|
+
|
93
|
+
Acknowledgements
|
94
|
+
----------------
|
95
|
+
|
96
|
+
Many thanks to [Bram Swenson][bram], the author of the old [subprocess][old]
|
97
|
+
gem, for graciously letting us use the name.
|
98
|
+
|
99
|
+
[bram]: https://github.com/bramswenson
|
100
|
+
[old]: https://github.com/bramswenson/subprocess
|
data/lib/subprocess.rb
CHANGED
@@ -16,7 +16,13 @@ module Subprocess
|
|
16
16
|
|
17
17
|
# An alias for `Process.new`. Mostly here to better emulate the Python API.
|
18
18
|
#
|
19
|
+
# @param [Array<String>] cmd See {Process#initialize}
|
20
|
+
# @param [Hash] opts See {Process#initialize}
|
21
|
+
# @yield [process] See {Process#initialize}
|
22
|
+
# @yieldparam process [Process] See {Process#initialize}
|
19
23
|
# @return [Process] A process with the given arguments
|
24
|
+
#
|
25
|
+
# @see Process#initialize
|
20
26
|
def self.popen(cmd, opts={}, &blk)
|
21
27
|
Process.new(cmd, opts, &blk)
|
22
28
|
end
|
@@ -28,9 +34,14 @@ module Subprocess
|
|
28
34
|
# fills up, as neither file descriptor will be read from. To avoid this, use
|
29
35
|
# {Process#communicate} from a passed block.
|
30
36
|
#
|
37
|
+
# @param [Array<String>] cmd See {Process#initialize}
|
38
|
+
# @param [Hash] opts See {Process#initialize}
|
39
|
+
# @yield [process] See {Process#initialize}
|
40
|
+
# @yieldparam process [Process] See {Process#initialize}
|
41
|
+
#
|
31
42
|
# @return [::Process::Status] The exit status of the process
|
32
43
|
#
|
33
|
-
# @see
|
44
|
+
# @see Process#initialize
|
34
45
|
def self.call(cmd, opts={}, &blk)
|
35
46
|
Process.new(cmd, opts, &blk).wait
|
36
47
|
end
|
@@ -58,11 +69,16 @@ module Subprocess
|
|
58
69
|
# fills up, as neither file descriptor will be read from. To avoid this, use
|
59
70
|
# {Process#communicate} from a passed block.
|
60
71
|
#
|
72
|
+
# @param [Array<String>] cmd See {Process#initialize}
|
73
|
+
# @param [Hash] opts See {Process#initialize}
|
74
|
+
# @yield [process] See {Process#initialize}
|
75
|
+
# @yieldparam process [Process] See {Process#initialize}
|
76
|
+
#
|
61
77
|
# @raise [NonZeroExit] if the process returned a non-zero exit status (i.e.,
|
62
78
|
# was terminated with an error or was killed by a signal)
|
63
79
|
# @return [::Process::Status] The exit status of the process
|
64
80
|
#
|
65
|
-
# @see
|
81
|
+
# @see Process#initialize
|
66
82
|
def self.check_call(cmd, opts={}, &blk)
|
67
83
|
status = Process.new(cmd, opts, &blk).wait
|
68
84
|
raise NonZeroExit.new(cmd, status) unless status.success?
|
@@ -70,16 +86,21 @@ module Subprocess
|
|
70
86
|
end
|
71
87
|
|
72
88
|
# Like {Subprocess::check_call}, but return the contents of `stdout`, much
|
73
|
-
# like
|
89
|
+
# like {::Kernel#system}.
|
74
90
|
#
|
75
91
|
# @example Get the system load
|
76
92
|
# system_load = Subprocess.check_output(['uptime']).split(' ').last(3)
|
77
93
|
#
|
94
|
+
# @param [Array<String>] cmd See {Process#initialize}
|
95
|
+
# @param [Hash] opts See {Process#initialize}
|
96
|
+
# @yield [process] See {Process#initialize}
|
97
|
+
# @yieldparam process [Process] See {Process#initialize}
|
98
|
+
#
|
78
99
|
# @raise [NonZeroExit] if the process returned a non-zero exit status (i.e.,
|
79
100
|
# was terminated with an error or was killed by a signal)
|
80
101
|
# @return [String] The contents of `stdout`
|
81
102
|
#
|
82
|
-
# @see
|
103
|
+
# @see Process#initialize
|
83
104
|
def self.check_output(cmd, opts={}, &blk)
|
84
105
|
opts[:stdout] = PIPE
|
85
106
|
child = Process.new(cmd, opts, &blk)
|
@@ -107,14 +128,7 @@ module Subprocess
|
|
107
128
|
# been according to the usual exit status convention
|
108
129
|
sig_num = status.exitstatus - 128
|
109
130
|
|
110
|
-
|
111
|
-
if Signal.respond_to?(:signame)
|
112
|
-
# ruby 2.0 way
|
113
|
-
sig_name = Signal.signame(sig_num)
|
114
|
-
else
|
115
|
-
# ruby 1.9 way
|
116
|
-
sig_name = Signal.list.key(sig_num)
|
117
|
-
end
|
131
|
+
sig_name = Signal.signame(sig_num)
|
118
132
|
|
119
133
|
if sig_name
|
120
134
|
parts << "(maybe SIG#{sig_name})"
|
@@ -137,16 +151,17 @@ module Subprocess
|
|
137
151
|
|
138
152
|
# Error class representing a process's abnormal exit.
|
139
153
|
class NonZeroExit < StandardError
|
140
|
-
#
|
141
|
-
#
|
142
|
-
#
|
143
|
-
#
|
144
|
-
#
|
145
|
-
#
|
146
|
-
#
|
147
|
-
|
148
|
-
|
149
|
-
|
154
|
+
# @note This is intended only for use in user-facing error messages. In
|
155
|
+
# particular, no shell quoting of any sort is performed when
|
156
|
+
# constructing this string, meaning that blindly running it in a shell
|
157
|
+
# might have different semantics than the original command.
|
158
|
+
#
|
159
|
+
# @return [String] The command and arguments for the process that exited
|
160
|
+
# abnormally.
|
161
|
+
attr_reader :command
|
162
|
+
|
163
|
+
# @return [::Process::Status] The Ruby status object returned by `waitpid`
|
164
|
+
attr_reader :status
|
150
165
|
|
151
166
|
# Return an instance of {NonZeroExit}.
|
152
167
|
#
|
@@ -168,42 +183,63 @@ module Subprocess
|
|
168
183
|
end
|
169
184
|
end
|
170
185
|
|
186
|
+
# Error class representing a timeout during a call to `communicate`
|
187
|
+
class CommunicateTimeout < StandardError
|
188
|
+
# @return [String] Content read from stdout before the timeout
|
189
|
+
attr_reader :stdout
|
190
|
+
|
191
|
+
# @return [String] Content read from stderr before the timeout
|
192
|
+
attr_reader :stderr
|
193
|
+
|
194
|
+
# @param [Array<String>] cmd
|
195
|
+
# @param [String] stdout
|
196
|
+
# @param [String] stderr
|
197
|
+
def initialize(cmd, stdout, stderr)
|
198
|
+
@stdout = stdout
|
199
|
+
@stderr = stderr
|
200
|
+
|
201
|
+
super("Timeout communicating with `#{cmd.join(' ')}`")
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
171
205
|
# A child process. The preferred way of spawning a subprocess is through the
|
172
206
|
# functions on {Subprocess} (especially {Subprocess::check_call} and
|
173
207
|
# {Subprocess::check_output}).
|
174
208
|
class Process
|
175
|
-
#
|
176
|
-
|
177
|
-
|
178
|
-
#
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
#
|
185
|
-
|
186
|
-
|
187
|
-
#
|
188
|
-
|
189
|
-
|
190
|
-
|
209
|
+
# @return [IO] The `IO` that is connected to this process's `stdin`.
|
210
|
+
attr_reader :stdin
|
211
|
+
|
212
|
+
# @return [IO] The `IO` that is connected to this process's `stdout`.
|
213
|
+
attr_reader :stdout
|
214
|
+
|
215
|
+
# @return [IO] The `IO` that is connected to this process's `stderr`.
|
216
|
+
attr_reader :stderr
|
217
|
+
|
218
|
+
# @return [Array<String>] The command this process was invoked with.
|
219
|
+
attr_reader :command
|
220
|
+
|
221
|
+
# @return [Integer] The process ID of the spawned process.
|
222
|
+
attr_reader :pid
|
223
|
+
|
224
|
+
# @return [::Process::Status, nil] The exit status code of the process.
|
225
|
+
# Only set after the process has exited.
|
226
|
+
attr_reader :status
|
191
227
|
|
192
228
|
# Create a new process.
|
193
229
|
#
|
194
230
|
# @param [Array<String>] cmd The command to run and its arguments (in the
|
195
231
|
# style of an `argv` array). Unlike Python's subprocess module, `cmd`
|
196
|
-
#
|
232
|
+
# cannot be a String.
|
197
233
|
#
|
198
|
-
# @option opts [IO,
|
234
|
+
# @option opts [IO, Integer, String, Subprocess::PIPE, nil] :stdin The `IO`,
|
199
235
|
# file descriptor number, or file name to use for the process's standard
|
200
236
|
# input. If the magic value {Subprocess::PIPE} is passed, a new pipe will
|
201
237
|
# be opened.
|
202
|
-
# @option opts [IO,
|
238
|
+
# @option opts [IO, Integer, String, Subprocess::PIPE, nil] :stdout The `IO`,
|
203
239
|
# file descriptor number, or file name to use for the process's standard
|
204
240
|
# output. If the magic value {Subprocess::PIPE} is passed, a pipe will be
|
205
241
|
# opened and attached to the process.
|
206
|
-
# @option opts [IO,
|
242
|
+
# @option opts [IO, Integer, String, Subprocess::PIPE, Subprocess::STDOUT,
|
207
243
|
# nil] :stderr The `IO`, file descriptor number, or file name to use for
|
208
244
|
# the process's standard error. If the special value {Subprocess::PIPE} is
|
209
245
|
# passed, a pipe will be opened and attached to the process. If the
|
@@ -214,12 +250,12 @@ module Subprocess
|
|
214
250
|
# child process.
|
215
251
|
# @option opts [Hash<String, String>] :env The environment to use in the
|
216
252
|
# child process.
|
217
|
-
# @option opts [Array<
|
253
|
+
# @option opts [Array<Integer>] :retain_fds An array of file descriptor
|
218
254
|
# numbers that should not be closed before executing the child process.
|
219
255
|
# Note that, unlike Python (which has :close_fds defaulting to false), all
|
220
256
|
# file descriptors not specified here will be closed.
|
221
257
|
# @option opts [Hash] :exec_opts A hash that will be merged into the options
|
222
|
-
# hash of the call to {Kernel#exec}.
|
258
|
+
# hash of the call to {::Kernel#exec}.
|
223
259
|
#
|
224
260
|
# @option opts [Proc] :preexec_fn A function that will be called in the
|
225
261
|
# child process immediately before executing `cmd`.
|
@@ -230,7 +266,7 @@ module Subprocess
|
|
230
266
|
# in conjunction with {Subprocess::check_call}.
|
231
267
|
# @yieldparam process [Process] The process that was just spawned.
|
232
268
|
def initialize(cmd, opts={}, &blk)
|
233
|
-
raise ArgumentError, "cmd must be an Array" unless Array === cmd
|
269
|
+
raise ArgumentError, "cmd must be an Array of strings" unless Array === cmd
|
234
270
|
raise ArgumentError, "cmd cannot be empty" if cmd.empty?
|
235
271
|
|
236
272
|
@command = cmd
|
@@ -250,8 +286,6 @@ module Subprocess
|
|
250
286
|
|
251
287
|
@pid = fork do
|
252
288
|
begin
|
253
|
-
FileUtils.cd(opts[:cwd]) if opts[:cwd]
|
254
|
-
|
255
289
|
::STDIN.reopen(@child_stdin) if @child_stdin
|
256
290
|
::STDOUT.reopen(@child_stdout) if @child_stdout
|
257
291
|
if opts[:stderr] == STDOUT
|
@@ -263,24 +297,44 @@ module Subprocess
|
|
263
297
|
# Set up a new environment if we're requested to do so.
|
264
298
|
if opts[:env]
|
265
299
|
ENV.clear
|
266
|
-
|
300
|
+
begin
|
301
|
+
ENV.update(opts[:env])
|
302
|
+
rescue TypeError => e
|
303
|
+
raise ArgumentError, "`env` option must be a hash where all keys and values are strings (#{e})"
|
304
|
+
end
|
267
305
|
end
|
268
306
|
|
269
307
|
# Call the user back, maybe?
|
270
|
-
|
308
|
+
if opts[:preexec_fn]
|
309
|
+
if opts[:cwd]
|
310
|
+
Dir.chdir(opts[:cwd], &opts[:preexec_fn])
|
311
|
+
else
|
312
|
+
opts[:preexec_fn].call
|
313
|
+
end
|
314
|
+
end
|
271
315
|
|
272
316
|
options = {close_others: true}.merge(opts.fetch(:exec_opts, {}))
|
273
317
|
if opts[:retain_fds]
|
274
318
|
retained_fds.each { |fd| options[fd] = fd }
|
275
319
|
end
|
320
|
+
if opts[:cwd]
|
321
|
+
# We use the chdir option to `exec` since wrapping the
|
322
|
+
# `exec` in a Dir.chdir block caused these sporadic errors on macOS:
|
323
|
+
# Too many open files - getcwd (Errno::EMFILE)
|
324
|
+
options[:chdir] = opts[:cwd]
|
325
|
+
end
|
276
326
|
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
327
|
+
begin
|
328
|
+
# Ruby's Kernel#exec will call an exec(3) variant if called with two
|
329
|
+
# or more arguments, but when called with just a single argument will
|
330
|
+
# spawn a subshell with that argument as the command. Since we always
|
331
|
+
# want to call exec(3), we use the third exec form, which passes a
|
332
|
+
# [cmdname, argv0] array as its first argument and never invokes a
|
333
|
+
# subshell.
|
334
|
+
exec([cmd[0], cmd[0]], *cmd[1..-1], options)
|
335
|
+
rescue TypeError => e
|
336
|
+
raise ArgumentError, "cmd must be an Array of strings (#{e})"
|
337
|
+
end
|
284
338
|
|
285
339
|
rescue Exception => e
|
286
340
|
# Dump all errors up to the parent through the control socket
|
@@ -343,7 +397,7 @@ module Subprocess
|
|
343
397
|
# condition (`EOFError` or `EPIPE`).
|
344
398
|
def drain_fd(fd, buf=nil)
|
345
399
|
loop do
|
346
|
-
tmp = fd.read_nonblock(4096)
|
400
|
+
tmp = fd.read_nonblock(4096).force_encoding(fd.external_encoding)
|
347
401
|
buf << tmp unless buf.nil?
|
348
402
|
end
|
349
403
|
rescue EOFError, Errno::EPIPE
|
@@ -354,40 +408,51 @@ module Subprocess
|
|
354
408
|
false
|
355
409
|
end
|
356
410
|
|
357
|
-
# Write the (optional) input to the process's `stdin
|
358
|
-
#
|
359
|
-
#
|
411
|
+
# Write the (optional) input to the process's `stdin` and read the contents of
|
412
|
+
# `stdout` and `stderr`. If a block is provided, stdout and stderr are yielded as they
|
413
|
+
# are read. Otherwise they are buffered in memory and returned when the process
|
414
|
+
# exits. Do this all using `IO::select`, so we don't deadlock due to full pipe
|
415
|
+
# buffers.
|
360
416
|
#
|
361
417
|
# This is only really useful if you set some of `:stdin`, `:stdout`, and
|
362
418
|
# `:stderr` to {Subprocess::PIPE}.
|
363
419
|
#
|
364
420
|
# @param [String] input A string to feed to the child's standard input.
|
365
|
-
# @
|
421
|
+
# @param [Numeric] timeout_s Raise {Subprocess::CommunicateTimeout} if communicate
|
422
|
+
# does not finish after timeout_s seconds.
|
423
|
+
# @yieldparam [String] stdout Data read from stdout since the last yield
|
424
|
+
# @yieldparam [String] stderr Data read from stderr since the last yield
|
425
|
+
# @return [Array(String, String), nil] An array of two elements: the data read from the
|
366
426
|
# child's standard output and standard error, respectively.
|
367
|
-
|
427
|
+
# Returns nil if a block is provided.
|
428
|
+
def communicate(input=nil, timeout_s=nil)
|
368
429
|
raise ArgumentError if !input.nil? && @stdin.nil?
|
369
430
|
|
370
431
|
stdout, stderr = "", ""
|
371
|
-
|
432
|
+
|
433
|
+
# NB: Always force encoding to binary so we handle unicode or binary input
|
434
|
+
# correctly across multiple write_nonblock calls, since we manually slice
|
435
|
+
# the input depending on how many bytes were written
|
436
|
+
input = input.dup.force_encoding('BINARY') unless input.nil?
|
372
437
|
|
373
438
|
@stdin.close if (input.nil? || input.empty?) && !@stdin.nil?
|
374
439
|
|
375
|
-
|
376
|
-
|
377
|
-
|
440
|
+
timeout_at = Time.now + timeout_s if timeout_s
|
441
|
+
|
442
|
+
self.class.catching_sigchld(pid) do |global_read, self_read|
|
443
|
+
wait_r = [@stdout, @stderr, self_read, global_read].compact
|
378
444
|
wait_w = [input && @stdin].compact
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
poll
|
445
|
+
done = false
|
446
|
+
while !done
|
447
|
+
# If the process has exited, we want to drain any remaining output before returning
|
448
|
+
if poll
|
449
|
+
ready_r = wait_r - [self_read, global_read]
|
450
|
+
ready_w = []
|
451
|
+
done = true
|
452
|
+
else
|
453
|
+
ready_r, ready_w = select_until(wait_r, wait_w, [], timeout_at)
|
454
|
+
raise CommunicateTimeout.new(@command, stdout, stderr) if ready_r.nil?
|
455
|
+
end
|
391
456
|
|
392
457
|
if ready_r.include?(@stdout)
|
393
458
|
if drain_fd(@stdout, stdout)
|
@@ -401,6 +466,13 @@ module Subprocess
|
|
401
466
|
end
|
402
467
|
end
|
403
468
|
|
469
|
+
if ready_r.include?(global_read)
|
470
|
+
if drain_fd(global_read)
|
471
|
+
raise "Unexpected internal error -- someone closed the global self-pipe!"
|
472
|
+
end
|
473
|
+
self.class.wakeup_sigchld
|
474
|
+
end
|
475
|
+
|
404
476
|
if ready_r.include?(self_read)
|
405
477
|
if drain_fd(self_read)
|
406
478
|
raise "Unexpected internal error -- someone closed our self-pipe!"
|
@@ -408,10 +480,24 @@ module Subprocess
|
|
408
480
|
end
|
409
481
|
|
410
482
|
if ready_w.include?(@stdin)
|
483
|
+
written = 0
|
411
484
|
begin
|
412
485
|
written = @stdin.write_nonblock(input)
|
413
486
|
rescue EOFError # Maybe I shouldn't catch this...
|
414
487
|
rescue Errno::EINTR
|
488
|
+
rescue IO::WaitWritable
|
489
|
+
# On OS X, a pipe can raise EAGAIN even after select indicates
|
490
|
+
# that it is writable. Once the process consumes from the pipe,
|
491
|
+
# the next write should succeed and we should make forward progress.
|
492
|
+
# Until then, treat this as not writing any bytes and continue looping.
|
493
|
+
# For details see: https://github.com/stripe/subprocess/pull/22
|
494
|
+
nil
|
495
|
+
rescue Errno::EPIPE
|
496
|
+
# The other side of the pipe closed before we could
|
497
|
+
# write all of our input. This can happen if the
|
498
|
+
# process exits prematurely.
|
499
|
+
@stdin.close
|
500
|
+
wait_w.delete(@stdin)
|
415
501
|
end
|
416
502
|
input[0...written] = ''
|
417
503
|
if input.empty?
|
@@ -420,29 +506,41 @@ module Subprocess
|
|
420
506
|
end
|
421
507
|
end
|
422
508
|
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
509
|
+
if block_given? && !(stderr.empty? && stdout.empty?)
|
510
|
+
yield stdout, stderr
|
511
|
+
stdout, stderr = "", ""
|
512
|
+
end
|
427
513
|
end
|
428
514
|
end
|
429
515
|
|
430
516
|
wait
|
431
517
|
|
432
|
-
|
518
|
+
if block_given?
|
519
|
+
nil
|
520
|
+
else
|
521
|
+
[stdout, stderr]
|
522
|
+
end
|
433
523
|
end
|
434
524
|
|
435
525
|
# Does exactly what it says on the box.
|
436
526
|
#
|
437
|
-
# @param [String, Symbol,
|
527
|
+
# @param [String, Symbol, Integer] signal The signal to send to the child
|
438
528
|
# process. Accepts all the same arguments as Ruby's built-in
|
439
529
|
# {::Process::kill}, for instance a string like "INT" or "SIGINT", or a
|
440
530
|
# signal number like 2.
|
531
|
+
#
|
532
|
+
# @return [Integer] See {::Process.kill}
|
533
|
+
#
|
534
|
+
# @see ::Process.kill
|
441
535
|
def send_signal(signal)
|
442
536
|
::Process.kill(signal, pid)
|
443
537
|
end
|
444
538
|
|
445
539
|
# Sends `SIGTERM` to the process.
|
540
|
+
#
|
541
|
+
# @return [Integer] See {send_signal}
|
542
|
+
#
|
543
|
+
# @see send_signal
|
446
544
|
def terminate
|
447
545
|
send_signal("TERM")
|
448
546
|
end
|
@@ -452,6 +550,10 @@ module Subprocess
|
|
452
550
|
# descriptor should appear to the child and to this process, respectively.
|
453
551
|
# "mine" is only non-nil in the case of a pipe (in fact, we just return a
|
454
552
|
# list of length one, since ruby will unpack nils from missing list items).
|
553
|
+
#
|
554
|
+
# @param [IO, Integer, String, nil] fd
|
555
|
+
# @param [String] mode
|
556
|
+
# @return [Array<IO>]
|
455
557
|
def parse_fd(fd, mode)
|
456
558
|
fds = case fd
|
457
559
|
when PIPE
|
@@ -473,6 +575,9 @@ module Subprocess
|
|
473
575
|
|
474
576
|
# The pair to parse_fd, returns whether or not the file descriptor was
|
475
577
|
# opened by us (and therefore should be closed by us).
|
578
|
+
#
|
579
|
+
# @param [IO, Integer, String, nil] fd
|
580
|
+
# @return [Boolean]
|
476
581
|
def our_fd?(fd)
|
477
582
|
case fd
|
478
583
|
when PIPE, String
|
@@ -482,32 +587,92 @@ module Subprocess
|
|
482
587
|
end
|
483
588
|
end
|
484
589
|
|
590
|
+
# Call IO.select timing out at Time `timeout_at`. If `timeout_at` is nil, never times out.
|
591
|
+
#
|
592
|
+
# @param [Array<IO>, nil] read_array
|
593
|
+
# @param [Array<IO>, nil] write_array
|
594
|
+
# @param [Array<IO>, nil] err_array
|
595
|
+
# @param [Integer, Float, nil] timeout_at
|
596
|
+
# @return [Array<Array<IO>>, nil]
|
597
|
+
def select_until(read_array, write_array, err_array, timeout_at)
|
598
|
+
if !timeout_at
|
599
|
+
return IO.select(read_array, write_array, err_array)
|
600
|
+
end
|
601
|
+
|
602
|
+
remaining = (timeout_at - Time.now)
|
603
|
+
return nil if remaining <= 0
|
604
|
+
|
605
|
+
IO.select(read_array, write_array, err_array, remaining)
|
606
|
+
end
|
607
|
+
|
485
608
|
@sigchld_mutex = Mutex.new
|
486
609
|
@sigchld_fds = {}
|
487
610
|
@sigchld_old_handler = nil
|
611
|
+
@sigchld_global_write = nil
|
612
|
+
@sigchld_global_read = nil
|
613
|
+
@sigchld_pipe_pid = nil
|
614
|
+
|
615
|
+
# @return [void]
|
616
|
+
def self.handle_sigchld
|
617
|
+
# We'd like to just notify everything in `@sigchld_fds`, but
|
618
|
+
# ruby signal handlers are not executed atomically with respect
|
619
|
+
# to other Ruby threads, so reading it is racy. We can't grab
|
620
|
+
# `@sigchld_mutex`, because signal execution blocks the main
|
621
|
+
# thread, and so we'd deadlock if the main thread currently
|
622
|
+
# holds it.
|
623
|
+
#
|
624
|
+
# Instead, we keep a long-lived notify self-pipe that we select
|
625
|
+
# on inside `communicate`, and we task `communicate` with
|
626
|
+
# grabbing the lock and fanning out the wakeups.
|
627
|
+
begin
|
628
|
+
@sigchld_global_write.write_nonblock("\x00")
|
629
|
+
rescue Errno::EWOULDBLOCK, Errno::EAGAIN
|
630
|
+
nil # ignore
|
631
|
+
end
|
632
|
+
end
|
488
633
|
|
489
634
|
# Wake up everyone. We can't tell who we should wake up without `wait`ing,
|
490
635
|
# and we want to let the process itself do that. In practice, we're not
|
491
636
|
# likely to have that many in-flight subprocesses, so this is probably not a
|
492
637
|
# big deal.
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
638
|
+
# @return [void]
|
639
|
+
def self.wakeup_sigchld
|
640
|
+
@sigchld_mutex.synchronize do
|
641
|
+
@sigchld_fds.values.each do |fd|
|
642
|
+
begin
|
643
|
+
fd.write_nonblock("\x00")
|
644
|
+
rescue Errno::EWOULDBLOCK, Errno::EAGAIN
|
645
|
+
# If the pipe is full, the other end will be woken up
|
646
|
+
# regardless when it next reads, so it's fine to skip the
|
647
|
+
# write (the pipe is a wakeup channel, and doesn't contain
|
648
|
+
# meaningful data).
|
649
|
+
end
|
498
650
|
end
|
499
651
|
end
|
500
652
|
end
|
501
653
|
|
654
|
+
# @param [Integer] pid
|
655
|
+
# @param [IO] fd
|
656
|
+
# @return [void]
|
502
657
|
def self.register_pid(pid, fd)
|
503
658
|
@sigchld_mutex.synchronize do
|
504
659
|
@sigchld_fds[pid] = fd
|
505
660
|
if @sigchld_fds.length == 1
|
661
|
+
if @sigchld_global_write.nil? || @sigchld_pipe_pid != ::Process.pid
|
662
|
+
# Check the PID so that if we fork we will re-open the
|
663
|
+
# pipe. It's important that a fork parent and child don't
|
664
|
+
# share this pipe, because if they do they risk stealing
|
665
|
+
# each others' wakeups.
|
666
|
+
@sigchld_pipe_pid = ::Process.pid
|
667
|
+
@sigchld_global_read, @sigchld_global_write = IO.pipe
|
668
|
+
end
|
506
669
|
@sigchld_old_handler = Signal.trap('SIGCHLD') {handle_sigchld}
|
507
670
|
end
|
508
671
|
end
|
509
672
|
end
|
510
673
|
|
674
|
+
# @param [Integer] pid
|
675
|
+
# @return [void]
|
511
676
|
def self.unregister_pid(pid)
|
512
677
|
@sigchld_mutex.synchronize do
|
513
678
|
if @sigchld_fds.length == 1
|
@@ -517,11 +682,17 @@ module Subprocess
|
|
517
682
|
end
|
518
683
|
end
|
519
684
|
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
685
|
+
# @param [Integer] pid
|
686
|
+
# @return [void]
|
687
|
+
def self.catching_sigchld(pid)
|
688
|
+
IO.pipe do |self_read, self_write|
|
689
|
+
begin
|
690
|
+
register_pid(pid, self_write)
|
691
|
+
yield @sigchld_global_read, self_read
|
692
|
+
ensure
|
693
|
+
unregister_pid(pid)
|
694
|
+
end
|
695
|
+
end
|
525
696
|
end
|
526
697
|
end
|
527
698
|
end
|
data/lib/subprocess/version.rb
CHANGED
data/rbi/subprocess.rbi
ADDED
@@ -0,0 +1,312 @@
|
|
1
|
+
# typed: strong
|
2
|
+
|
3
|
+
# THIS FILE IS AUTOGENERATED. DO NOT EDIT.
|
4
|
+
# To regenerate from YARD comments:
|
5
|
+
#
|
6
|
+
# bundle exec rake sord
|
7
|
+
#
|
8
|
+
# A Ruby clone of Python's subprocess module.
|
9
|
+
#
|
10
|
+
# @see http://docs.python.org/2/library/subprocess.html
|
11
|
+
module ::Subprocess
|
12
|
+
PIPE = T.let(-1, T.untyped)
|
13
|
+
STDOUT = T.let(-2, T.untyped)
|
14
|
+
VERSION = T.let('1.5.5', T.untyped)
|
15
|
+
|
16
|
+
# An alias for `Process.new`. Mostly here to better emulate the Python API.
|
17
|
+
#
|
18
|
+
# _@param_ `cmd` — See {Process#initialize}
|
19
|
+
#
|
20
|
+
# _@param_ `opts` — See {Process#initialize}
|
21
|
+
#
|
22
|
+
# _@return_ — A process with the given arguments
|
23
|
+
#
|
24
|
+
# _@see_ `Process#initialize`
|
25
|
+
sig { params(cmd: T::Array[String], opts: T::Hash[T.untyped, T.untyped], blk: T.nilable(T.proc.params(process: Process).void)).returns(Process) }
|
26
|
+
def self.popen(cmd, opts = {}, &blk); end
|
27
|
+
|
28
|
+
# Call and wait for the return of a given process.
|
29
|
+
#
|
30
|
+
# _@param_ `cmd` — See {Process#initialize}
|
31
|
+
#
|
32
|
+
# _@param_ `opts` — See {Process#initialize}
|
33
|
+
#
|
34
|
+
# _@return_ — The exit status of the process
|
35
|
+
#
|
36
|
+
# _@note_ — If you call this function with `:stdout => PIPE` or `:stderr => PIPE`,
|
37
|
+
# this function will block indefinitely as soon as the OS's pipe buffer
|
38
|
+
# fills up, as neither file descriptor will be read from. To avoid this, use
|
39
|
+
# {Process#communicate} from a passed block.
|
40
|
+
#
|
41
|
+
# _@see_ `Process#initialize`
|
42
|
+
sig { params(cmd: T::Array[String], opts: T::Hash[T.untyped, T.untyped], blk: T.nilable(T.proc.params(process: Process).void)).returns(::Process::Status) }
|
43
|
+
def self.call(cmd, opts = {}, &blk); end
|
44
|
+
|
45
|
+
# Like {Subprocess::call}, except raise a {NonZeroExit} if the process did not
|
46
|
+
# terminate successfully.
|
47
|
+
#
|
48
|
+
# _@param_ `cmd` — See {Process#initialize}
|
49
|
+
#
|
50
|
+
# _@param_ `opts` — See {Process#initialize}
|
51
|
+
#
|
52
|
+
# _@return_ — The exit status of the process
|
53
|
+
#
|
54
|
+
# Grep a file for a string
|
55
|
+
# ```ruby
|
56
|
+
# Subprocess.check_call(%W{grep -q llama ~/favorite_animals})
|
57
|
+
# ```
|
58
|
+
#
|
59
|
+
# Communicate with a child process
|
60
|
+
# ```ruby
|
61
|
+
# Subprocess.check_call(%W{sendmail -t}, :stdin => Subprocess::PIPE) do |p|
|
62
|
+
# p.communicate <<-EMAIL
|
63
|
+
# From: alpaca@example.com
|
64
|
+
# To: llama@example.com
|
65
|
+
# Subject: I am so fluffy.
|
66
|
+
#
|
67
|
+
# SO FLUFFY!
|
68
|
+
# http://upload.wikimedia.org/wikipedia/commons/3/3e/Unshorn_alpaca_grazing.jpg
|
69
|
+
# EMAIL
|
70
|
+
# end
|
71
|
+
# ```
|
72
|
+
#
|
73
|
+
# _@note_ — If you call this function with `:stdout => PIPE` or `:stderr => PIPE`,
|
74
|
+
# this function will block indefinitely as soon as the OS's pipe buffer
|
75
|
+
# fills up, as neither file descriptor will be read from. To avoid this, use
|
76
|
+
# {Process#communicate} from a passed block.
|
77
|
+
#
|
78
|
+
# _@see_ `Process#initialize`
|
79
|
+
sig { params(cmd: T::Array[String], opts: T::Hash[T.untyped, T.untyped], blk: T.nilable(T.proc.params(process: Process).void)).returns(::Process::Status) }
|
80
|
+
def self.check_call(cmd, opts = {}, &blk); end
|
81
|
+
|
82
|
+
# Like {Subprocess::check_call}, but return the contents of `stdout`, much
|
83
|
+
# like {::Kernel#system}.
|
84
|
+
#
|
85
|
+
# _@param_ `cmd` — See {Process#initialize}
|
86
|
+
#
|
87
|
+
# _@param_ `opts` — See {Process#initialize}
|
88
|
+
#
|
89
|
+
# _@return_ — The contents of `stdout`
|
90
|
+
#
|
91
|
+
# Get the system load
|
92
|
+
# ```ruby
|
93
|
+
# system_load = Subprocess.check_output(['uptime']).split(' ').last(3)
|
94
|
+
# ```
|
95
|
+
#
|
96
|
+
# _@see_ `Process#initialize`
|
97
|
+
sig { params(cmd: T::Array[String], opts: T::Hash[T.untyped, T.untyped], blk: T.nilable(T.proc.params(process: Process).void)).returns(String) }
|
98
|
+
def self.check_output(cmd, opts = {}, &blk); end
|
99
|
+
|
100
|
+
# Print a human readable interpretation of a process exit status.
|
101
|
+
#
|
102
|
+
# _@param_ `status` — The status returned by `waitpid2`.
|
103
|
+
#
|
104
|
+
# _@param_ `convert_high_exit` — Whether to convert exit statuses greater than 128 into the usual convention for exiting after trapping a signal. (e.g. many programs will exit with status 130 after receiving a SIGINT / signal 2.)
|
105
|
+
#
|
106
|
+
# _@return_ — Text interpretation
|
107
|
+
sig { params(status: ::Process::Status, convert_high_exit: T::Boolean).returns(String) }
|
108
|
+
def self.status_to_s(status, convert_high_exit = true); end
|
109
|
+
|
110
|
+
# Error class representing a process's abnormal exit.
|
111
|
+
class NonZeroExit < StandardError
|
112
|
+
# Return an instance of {NonZeroExit}.
|
113
|
+
#
|
114
|
+
# _@param_ `cmd` — The command that returned a non-zero status.
|
115
|
+
#
|
116
|
+
# _@param_ `status` — The status returned by `waitpid`.
|
117
|
+
sig { params(cmd: T::Array[String], status: ::Process::Status).void }
|
118
|
+
def initialize(cmd, status); end
|
119
|
+
|
120
|
+
# _@return_ — The command and arguments for the process that exited
|
121
|
+
# abnormally.
|
122
|
+
#
|
123
|
+
# _@note_ — This is intended only for use in user-facing error messages. In
|
124
|
+
# particular, no shell quoting of any sort is performed when
|
125
|
+
# constructing this string, meaning that blindly running it in a shell
|
126
|
+
# might have different semantics than the original command.
|
127
|
+
sig { returns(String) }
|
128
|
+
attr_reader :command
|
129
|
+
|
130
|
+
# _@return_ — The Ruby status object returned by `waitpid`
|
131
|
+
sig { returns(::Process::Status) }
|
132
|
+
attr_reader :status
|
133
|
+
end
|
134
|
+
|
135
|
+
# Error class representing a timeout during a call to `communicate`
|
136
|
+
class CommunicateTimeout < StandardError
|
137
|
+
# _@param_ `cmd`
|
138
|
+
#
|
139
|
+
# _@param_ `stdout`
|
140
|
+
#
|
141
|
+
# _@param_ `stderr`
|
142
|
+
sig { params(cmd: T::Array[String], stdout: String, stderr: String).void }
|
143
|
+
def initialize(cmd, stdout, stderr); end
|
144
|
+
|
145
|
+
# _@return_ — Content read from stdout before the timeout
|
146
|
+
sig { returns(String) }
|
147
|
+
attr_reader :stdout
|
148
|
+
|
149
|
+
# _@return_ — Content read from stderr before the timeout
|
150
|
+
sig { returns(String) }
|
151
|
+
attr_reader :stderr
|
152
|
+
end
|
153
|
+
|
154
|
+
# A child process. The preferred way of spawning a subprocess is through the
|
155
|
+
# functions on {Subprocess} (especially {Subprocess::check_call} and
|
156
|
+
# {Subprocess::check_output}).
|
157
|
+
class Process
|
158
|
+
# Create a new process.
|
159
|
+
#
|
160
|
+
# _@param_ `cmd` — The command to run and its arguments (in the style of an `argv` array). Unlike Python's subprocess module, `cmd` cannot be a String.
|
161
|
+
sig { params(cmd: T::Array[String], opts: T::Hash[T.untyped, T.untyped], blk: T.nilable(T.proc.params(process: Process).void)).void }
|
162
|
+
def initialize(cmd, opts = {}, &blk); end
|
163
|
+
|
164
|
+
# Poll the child, setting (and returning) its status. If the child has not
|
165
|
+
# terminated, return nil and exit immediately
|
166
|
+
#
|
167
|
+
# _@return_ — The exit status of the process
|
168
|
+
sig { returns(T.nilable(::Process::Status)) }
|
169
|
+
def poll; end
|
170
|
+
|
171
|
+
# Wait for the child to return, setting and returning the status of the
|
172
|
+
# child.
|
173
|
+
#
|
174
|
+
# _@return_ — The exit status of the process
|
175
|
+
sig { returns(::Process::Status) }
|
176
|
+
def wait; end
|
177
|
+
|
178
|
+
# Do nonblocking reads from `fd`, appending all data read into `buf`.
|
179
|
+
#
|
180
|
+
# _@param_ `fd` — The file to read from.
|
181
|
+
#
|
182
|
+
# _@param_ `buf` — A buffer to append the read data to.
|
183
|
+
#
|
184
|
+
# _@return_ — Whether `fd` was closed due to an exceptional
|
185
|
+
# condition (`EOFError` or `EPIPE`).
|
186
|
+
sig { params(fd: IO, buf: T.nilable(String)).returns(T::Boolean) }
|
187
|
+
def drain_fd(fd, buf = nil); end
|
188
|
+
|
189
|
+
# Write the (optional) input to the process's `stdin` and read the contents of
|
190
|
+
# `stdout` and `stderr`. If a block is provided, stdout and stderr are yielded as they
|
191
|
+
# are read. Otherwise they are buffered in memory and returned when the process
|
192
|
+
# exits. Do this all using `IO::select`, so we don't deadlock due to full pipe
|
193
|
+
# buffers.
|
194
|
+
#
|
195
|
+
# This is only really useful if you set some of `:stdin`, `:stdout`, and
|
196
|
+
# `:stderr` to {Subprocess::PIPE}.
|
197
|
+
#
|
198
|
+
# _@param_ `input` — A string to feed to the child's standard input.
|
199
|
+
#
|
200
|
+
# _@param_ `timeout_s` — Raise {Subprocess::CommunicateTimeout} if communicate does not finish after timeout_s seconds.
|
201
|
+
#
|
202
|
+
# _@return_ — An array of two elements: the data read from the
|
203
|
+
# child's standard output and standard error, respectively.
|
204
|
+
# Returns nil if a block is provided.
|
205
|
+
sig { params(input: T.nilable(String), timeout_s: T.nilable(Numeric)).returns([String, String]) }
|
206
|
+
def communicate(input = nil, timeout_s = nil); end
|
207
|
+
|
208
|
+
# Does exactly what it says on the box.
|
209
|
+
#
|
210
|
+
# _@param_ `signal` — The signal to send to the child process. Accepts all the same arguments as Ruby's built-in {::Process::kill}, for instance a string like "INT" or "SIGINT", or a signal number like 2.
|
211
|
+
#
|
212
|
+
# _@return_ — See {::Process.kill}
|
213
|
+
#
|
214
|
+
# _@see_ `::Process.kill`
|
215
|
+
sig { params(signal: T.any(String, Symbol, Integer)).returns(Integer) }
|
216
|
+
def send_signal(signal); end
|
217
|
+
|
218
|
+
# Sends `SIGTERM` to the process.
|
219
|
+
#
|
220
|
+
# _@return_ — See {send_signal}
|
221
|
+
#
|
222
|
+
# _@see_ `send_signal`
|
223
|
+
sig { returns(Integer) }
|
224
|
+
def terminate; end
|
225
|
+
|
226
|
+
# Return a pair of values (child, mine), which are how the given file
|
227
|
+
# descriptor should appear to the child and to this process, respectively.
|
228
|
+
# "mine" is only non-nil in the case of a pipe (in fact, we just return a
|
229
|
+
# list of length one, since ruby will unpack nils from missing list items).
|
230
|
+
#
|
231
|
+
# _@param_ `fd`
|
232
|
+
#
|
233
|
+
# _@param_ `mode`
|
234
|
+
sig { params(fd: T.nilable(T.any(IO, Integer, String)), mode: String).returns(T::Array[IO]) }
|
235
|
+
def parse_fd(fd, mode); end
|
236
|
+
|
237
|
+
# The pair to parse_fd, returns whether or not the file descriptor was
|
238
|
+
# opened by us (and therefore should be closed by us).
|
239
|
+
#
|
240
|
+
# _@param_ `fd`
|
241
|
+
sig { params(fd: T.nilable(T.any(IO, Integer, String))).returns(T::Boolean) }
|
242
|
+
def our_fd?(fd); end
|
243
|
+
|
244
|
+
# Call IO.select timing out at Time `timeout_at`. If `timeout_at` is nil, never times out.
|
245
|
+
#
|
246
|
+
# _@param_ `read_array`
|
247
|
+
#
|
248
|
+
# _@param_ `write_array`
|
249
|
+
#
|
250
|
+
# _@param_ `err_array`
|
251
|
+
#
|
252
|
+
# _@param_ `timeout_at`
|
253
|
+
sig do
|
254
|
+
params(
|
255
|
+
read_array: T.nilable(T::Array[IO]),
|
256
|
+
write_array: T.nilable(T::Array[IO]),
|
257
|
+
err_array: T.nilable(T::Array[IO]),
|
258
|
+
timeout_at: T.nilable(T.any(Integer, Float))
|
259
|
+
).returns(T.nilable(T::Array[T::Array[IO]]))
|
260
|
+
end
|
261
|
+
def select_until(read_array, write_array, err_array, timeout_at); end
|
262
|
+
|
263
|
+
sig { void }
|
264
|
+
def self.handle_sigchld; end
|
265
|
+
|
266
|
+
# Wake up everyone. We can't tell who we should wake up without `wait`ing,
|
267
|
+
# and we want to let the process itself do that. In practice, we're not
|
268
|
+
# likely to have that many in-flight subprocesses, so this is probably not a
|
269
|
+
# big deal.
|
270
|
+
sig { void }
|
271
|
+
def self.wakeup_sigchld; end
|
272
|
+
|
273
|
+
# _@param_ `pid`
|
274
|
+
#
|
275
|
+
# _@param_ `fd`
|
276
|
+
sig { params(pid: Integer, fd: IO).void }
|
277
|
+
def self.register_pid(pid, fd); end
|
278
|
+
|
279
|
+
# _@param_ `pid`
|
280
|
+
sig { params(pid: Integer).void }
|
281
|
+
def self.unregister_pid(pid); end
|
282
|
+
|
283
|
+
# _@param_ `pid`
|
284
|
+
sig { params(pid: Integer).void }
|
285
|
+
def self.catching_sigchld(pid); end
|
286
|
+
|
287
|
+
# _@return_ — The `IO` that is connected to this process's `stdin`.
|
288
|
+
sig { returns(IO) }
|
289
|
+
attr_reader :stdin
|
290
|
+
|
291
|
+
# _@return_ — The `IO` that is connected to this process's `stdout`.
|
292
|
+
sig { returns(IO) }
|
293
|
+
attr_reader :stdout
|
294
|
+
|
295
|
+
# _@return_ — The `IO` that is connected to this process's `stderr`.
|
296
|
+
sig { returns(IO) }
|
297
|
+
attr_reader :stderr
|
298
|
+
|
299
|
+
# _@return_ — The command this process was invoked with.
|
300
|
+
sig { returns(T::Array[String]) }
|
301
|
+
attr_reader :command
|
302
|
+
|
303
|
+
# _@return_ — The process ID of the spawned process.
|
304
|
+
sig { returns(Integer) }
|
305
|
+
attr_reader :pid
|
306
|
+
|
307
|
+
# _@return_ — The exit status code of the process.
|
308
|
+
# Only set after the process has exited.
|
309
|
+
sig { returns(T.nilable(::Process::Status)) }
|
310
|
+
attr_reader :status
|
311
|
+
end
|
312
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: subprocess
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.5.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Carl Jackson
|
@@ -12,7 +12,7 @@ authors:
|
|
12
12
|
autorequire:
|
13
13
|
bindir: bin
|
14
14
|
cert_chain: []
|
15
|
-
date:
|
15
|
+
date: 2021-07-15 00:00:00.000000000 Z
|
16
16
|
dependencies:
|
17
17
|
- !ruby/object:Gem::Dependency
|
18
18
|
name: minitest
|
@@ -56,6 +56,20 @@ dependencies:
|
|
56
56
|
- - ">="
|
57
57
|
- !ruby/object:Gem::Version
|
58
58
|
version: '0'
|
59
|
+
- !ruby/object:Gem::Dependency
|
60
|
+
name: sord
|
61
|
+
requirement: !ruby/object:Gem::Requirement
|
62
|
+
requirements:
|
63
|
+
- - ">="
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
66
|
+
type: :development
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: !ruby/object:Gem::Requirement
|
69
|
+
requirements:
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: '0'
|
59
73
|
description: Control and communicate with spawned processes
|
60
74
|
email:
|
61
75
|
- carl@stripe.com
|
@@ -70,6 +84,7 @@ files:
|
|
70
84
|
- README.md
|
71
85
|
- lib/subprocess.rb
|
72
86
|
- lib/subprocess/version.rb
|
87
|
+
- rbi/subprocess.rbi
|
73
88
|
homepage: https://github.com/stripe/subprocess
|
74
89
|
licenses:
|
75
90
|
- MIT
|
@@ -89,10 +104,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
89
104
|
- !ruby/object:Gem::Version
|
90
105
|
version: '0'
|
91
106
|
requirements: []
|
92
|
-
|
93
|
-
rubygems_version: 2.2.2
|
107
|
+
rubygems_version: 3.1.2
|
94
108
|
signing_key:
|
95
109
|
specification_version: 4
|
96
110
|
summary: A port of Python's subprocess module to Ruby
|
97
111
|
test_files: []
|
98
|
-
has_rdoc:
|