subprocess 1.3.0 → 1.5.5
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 +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  [](http://rubydoc.info/github/stripe/subprocess/Subprocess)
|
|
2
2
|
|
|
3
3
|

|
|
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:
|