ruby-sh 2.2.6 → 3.0.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 +4 -4
- data/.rubocop.yml +2 -3
- data/.ruby-version +1 -1
- data/Gemfile +6 -8
- data/Gemfile.lock +131 -75
- data/README.md +118 -152
- data/lib/rubsh/command.rb +5 -8
- data/lib/rubsh/running_command.rb +66 -108
- data/lib/rubsh/version.rb +1 -1
- data/lib/rubsh.rb +2 -6
- data/rubsh.gemspec +3 -3
- metadata +7 -14
- data/.overcommit.yml +0 -26
- data/lib/rubsh/running_pipeline.rb +0 -268
- data/lib/rubsh/shell/env.rb +0 -15
- data/lib/rubsh/shell.rb +0 -24
@@ -18,15 +18,6 @@ module Rubsh
|
|
18
18
|
_no_err
|
19
19
|
_long_sep
|
20
20
|
_long_prefix
|
21
|
-
_pipeline
|
22
|
-
]
|
23
|
-
|
24
|
-
SPECIAL_KWARGS_WITHIN_PIPELINE = %i[
|
25
|
-
_env
|
26
|
-
_cwd
|
27
|
-
_long_sep
|
28
|
-
_long_prefix
|
29
|
-
_pipeline
|
30
21
|
]
|
31
22
|
|
32
23
|
# @!attribute [r] pid
|
@@ -53,8 +44,7 @@ module Rubsh
|
|
53
44
|
# @return [String]
|
54
45
|
attr_reader :stderr_data
|
55
46
|
|
56
|
-
def initialize(
|
57
|
-
@sh = sh
|
47
|
+
def initialize(prog, progpath, *args, **kwargs)
|
58
48
|
@prog = prog
|
59
49
|
@progpath = progpath
|
60
50
|
@args = []
|
@@ -101,9 +91,6 @@ module Rubsh
|
|
101
91
|
@_long_sep = "="
|
102
92
|
@_long_prefix = "--"
|
103
93
|
|
104
|
-
# Special Kwargs - Misc
|
105
|
-
@_pipeline = nil
|
106
|
-
|
107
94
|
opts = []
|
108
95
|
args.each do |arg|
|
109
96
|
if arg.is_a?(::Hash)
|
@@ -113,7 +100,6 @@ module Rubsh
|
|
113
100
|
end
|
114
101
|
end
|
115
102
|
kwargs.each { |k, v| opts << Option.build(k, v) }
|
116
|
-
validate_opts(opts)
|
117
103
|
extract_opts(opts)
|
118
104
|
end
|
119
105
|
|
@@ -128,61 +114,29 @@ module Rubsh
|
|
128
114
|
@_ok_code.include?(@exit_code)
|
129
115
|
end
|
130
116
|
|
131
|
-
# @return [void]
|
132
|
-
def wait(timeout: nil)
|
133
|
-
wait2(timeout: timeout)
|
134
|
-
handle_return_code
|
135
|
-
end
|
136
|
-
|
137
|
-
# @return [String]
|
138
|
-
def to_s
|
139
|
-
@prog_with_args
|
140
|
-
end
|
141
|
-
|
142
117
|
# @!visibility private
|
143
118
|
def __run
|
144
|
-
if @
|
145
|
-
|
119
|
+
if @_bg
|
120
|
+
spawn
|
146
121
|
else
|
147
|
-
|
122
|
+
spawn
|
123
|
+
wait(timeout: @_timeout)
|
148
124
|
end
|
149
125
|
end
|
150
126
|
|
151
|
-
#
|
152
|
-
def
|
153
|
-
|
154
|
-
|
155
|
-
redirection_args ||= compile_redirection_args
|
156
|
-
extra_args = compile_extra_args(cwd: cwd)
|
157
|
-
|
158
|
-
# For logging
|
159
|
-
@prog_with_args = [@progpath].concat(cmd_args).join(" ")
|
160
|
-
|
161
|
-
# .
|
162
|
-
_args =
|
163
|
-
if env
|
164
|
-
[env, [@progpath, @prog], *cmd_args, **redirection_args, **extra_args, unsetenv_others: true]
|
165
|
-
else
|
166
|
-
[[@progpath, @prog], *cmd_args, **redirection_args, **extra_args]
|
167
|
-
end
|
127
|
+
# @return [void]
|
128
|
+
def wait(timeout: nil)
|
129
|
+
wait2(@pid, timeout)
|
130
|
+
handle_return_code
|
168
131
|
end
|
169
132
|
|
170
|
-
#
|
171
|
-
def
|
133
|
+
# @return [String]
|
134
|
+
def to_s
|
172
135
|
@prog_with_args
|
173
136
|
end
|
174
137
|
|
175
138
|
private
|
176
139
|
|
177
|
-
def validate_opts(opts)
|
178
|
-
within_pipeline = opts.any? { |opt| opt.special_kwarg?(:_pipeline) }
|
179
|
-
within_pipeline && opts.each do |opt|
|
180
|
-
if opt.special_kwarg? && !SPECIAL_KWARGS_WITHIN_PIPELINE.include?(opt.k.to_sym)
|
181
|
-
raise ::ArgumentError, format("unsupported special kwargs within _pipeline `%s'", opt.k)
|
182
|
-
end
|
183
|
-
end
|
184
|
-
end
|
185
|
-
|
186
140
|
def extract_opts(opts)
|
187
141
|
args_hash = {}
|
188
142
|
opts.each do |opt|
|
@@ -200,6 +154,8 @@ module Rubsh
|
|
200
154
|
@args << arg
|
201
155
|
end
|
202
156
|
end
|
157
|
+
|
158
|
+
raise ::ArgumentError, "kwargs :_bg conflicts with kwargs :_timeout" if @_bg && @_timeout
|
203
159
|
end
|
204
160
|
|
205
161
|
def extract_special_kwargs_opts(opt)
|
@@ -238,11 +194,52 @@ module Rubsh
|
|
238
194
|
@_long_sep = opt.v
|
239
195
|
when :_long_prefix
|
240
196
|
@_long_prefix = opt.v
|
241
|
-
when :_pipeline
|
242
|
-
@_pipeline = opt.v
|
243
197
|
end
|
244
198
|
end
|
245
199
|
|
200
|
+
def spawn
|
201
|
+
args = spawn_arguments
|
202
|
+
@pid = ::Process.spawn(*args)
|
203
|
+
@started_at = Time.now
|
204
|
+
@in_wr&.write(@_in_data) if @_in_data
|
205
|
+
@in_wr&.close
|
206
|
+
|
207
|
+
if @out_rd
|
208
|
+
@out_rd_reader = StreamReader.new(@out_rd, bufsize: @_capture ? @_out_bufsize : nil, &proc { |chunk|
|
209
|
+
@stdout_data << chunk unless @_no_out
|
210
|
+
@_capture&.call(chunk, nil)
|
211
|
+
})
|
212
|
+
end
|
213
|
+
if @err_rd
|
214
|
+
@err_rd_reader = StreamReader.new(@err_rd, bufsize: @_capture ? @_err_bufsize : nil, &proc { |chunk|
|
215
|
+
@stderr_data << chunk unless @_no_err
|
216
|
+
@_capture&.call(nil, chunk)
|
217
|
+
})
|
218
|
+
end
|
219
|
+
ensure
|
220
|
+
@in_rd&.close
|
221
|
+
@out_wr&.close
|
222
|
+
@err_wr&.close
|
223
|
+
end
|
224
|
+
|
225
|
+
def spawn_arguments
|
226
|
+
env = @_env
|
227
|
+
cmd_args = compile_cmd_args
|
228
|
+
redirection_args = compile_redirection_args
|
229
|
+
extra_args = compile_extra_args
|
230
|
+
|
231
|
+
# For logging
|
232
|
+
@prog_with_args = [@progpath].concat(cmd_args).join(" ")
|
233
|
+
|
234
|
+
# .
|
235
|
+
_args =
|
236
|
+
if env
|
237
|
+
[env, [@progpath, @prog], *cmd_args, **redirection_args, **extra_args, unsetenv_others: true]
|
238
|
+
else
|
239
|
+
[[@progpath, @prog], *cmd_args, **redirection_args, **extra_args]
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
246
243
|
def compile_cmd_args
|
247
244
|
@args.map { |arg| arg.compile(long_sep: @_long_sep, long_prefix: @_long_prefix) }.compact.flatten
|
248
245
|
end
|
@@ -277,67 +274,38 @@ module Rubsh
|
|
277
274
|
args
|
278
275
|
end
|
279
276
|
|
280
|
-
def compile_extra_args
|
281
|
-
chdir = cwd || @_cwd
|
282
|
-
|
277
|
+
def compile_extra_args
|
283
278
|
args = {}
|
284
|
-
args[:chdir] =
|
279
|
+
args[:chdir] = @_cwd if @_cwd
|
285
280
|
args
|
286
281
|
end
|
287
282
|
|
288
|
-
def
|
289
|
-
args = __spawn_arguments
|
290
|
-
@pid = ::Process.spawn(*args)
|
291
|
-
@started_at = Time.now
|
292
|
-
@in_wr&.write(@_in_data) if @_in_data
|
293
|
-
@in_wr&.close
|
294
|
-
|
295
|
-
if @out_rd
|
296
|
-
@out_rd_reader = StreamReader.new(@out_rd, bufsize: @_capture ? @_out_bufsize : nil, &proc { |chunk|
|
297
|
-
@stdout_data << chunk unless @_no_out
|
298
|
-
@_capture&.call(chunk, nil)
|
299
|
-
})
|
300
|
-
end
|
301
|
-
if @err_rd
|
302
|
-
@err_rd_reader = StreamReader.new(@err_rd, bufsize: @_capture ? @_err_bufsize : nil, &proc { |chunk|
|
303
|
-
@stderr_data << chunk unless @_no_err
|
304
|
-
@_capture&.call(nil, chunk)
|
305
|
-
})
|
306
|
-
end
|
307
|
-
ensure
|
308
|
-
@in_rd&.close
|
309
|
-
@out_wr&.close
|
310
|
-
@err_wr&.close
|
311
|
-
end
|
312
|
-
|
313
|
-
def wait2(timeout: nil)
|
283
|
+
def wait2(pid, timeout)
|
314
284
|
timeout_occurred = false
|
315
285
|
_, status = nil, nil
|
316
286
|
|
317
287
|
if timeout
|
318
288
|
begin
|
319
|
-
::Timeout.timeout(timeout) { _, status = ::Process.wait2(
|
289
|
+
::Timeout.timeout(timeout) { _, status = ::Process.wait2(pid) }
|
320
290
|
rescue ::Timeout::Error
|
321
291
|
timeout_occurred = true
|
322
292
|
|
323
|
-
::Process.kill("TERM",
|
293
|
+
::Process.kill("TERM", pid) # graceful stop
|
324
294
|
30.times do
|
325
|
-
_, status = ::Process.wait2(
|
295
|
+
_, status = ::Process.wait2(pid, ::Process::WNOHANG | ::Process::WUNTRACED)
|
326
296
|
break if status
|
327
297
|
sleep 0.1
|
328
298
|
end
|
329
|
-
failure =
|
299
|
+
failure = pid if status.nil?
|
330
300
|
failure && ::Process.kill("KILL", failure) # forceful stop
|
331
301
|
end
|
332
302
|
else
|
333
|
-
_, status = ::Process.wait2(
|
303
|
+
_, status = ::Process.wait2(pid)
|
334
304
|
end
|
335
305
|
|
336
|
-
@exit_code = status&.exitstatus
|
306
|
+
@exit_code = status&.exitstatus || -1
|
337
307
|
@finished_at = Time.now
|
338
308
|
raise Exceptions::CommandTimeoutError, "execution expired" if timeout_occurred
|
339
|
-
rescue Errno::ECHILD, Errno::ESRCH
|
340
|
-
raise Exceptions::CommandTimeoutError, "execution expired" if timeout_occurred
|
341
309
|
ensure
|
342
310
|
@out_rd_reader&.wait
|
343
311
|
@err_rd_reader&.wait
|
@@ -345,18 +313,8 @@ module Rubsh
|
|
345
313
|
|
346
314
|
def handle_return_code
|
347
315
|
return if ok?
|
348
|
-
message = format("\n\n
|
316
|
+
message = format("\n\n $?: %s\n\n RAN: %s\n\n STDOUT:\n%s\n\n STDERR:\n%s\n", @exit_code, @prog_with_args, @stdout_data, @stderr_data)
|
349
317
|
raise Exceptions::CommandReturnFailureError.new(@exit_code, message)
|
350
318
|
end
|
351
|
-
|
352
|
-
def run_in_background
|
353
|
-
spawn
|
354
|
-
Process.detach(@pid)
|
355
|
-
end
|
356
|
-
|
357
|
-
def run_in_foreground
|
358
|
-
spawn
|
359
|
-
wait(timeout: @_timeout)
|
360
|
-
end
|
361
319
|
end
|
362
320
|
end
|
data/lib/rubsh/version.rb
CHANGED
data/lib/rubsh.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "open3"
|
4
3
|
require "timeout"
|
5
4
|
|
6
5
|
require_relative "rubsh/argument"
|
@@ -8,14 +7,11 @@ require_relative "rubsh/command"
|
|
8
7
|
require_relative "rubsh/exceptions"
|
9
8
|
require_relative "rubsh/option"
|
10
9
|
require_relative "rubsh/running_command"
|
11
|
-
require_relative "rubsh/running_pipeline"
|
12
|
-
require_relative "rubsh/shell/env"
|
13
|
-
require_relative "rubsh/shell"
|
14
10
|
require_relative "rubsh/stream_reader"
|
15
11
|
require_relative "rubsh/version"
|
16
12
|
|
17
13
|
module Rubsh
|
18
|
-
def self.
|
19
|
-
|
14
|
+
def self.cmd(prog)
|
15
|
+
Command.new(prog)
|
20
16
|
end
|
21
17
|
end
|
data/rubsh.gemspec
CHANGED
@@ -10,13 +10,13 @@ Gem::Specification.new do |spec|
|
|
10
10
|
|
11
11
|
spec.summary = "Rubsh (a.k.a. ruby-sh) - Inspired by python-sh, allows you to call any program as if it were a function."
|
12
12
|
spec.description = "Rubsh (a.k.a. ruby-sh) - Inspired by python-sh, allows you to call any program as if it were a function."
|
13
|
-
spec.homepage = "https://github.com/souk4711/
|
13
|
+
spec.homepage = "https://github.com/souk4711/ruby-sh"
|
14
14
|
spec.license = "MIT"
|
15
15
|
spec.required_ruby_version = ">= 2.7.0"
|
16
16
|
|
17
17
|
spec.metadata["homepage_uri"] = spec.homepage
|
18
|
-
spec.metadata["source_code_uri"] = "https://github.com/souk4711/
|
19
|
-
spec.metadata["changelog_uri"] = "https://github.com/souk4711/
|
18
|
+
spec.metadata["source_code_uri"] = "https://github.com/souk4711/ruby-sh"
|
19
|
+
spec.metadata["changelog_uri"] = "https://github.com/souk4711/ruby-sh"
|
20
20
|
|
21
21
|
# Specify which files should be added to the gem when it is released.
|
22
22
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby-sh
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 3.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- John Doe
|
8
|
-
autorequire:
|
9
8
|
bindir: exe
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
12
11
|
dependencies: []
|
13
12
|
description: Rubsh (a.k.a. ruby-sh) - Inspired by python-sh, allows you to call any
|
14
13
|
program as if it were a function.
|
@@ -19,7 +18,6 @@ extensions: []
|
|
19
18
|
extra_rdoc_files: []
|
20
19
|
files:
|
21
20
|
- ".editorconfig"
|
22
|
-
- ".overcommit.yml"
|
23
21
|
- ".rspec"
|
24
22
|
- ".rubocop.yml"
|
25
23
|
- ".ruby-version"
|
@@ -36,21 +34,17 @@ files:
|
|
36
34
|
- lib/rubsh/exceptions.rb
|
37
35
|
- lib/rubsh/option.rb
|
38
36
|
- lib/rubsh/running_command.rb
|
39
|
-
- lib/rubsh/running_pipeline.rb
|
40
|
-
- lib/rubsh/shell.rb
|
41
|
-
- lib/rubsh/shell/env.rb
|
42
37
|
- lib/rubsh/stream_reader.rb
|
43
38
|
- lib/rubsh/version.rb
|
44
39
|
- rubsh.gemspec
|
45
|
-
homepage: https://github.com/souk4711/
|
40
|
+
homepage: https://github.com/souk4711/ruby-sh
|
46
41
|
licenses:
|
47
42
|
- MIT
|
48
43
|
metadata:
|
49
|
-
homepage_uri: https://github.com/souk4711/
|
50
|
-
source_code_uri: https://github.com/souk4711/
|
51
|
-
changelog_uri: https://github.com/souk4711/
|
44
|
+
homepage_uri: https://github.com/souk4711/ruby-sh
|
45
|
+
source_code_uri: https://github.com/souk4711/ruby-sh
|
46
|
+
changelog_uri: https://github.com/souk4711/ruby-sh
|
52
47
|
rubygems_mfa_required: 'true'
|
53
|
-
post_install_message:
|
54
48
|
rdoc_options: []
|
55
49
|
require_paths:
|
56
50
|
- lib
|
@@ -65,8 +59,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
65
59
|
- !ruby/object:Gem::Version
|
66
60
|
version: '0'
|
67
61
|
requirements: []
|
68
|
-
rubygems_version: 3.
|
69
|
-
signing_key:
|
62
|
+
rubygems_version: 3.6.9
|
70
63
|
specification_version: 4
|
71
64
|
summary: Rubsh (a.k.a. ruby-sh) - Inspired by python-sh, allows you to call any program
|
72
65
|
as if it were a function.
|
data/.overcommit.yml
DELETED
@@ -1,26 +0,0 @@
|
|
1
|
-
# Use this file to configure the Overcommit hooks you wish to use. This will
|
2
|
-
# extend the default configuration defined in:
|
3
|
-
# https://github.com/sds/overcommit/blob/master/config/default.yml
|
4
|
-
#
|
5
|
-
# At the topmost level of this YAML file is a key representing type of hook
|
6
|
-
# being run (e.g. pre-commit, commit-msg, etc.). Within each type you can
|
7
|
-
# customize each hook, such as whether to only run it on certain files (via
|
8
|
-
# `include`), whether to only display output if it fails (via `quiet`), etc.
|
9
|
-
#
|
10
|
-
# For a complete list of hooks, see:
|
11
|
-
# https://github.com/sds/overcommit/tree/master/lib/overcommit/hook
|
12
|
-
#
|
13
|
-
# For a complete list of options that you can use to customize hooks, see:
|
14
|
-
# https://github.com/sds/overcommit#configuration
|
15
|
-
#
|
16
|
-
# Uncomment the following lines to make the configuration take effect.
|
17
|
-
|
18
|
-
PreCommit:
|
19
|
-
RuboCop:
|
20
|
-
enabled: true
|
21
|
-
command: 'bin/rubocop'
|
22
|
-
|
23
|
-
PrePush:
|
24
|
-
RSpec:
|
25
|
-
enabled: true
|
26
|
-
command: 'bin/rspec'
|
@@ -1,268 +0,0 @@
|
|
1
|
-
module Rubsh
|
2
|
-
class RunningPipeline
|
3
|
-
SPECIAL_KWARGS = %i[
|
4
|
-
_in_data
|
5
|
-
_in
|
6
|
-
_out
|
7
|
-
_err
|
8
|
-
_err_to_out
|
9
|
-
_capture
|
10
|
-
_bg
|
11
|
-
_env
|
12
|
-
_timeout
|
13
|
-
_cwd
|
14
|
-
_ok_code
|
15
|
-
_out_bufsize
|
16
|
-
_err_bufsize
|
17
|
-
_no_out
|
18
|
-
_no_err
|
19
|
-
]
|
20
|
-
|
21
|
-
# @!attribute [r] exit_code
|
22
|
-
# @return [Number]
|
23
|
-
attr_reader :exit_code
|
24
|
-
|
25
|
-
# @!attribute [r] started_at
|
26
|
-
# @return [Time]
|
27
|
-
attr_reader :started_at
|
28
|
-
|
29
|
-
# @!attribute [r] finished_at
|
30
|
-
# @return [Time]
|
31
|
-
attr_reader :finished_at
|
32
|
-
|
33
|
-
# @!attribute [r] stdout_data
|
34
|
-
# @return [String]
|
35
|
-
attr_reader :stdout_data
|
36
|
-
|
37
|
-
# @!attribute [r] stderr_data
|
38
|
-
# @return [String]
|
39
|
-
attr_reader :stderr_data
|
40
|
-
|
41
|
-
def initialize(sh)
|
42
|
-
@sh = sh
|
43
|
-
@rcmds = []
|
44
|
-
|
45
|
-
# Runtime
|
46
|
-
@prog_with_args = nil
|
47
|
-
@exit_code = nil
|
48
|
-
@started_at = nil
|
49
|
-
@finished_at = nil
|
50
|
-
@stdout_data = "".force_encoding(::Encoding.default_external)
|
51
|
-
@stderr_data = "".force_encoding(::Encoding.default_external)
|
52
|
-
@in_rd = nil
|
53
|
-
@in_wr = nil
|
54
|
-
@out_rd = nil
|
55
|
-
@out_wr = nil
|
56
|
-
@err_rd = nil
|
57
|
-
@err_wr = nil
|
58
|
-
@out_rd_reader = nil
|
59
|
-
@err_rd_reader = nil
|
60
|
-
@waiters = []
|
61
|
-
|
62
|
-
# Special Kwargs - Controlling Input/Output
|
63
|
-
@_in_data = nil
|
64
|
-
@_in = nil
|
65
|
-
@_out = nil
|
66
|
-
@_err = nil
|
67
|
-
@_err_to_out = false
|
68
|
-
@_capture = nil
|
69
|
-
|
70
|
-
# Special Kwargs - Execution
|
71
|
-
@_bg = false
|
72
|
-
@_env = nil
|
73
|
-
@_timeout = nil
|
74
|
-
@_cwd = nil
|
75
|
-
@_ok_code = [0]
|
76
|
-
|
77
|
-
# Special Kwargs - Performance & Optimization
|
78
|
-
@_out_bufsize = 0
|
79
|
-
@_err_bufsize = 0
|
80
|
-
@_no_out = false
|
81
|
-
@_no_err = false
|
82
|
-
end
|
83
|
-
|
84
|
-
# @return [Numeric, nil]
|
85
|
-
def wall_time
|
86
|
-
@finished_at.nil? ? nil : @finished_at - @started_at
|
87
|
-
end
|
88
|
-
alias_method :execution_time, :wall_time
|
89
|
-
|
90
|
-
# @return [Boolean]
|
91
|
-
def ok?
|
92
|
-
@_ok_code.include?(@exit_code)
|
93
|
-
end
|
94
|
-
|
95
|
-
# @return [void]
|
96
|
-
def wait(timeout: nil)
|
97
|
-
wait2(timeout: timeout)
|
98
|
-
handle_return_code
|
99
|
-
end
|
100
|
-
|
101
|
-
# @return [String]
|
102
|
-
def to_s
|
103
|
-
@prog_with_args
|
104
|
-
end
|
105
|
-
|
106
|
-
# @!visibility private
|
107
|
-
def __add_running_command(cmd)
|
108
|
-
@rcmds << cmd
|
109
|
-
end
|
110
|
-
|
111
|
-
# @!visibility private
|
112
|
-
def __run(**kwargs)
|
113
|
-
raise Exceptions::CommandNotFoundError, format("no commands") if @rcmds.length == 0
|
114
|
-
extract_opts(**kwargs)
|
115
|
-
@_bg ? run_in_background : run_in_foreground
|
116
|
-
end
|
117
|
-
|
118
|
-
private
|
119
|
-
|
120
|
-
def extract_opts(**kwargs)
|
121
|
-
kwargs.each do |k, v|
|
122
|
-
raise ::ArgumentError, format("unsupported special kwarg `%s'", k) unless SPECIAL_KWARGS.include?(k.to_sym)
|
123
|
-
case k.to_sym
|
124
|
-
when :_in_data
|
125
|
-
@_in_data = v
|
126
|
-
when :_in
|
127
|
-
@_in = v
|
128
|
-
when :_out
|
129
|
-
@_out = v
|
130
|
-
when :_err
|
131
|
-
@_err = v
|
132
|
-
when :_err_to_out
|
133
|
-
@_err_to_out = v
|
134
|
-
when :_capture
|
135
|
-
@_capture = v
|
136
|
-
when :_bg
|
137
|
-
@_bg = v
|
138
|
-
when :_env
|
139
|
-
@_env = v.transform_keys(&:to_s).transform_values(&:to_s)
|
140
|
-
when :_timeout
|
141
|
-
@_timeout = v
|
142
|
-
when :_cwd
|
143
|
-
@_cwd = v
|
144
|
-
when :_ok_code
|
145
|
-
@_ok_code = [*v]
|
146
|
-
when :_out_bufsize
|
147
|
-
@_out_bufsize = v
|
148
|
-
when :_err_bufsize
|
149
|
-
@_err_bufsize = v
|
150
|
-
when :_no_out
|
151
|
-
@_no_out = v
|
152
|
-
when :_no_err
|
153
|
-
@_no_err = v
|
154
|
-
end
|
155
|
-
end
|
156
|
-
end
|
157
|
-
|
158
|
-
def compile_redirection_args
|
159
|
-
args = {}
|
160
|
-
|
161
|
-
if @_in
|
162
|
-
args[:in] = @_in
|
163
|
-
else
|
164
|
-
@in_rd, @in_wr = ::IO.pipe
|
165
|
-
@in_wr.sync = true
|
166
|
-
args[:in] = @in_rd.fileno
|
167
|
-
end
|
168
|
-
|
169
|
-
if @_out
|
170
|
-
args[:out] = @_out
|
171
|
-
else
|
172
|
-
@out_rd, @out_wr = ::IO.pipe
|
173
|
-
args[:out] = @out_wr.fileno
|
174
|
-
end
|
175
|
-
|
176
|
-
if @_err_to_out
|
177
|
-
args[:err] = [:child, :out]
|
178
|
-
elsif @_err
|
179
|
-
args[:err] = @_err
|
180
|
-
else
|
181
|
-
@err_rd, @err_wr = ::IO.pipe
|
182
|
-
args[:err] = @err_wr.fileno
|
183
|
-
end
|
184
|
-
|
185
|
-
args
|
186
|
-
end
|
187
|
-
|
188
|
-
def spawn
|
189
|
-
cmds = @rcmds.map { |r| r.__spawn_arguments(env: @_env, cwd: @_cwd, redirection_args: {}) }
|
190
|
-
@prog_with_args = @rcmds.map(&:__prog_with_args).join(" | ")
|
191
|
-
@waiters = ::Open3.pipeline_start(*cmds, compile_redirection_args)
|
192
|
-
@started_at = Time.now
|
193
|
-
@in_wr&.write(@_in_data) if @_in_data
|
194
|
-
@in_wr&.close
|
195
|
-
|
196
|
-
if @out_rd
|
197
|
-
@out_rd_reader = StreamReader.new(@out_rd, bufsize: @_capture ? @_out_bufsize : nil, &proc { |chunk|
|
198
|
-
@stdout_data << chunk unless @_no_out
|
199
|
-
@_capture&.call(chunk, nil)
|
200
|
-
})
|
201
|
-
end
|
202
|
-
if @err_rd
|
203
|
-
@err_rd_reader = StreamReader.new(@err_rd, bufsize: @_capture ? @_err_bufsize : nil, &proc { |chunk|
|
204
|
-
@stderr_data << chunk unless @_no_err
|
205
|
-
@_capture&.call(nil, chunk)
|
206
|
-
})
|
207
|
-
end
|
208
|
-
ensure
|
209
|
-
@in_rd&.close
|
210
|
-
@out_wr&.close
|
211
|
-
@err_wr&.close
|
212
|
-
end
|
213
|
-
|
214
|
-
def wait2(timeout: nil)
|
215
|
-
timeout_occurred = false
|
216
|
-
last_status = nil
|
217
|
-
|
218
|
-
if timeout
|
219
|
-
begin
|
220
|
-
::Timeout.timeout(timeout) { last_status = @waiters.map(&:value)[-1] }
|
221
|
-
rescue ::Timeout::Error
|
222
|
-
timeout_occurred = true
|
223
|
-
|
224
|
-
failures = []
|
225
|
-
@waiters.each { |w| ::Process.kill("TERM", w.pid) } # graceful stop
|
226
|
-
@waiters.each { |w|
|
227
|
-
_, status = nil, nil
|
228
|
-
30.times do
|
229
|
-
_, status = ::Process.wait2(w.pid, ::Process::WNOHANG | ::Process::WUNTRACED)
|
230
|
-
break if status
|
231
|
-
sleep 0.1
|
232
|
-
rescue ::Errno::ECHILD, ::Errno::ESRCH
|
233
|
-
status = true
|
234
|
-
end
|
235
|
-
failures << w.pid if status.nil?
|
236
|
-
}
|
237
|
-
failures.each { |pid| ::Process.kill("KILL", pid) } # forceful stop
|
238
|
-
end
|
239
|
-
else
|
240
|
-
last_status = @waiters.map(&:value)[-1]
|
241
|
-
end
|
242
|
-
|
243
|
-
@exit_code = last_status&.exitstatus
|
244
|
-
@finished_at = Time.now
|
245
|
-
raise Exceptions::CommandTimeoutError, "execution expired" if timeout_occurred
|
246
|
-
rescue ::Errno::ECHILD, ::Errno::ESRCH
|
247
|
-
raise Exceptions::CommandTimeoutError, "execution expired" if timeout_occurred
|
248
|
-
ensure
|
249
|
-
@out_rd_reader&.wait
|
250
|
-
@err_rd_reader&.wait
|
251
|
-
end
|
252
|
-
|
253
|
-
def handle_return_code
|
254
|
-
return if ok?
|
255
|
-
message = format("\n\n RAN: %s\n\n STDOUT:\n%s\n STDERR:\n%s\n", @prog_with_args, @stdout_data, @stderr_data)
|
256
|
-
raise Exceptions::CommandReturnFailureError.new(@exit_code, message)
|
257
|
-
end
|
258
|
-
|
259
|
-
def run_in_background
|
260
|
-
spawn
|
261
|
-
end
|
262
|
-
|
263
|
-
def run_in_foreground
|
264
|
-
spawn
|
265
|
-
wait(timeout: @_timeout)
|
266
|
-
end
|
267
|
-
end
|
268
|
-
end
|