ruby-sh 2.1.4 → 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/.editorconfig +9 -0
- data/.rubocop.yml +2 -2
- data/.ruby-version +1 -0
- data/Gemfile +16 -2
- data/Gemfile.lock +147 -40
- data/README.md +122 -179
- data/Rakefile +4 -5
- data/lib/rubsh/command.rb +7 -10
- data/lib/rubsh/running_command.rb +103 -120
- data/lib/rubsh/version.rb +1 -1
- data/lib/rubsh.rb +8 -3
- data/rubsh.gemspec +3 -3
- metadata +9 -16
- data/.overcommit.yml +0 -26
- data/.rubocop +0 -0
- data/lib/rubsh/running_pipeline.rb +0 -243
- data/lib/rubsh/shell/env.rb +0 -15
- data/lib/rubsh/shell.rb +0 -24
- data/sig/rubsh.rbs +0 -4
@@ -1,5 +1,3 @@
|
|
1
|
-
require "timeout"
|
2
|
-
|
3
1
|
module Rubsh
|
4
2
|
class RunningCommand
|
5
3
|
SPECIAL_KWARGS = %i[
|
@@ -20,15 +18,6 @@ module Rubsh
|
|
20
18
|
_no_err
|
21
19
|
_long_sep
|
22
20
|
_long_prefix
|
23
|
-
_pipeline
|
24
|
-
]
|
25
|
-
|
26
|
-
SPECIAL_KWARGS_WITHIN_PIPELINE = %i[
|
27
|
-
_env
|
28
|
-
_cwd
|
29
|
-
_long_sep
|
30
|
-
_long_prefix
|
31
|
-
_pipeline
|
32
21
|
]
|
33
22
|
|
34
23
|
# @!attribute [r] pid
|
@@ -39,6 +28,14 @@ module Rubsh
|
|
39
28
|
# @return [Number]
|
40
29
|
attr_reader :exit_code
|
41
30
|
|
31
|
+
# @!attribute [r] started_at
|
32
|
+
# @return [Time]
|
33
|
+
attr_reader :started_at
|
34
|
+
|
35
|
+
# @!attribute [r] finished_at
|
36
|
+
# @return [Time]
|
37
|
+
attr_reader :finished_at
|
38
|
+
|
42
39
|
# @!attribute [r] stdout_data
|
43
40
|
# @return [String]
|
44
41
|
attr_reader :stdout_data
|
@@ -47,8 +44,7 @@ module Rubsh
|
|
47
44
|
# @return [String]
|
48
45
|
attr_reader :stderr_data
|
49
46
|
|
50
|
-
def initialize(
|
51
|
-
@sh = sh
|
47
|
+
def initialize(prog, progpath, *args, **kwargs)
|
52
48
|
@prog = prog
|
53
49
|
@progpath = progpath
|
54
50
|
@args = []
|
@@ -57,6 +53,8 @@ module Rubsh
|
|
57
53
|
@prog_with_args = nil
|
58
54
|
@pid = nil
|
59
55
|
@exit_code = nil
|
56
|
+
@started_at = nil
|
57
|
+
@finished_at = nil
|
60
58
|
@stdout_data = "".force_encoding(::Encoding.default_external)
|
61
59
|
@stderr_data = "".force_encoding(::Encoding.default_external)
|
62
60
|
@in_rd = nil
|
@@ -93,9 +91,6 @@ module Rubsh
|
|
93
91
|
@_long_sep = "="
|
94
92
|
@_long_prefix = "--"
|
95
93
|
|
96
|
-
# Special Kwargs - Misc
|
97
|
-
@_pipeline = nil
|
98
|
-
|
99
94
|
opts = []
|
100
95
|
args.each do |arg|
|
101
96
|
if arg.is_a?(::Hash)
|
@@ -105,92 +100,43 @@ module Rubsh
|
|
105
100
|
end
|
106
101
|
end
|
107
102
|
kwargs.each { |k, v| opts << Option.build(k, v) }
|
108
|
-
validate_opts(opts)
|
109
103
|
extract_opts(opts)
|
110
104
|
end
|
111
105
|
|
112
|
-
# @return [
|
113
|
-
def
|
114
|
-
|
115
|
-
_, status = nil, nil
|
116
|
-
|
117
|
-
if timeout
|
118
|
-
begin
|
119
|
-
::Timeout.timeout(timeout) { _, status = ::Process.wait2(@pid) }
|
120
|
-
rescue ::Timeout::Error
|
121
|
-
timeout_occurred = true
|
122
|
-
|
123
|
-
::Process.kill("TERM", @pid) # graceful stop
|
124
|
-
30.times do
|
125
|
-
_, status = ::Process.wait2(@pid, ::Process::WNOHANG | ::Process::WUNTRACED)
|
126
|
-
break if status
|
127
|
-
sleep 0.1
|
128
|
-
end
|
129
|
-
failure = @pid if status.nil?
|
130
|
-
failure && ::Process.kill("KILL", failure) # forceful stop
|
131
|
-
end
|
132
|
-
else
|
133
|
-
_, status = ::Process.wait2(@pid)
|
134
|
-
end
|
135
|
-
|
136
|
-
@exit_code = status&.exitstatus
|
137
|
-
raise Exceptions::CommandTimeoutError, "execution expired" if timeout_occurred
|
138
|
-
rescue Errno::ECHILD, Errno::ESRCH
|
139
|
-
raise Exceptions::CommandTimeoutError, "execution expired" if timeout_occurred
|
140
|
-
ensure
|
141
|
-
@out_rd_reader&.wait
|
142
|
-
@err_rd_reader&.wait
|
106
|
+
# @return [Numeric, nil]
|
107
|
+
def wall_time
|
108
|
+
@finished_at.nil? ? nil : @finished_at - @started_at
|
143
109
|
end
|
110
|
+
alias_method :execution_time, :wall_time
|
144
111
|
|
145
|
-
# @return [
|
146
|
-
def
|
147
|
-
|
112
|
+
# @return [Boolean]
|
113
|
+
def ok?
|
114
|
+
@_ok_code.include?(@exit_code)
|
148
115
|
end
|
149
116
|
|
150
117
|
# @!visibility private
|
151
118
|
def __run
|
152
|
-
if @
|
153
|
-
|
119
|
+
if @_bg
|
120
|
+
spawn
|
154
121
|
else
|
155
|
-
|
122
|
+
spawn
|
123
|
+
wait(timeout: @_timeout)
|
156
124
|
end
|
157
125
|
end
|
158
126
|
|
159
|
-
#
|
160
|
-
def
|
161
|
-
|
162
|
-
|
163
|
-
redirection_args ||= compile_redirection_args
|
164
|
-
extra_args = compile_extra_args(cwd: cwd)
|
165
|
-
|
166
|
-
# For logging
|
167
|
-
@prog_with_args = [@progpath].concat(cmd_args).join(" ")
|
168
|
-
|
169
|
-
# .
|
170
|
-
_args =
|
171
|
-
if env
|
172
|
-
[env, [@progpath, @prog], *cmd_args, **redirection_args, **extra_args, unsetenv_others: true]
|
173
|
-
else
|
174
|
-
[[@progpath, @prog], *cmd_args, **redirection_args, **extra_args]
|
175
|
-
end
|
127
|
+
# @return [void]
|
128
|
+
def wait(timeout: nil)
|
129
|
+
wait2(@pid, timeout)
|
130
|
+
handle_return_code
|
176
131
|
end
|
177
132
|
|
178
|
-
#
|
179
|
-
def
|
133
|
+
# @return [String]
|
134
|
+
def to_s
|
180
135
|
@prog_with_args
|
181
136
|
end
|
182
137
|
|
183
138
|
private
|
184
139
|
|
185
|
-
def validate_opts(opts)
|
186
|
-
within_pipeline = opts.any? { |opt| opt.special_kwarg?(:_pipeline) }
|
187
|
-
within_pipeline && opts.each do |opt|
|
188
|
-
if opt.special_kwarg? && !SPECIAL_KWARGS_WITHIN_PIPELINE.include?(opt.k.to_sym)
|
189
|
-
raise ::ArgumentError, format("unsupported special kwargs within _pipeline `%s'", opt.k)
|
190
|
-
end
|
191
|
-
end
|
192
|
-
end
|
193
|
-
|
194
140
|
def extract_opts(opts)
|
195
141
|
args_hash = {}
|
196
142
|
opts.each do |opt|
|
@@ -208,6 +154,8 @@ module Rubsh
|
|
208
154
|
@args << arg
|
209
155
|
end
|
210
156
|
end
|
157
|
+
|
158
|
+
raise ::ArgumentError, "kwargs :_bg conflicts with kwargs :_timeout" if @_bg && @_timeout
|
211
159
|
end
|
212
160
|
|
213
161
|
def extract_special_kwargs_opts(opt)
|
@@ -246,11 +194,52 @@ module Rubsh
|
|
246
194
|
@_long_sep = opt.v
|
247
195
|
when :_long_prefix
|
248
196
|
@_long_prefix = opt.v
|
249
|
-
when :_pipeline
|
250
|
-
@_pipeline = opt.v
|
251
197
|
end
|
252
198
|
end
|
253
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
|
+
|
254
243
|
def compile_cmd_args
|
255
244
|
@args.map { |arg| arg.compile(long_sep: @_long_sep, long_prefix: @_long_prefix) }.compact.flatten
|
256
245
|
end
|
@@ -285,53 +274,47 @@ module Rubsh
|
|
285
274
|
args
|
286
275
|
end
|
287
276
|
|
288
|
-
def compile_extra_args
|
289
|
-
chdir = cwd || @_cwd
|
290
|
-
|
277
|
+
def compile_extra_args
|
291
278
|
args = {}
|
292
|
-
args[:chdir] =
|
279
|
+
args[:chdir] = @_cwd if @_cwd
|
293
280
|
args
|
294
281
|
end
|
295
282
|
|
296
|
-
def
|
297
|
-
|
298
|
-
|
299
|
-
@in_wr&.write(@_in_data) if @_in_data
|
300
|
-
@in_wr&.close
|
283
|
+
def wait2(pid, timeout)
|
284
|
+
timeout_occurred = false
|
285
|
+
_, status = nil, nil
|
301
286
|
|
302
|
-
if
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
287
|
+
if timeout
|
288
|
+
begin
|
289
|
+
::Timeout.timeout(timeout) { _, status = ::Process.wait2(pid) }
|
290
|
+
rescue ::Timeout::Error
|
291
|
+
timeout_occurred = true
|
292
|
+
|
293
|
+
::Process.kill("TERM", pid) # graceful stop
|
294
|
+
30.times do
|
295
|
+
_, status = ::Process.wait2(pid, ::Process::WNOHANG | ::Process::WUNTRACED)
|
296
|
+
break if status
|
297
|
+
sleep 0.1
|
298
|
+
end
|
299
|
+
failure = pid if status.nil?
|
300
|
+
failure && ::Process.kill("KILL", failure) # forceful stop
|
301
|
+
end
|
302
|
+
else
|
303
|
+
_, status = ::Process.wait2(pid)
|
313
304
|
end
|
305
|
+
|
306
|
+
@exit_code = status&.exitstatus || -1
|
307
|
+
@finished_at = Time.now
|
308
|
+
raise Exceptions::CommandTimeoutError, "execution expired" if timeout_occurred
|
314
309
|
ensure
|
315
|
-
@
|
316
|
-
@
|
317
|
-
@err_wr&.close
|
310
|
+
@out_rd_reader&.wait
|
311
|
+
@err_rd_reader&.wait
|
318
312
|
end
|
319
313
|
|
320
314
|
def handle_return_code
|
321
|
-
return if
|
322
|
-
message = format("\n\n
|
315
|
+
return if ok?
|
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)
|
323
317
|
raise Exceptions::CommandReturnFailureError.new(@exit_code, message)
|
324
318
|
end
|
325
|
-
|
326
|
-
def run_in_background
|
327
|
-
spawn
|
328
|
-
Process.detach(@pid)
|
329
|
-
end
|
330
|
-
|
331
|
-
def run_in_foreground
|
332
|
-
spawn
|
333
|
-
wait(timeout: @_timeout)
|
334
|
-
handle_return_code
|
335
|
-
end
|
336
319
|
end
|
337
320
|
end
|
data/lib/rubsh/version.rb
CHANGED
data/lib/rubsh.rb
CHANGED
@@ -1,12 +1,17 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "timeout"
|
4
|
+
|
3
5
|
require_relative "rubsh/argument"
|
4
6
|
require_relative "rubsh/command"
|
5
7
|
require_relative "rubsh/exceptions"
|
6
8
|
require_relative "rubsh/option"
|
7
9
|
require_relative "rubsh/running_command"
|
8
|
-
require_relative "rubsh/running_pipeline"
|
9
|
-
require_relative "rubsh/shell/env"
|
10
|
-
require_relative "rubsh/shell"
|
11
10
|
require_relative "rubsh/stream_reader"
|
12
11
|
require_relative "rubsh/version"
|
12
|
+
|
13
|
+
module Rubsh
|
14
|
+
def self.cmd(prog)
|
15
|
+
Command.new(prog)
|
16
|
+
end
|
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.
|
@@ -18,10 +17,10 @@ executables: []
|
|
18
17
|
extensions: []
|
19
18
|
extra_rdoc_files: []
|
20
19
|
files:
|
21
|
-
- ".
|
20
|
+
- ".editorconfig"
|
22
21
|
- ".rspec"
|
23
|
-
- ".rubocop"
|
24
22
|
- ".rubocop.yml"
|
23
|
+
- ".ruby-version"
|
25
24
|
- ".yardopts"
|
26
25
|
- CODE_OF_CONDUCT.md
|
27
26
|
- Gemfile
|
@@ -35,22 +34,17 @@ files:
|
|
35
34
|
- lib/rubsh/exceptions.rb
|
36
35
|
- lib/rubsh/option.rb
|
37
36
|
- lib/rubsh/running_command.rb
|
38
|
-
- lib/rubsh/running_pipeline.rb
|
39
|
-
- lib/rubsh/shell.rb
|
40
|
-
- lib/rubsh/shell/env.rb
|
41
37
|
- lib/rubsh/stream_reader.rb
|
42
38
|
- lib/rubsh/version.rb
|
43
39
|
- rubsh.gemspec
|
44
|
-
|
45
|
-
homepage: https://github.com/souk4711/rubsh
|
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'
|
data/.rubocop
DELETED
File without changes
|
@@ -1,243 +0,0 @@
|
|
1
|
-
require "open3"
|
2
|
-
|
3
|
-
module Rubsh
|
4
|
-
class RunningPipeline
|
5
|
-
SPECIAL_KWARGS = %i[
|
6
|
-
_in_data
|
7
|
-
_in
|
8
|
-
_out
|
9
|
-
_err
|
10
|
-
_err_to_out
|
11
|
-
_capture
|
12
|
-
_bg
|
13
|
-
_env
|
14
|
-
_timeout
|
15
|
-
_cwd
|
16
|
-
_ok_code
|
17
|
-
_out_bufsize
|
18
|
-
_err_bufsize
|
19
|
-
_no_out
|
20
|
-
_no_err
|
21
|
-
]
|
22
|
-
|
23
|
-
# @!attribute [r] exit_code
|
24
|
-
# @return [Number]
|
25
|
-
attr_reader :exit_code
|
26
|
-
|
27
|
-
# @!attribute [r] stdout_data
|
28
|
-
# @return [String]
|
29
|
-
attr_reader :stdout_data
|
30
|
-
|
31
|
-
# @!attribute [r] stderr_data
|
32
|
-
# @return [String]
|
33
|
-
attr_reader :stderr_data
|
34
|
-
|
35
|
-
def initialize(sh)
|
36
|
-
@sh = sh
|
37
|
-
@rcmds = []
|
38
|
-
|
39
|
-
# Runtime
|
40
|
-
@prog_with_args = nil
|
41
|
-
@exit_code = nil
|
42
|
-
@stdout_data = "".force_encoding(::Encoding.default_external)
|
43
|
-
@stderr_data = "".force_encoding(::Encoding.default_external)
|
44
|
-
@in_rd = nil
|
45
|
-
@in_wr = nil
|
46
|
-
@out_rd = nil
|
47
|
-
@out_wr = nil
|
48
|
-
@err_rd = nil
|
49
|
-
@err_wr = nil
|
50
|
-
@out_rd_reader = nil
|
51
|
-
@err_rd_reader = nil
|
52
|
-
@waiters = []
|
53
|
-
|
54
|
-
# Special Kwargs - Controlling Input/Output
|
55
|
-
@_in_data = nil
|
56
|
-
@_in = nil
|
57
|
-
@_out = nil
|
58
|
-
@_err = nil
|
59
|
-
@_err_to_out = false
|
60
|
-
@_capture = nil
|
61
|
-
|
62
|
-
# Special Kwargs - Execution
|
63
|
-
@_bg = false
|
64
|
-
@_env = nil
|
65
|
-
@_timeout = nil
|
66
|
-
@_cwd = nil
|
67
|
-
@_ok_code = [0]
|
68
|
-
|
69
|
-
# Special Kwargs - Performance & Optimization
|
70
|
-
@_out_bufsize = 0
|
71
|
-
@_err_bufsize = 0
|
72
|
-
@_no_out = false
|
73
|
-
@_no_err = false
|
74
|
-
end
|
75
|
-
|
76
|
-
# @return [void]
|
77
|
-
def wait(timeout: nil)
|
78
|
-
timeout_occurred = false
|
79
|
-
last_status = nil
|
80
|
-
|
81
|
-
if timeout
|
82
|
-
begin
|
83
|
-
::Timeout.timeout(timeout) { last_status = @waiters.map(&:value)[-1] }
|
84
|
-
rescue ::Timeout::Error
|
85
|
-
timeout_occurred = true
|
86
|
-
|
87
|
-
failures = []
|
88
|
-
@waiters.each { |w| ::Process.kill("TERM", w.pid) } # graceful stop
|
89
|
-
@waiters.each { |w|
|
90
|
-
_, status = nil, nil
|
91
|
-
30.times do
|
92
|
-
_, status = ::Process.wait2(w.pid, ::Process::WNOHANG | ::Process::WUNTRACED)
|
93
|
-
break if status
|
94
|
-
sleep 0.1
|
95
|
-
rescue ::Errno::ECHILD, ::Errno::ESRCH
|
96
|
-
status = true
|
97
|
-
end
|
98
|
-
failures << w.pid if status.nil?
|
99
|
-
}
|
100
|
-
failures.each { |pid| ::Process.kill("KILL", pid) } # forceful stop
|
101
|
-
end
|
102
|
-
else
|
103
|
-
last_status = @waiters.map(&:value)[-1]
|
104
|
-
end
|
105
|
-
|
106
|
-
@exit_code = last_status&.exitstatus
|
107
|
-
raise Exceptions::CommandTimeoutError, "execution expired" if timeout_occurred
|
108
|
-
rescue ::Errno::ECHILD, ::Errno::ESRCH
|
109
|
-
raise Exceptions::CommandTimeoutError, "execution expired" if timeout_occurred
|
110
|
-
ensure
|
111
|
-
@out_rd_reader&.wait
|
112
|
-
@err_rd_reader&.wait
|
113
|
-
end
|
114
|
-
|
115
|
-
# @return [String]
|
116
|
-
def inspect
|
117
|
-
format("#<Rubsh::RunningPipeline '%s'>", @prog_with_args)
|
118
|
-
end
|
119
|
-
|
120
|
-
# @!visibility private
|
121
|
-
def __add_running_command(cmd)
|
122
|
-
@rcmds << cmd
|
123
|
-
end
|
124
|
-
|
125
|
-
# @!visibility private
|
126
|
-
def __run(**kwargs)
|
127
|
-
raise Exceptions::CommandNotFoundError, format("no commands") if @rcmds.length == 0
|
128
|
-
extract_opts(**kwargs)
|
129
|
-
@_bg ? run_in_background : run_in_foreground
|
130
|
-
end
|
131
|
-
|
132
|
-
private
|
133
|
-
|
134
|
-
def extract_opts(**kwargs)
|
135
|
-
kwargs.each do |k, v|
|
136
|
-
raise ::ArgumentError, format("unsupported special kwarg `%s'", k) unless SPECIAL_KWARGS.include?(k.to_sym)
|
137
|
-
case k.to_sym
|
138
|
-
when :_in_data
|
139
|
-
@_in_data = v
|
140
|
-
when :_in
|
141
|
-
@_in = v
|
142
|
-
when :_out
|
143
|
-
@_out = v
|
144
|
-
when :_err
|
145
|
-
@_err = v
|
146
|
-
when :_err_to_out
|
147
|
-
@_err_to_out = v
|
148
|
-
when :_capture
|
149
|
-
@_capture = v
|
150
|
-
when :_bg
|
151
|
-
@_bg = v
|
152
|
-
when :_env
|
153
|
-
@_env = v.transform_keys(&:to_s).transform_values(&:to_s)
|
154
|
-
when :_timeout
|
155
|
-
@_timeout = v
|
156
|
-
when :_cwd
|
157
|
-
@_cwd = v
|
158
|
-
when :_ok_code
|
159
|
-
@_ok_code = [*v]
|
160
|
-
when :_out_bufsize
|
161
|
-
@_out_bufsize = v
|
162
|
-
when :_err_bufsize
|
163
|
-
@_err_bufsize = v
|
164
|
-
when :_no_out
|
165
|
-
@_no_out = v
|
166
|
-
when :_no_err
|
167
|
-
@_no_err = v
|
168
|
-
end
|
169
|
-
end
|
170
|
-
end
|
171
|
-
|
172
|
-
def compile_redirection_args
|
173
|
-
args = {}
|
174
|
-
|
175
|
-
if @_in
|
176
|
-
args[:in] = @_in
|
177
|
-
else
|
178
|
-
@in_rd, @in_wr = ::IO.pipe
|
179
|
-
@in_wr.sync = true
|
180
|
-
args[:in] = @in_rd.fileno
|
181
|
-
end
|
182
|
-
|
183
|
-
if @_out
|
184
|
-
args[:out] = @_out
|
185
|
-
else
|
186
|
-
@out_rd, @out_wr = ::IO.pipe
|
187
|
-
args[:out] = @out_wr.fileno
|
188
|
-
end
|
189
|
-
|
190
|
-
if @_err_to_out
|
191
|
-
args[:err] = [:child, :out]
|
192
|
-
elsif @_err
|
193
|
-
args[:err] = @_err
|
194
|
-
else
|
195
|
-
@err_rd, @err_wr = ::IO.pipe
|
196
|
-
args[:err] = @err_wr.fileno
|
197
|
-
end
|
198
|
-
|
199
|
-
args
|
200
|
-
end
|
201
|
-
|
202
|
-
def spawn
|
203
|
-
cmds = @rcmds.map { |r| r.__spawn_arguments(env: @_env, cwd: @_cwd, redirection_args: {}) }
|
204
|
-
@prog_with_args = @rcmds.map(&:__prog_with_args).join(" | ")
|
205
|
-
@waiters = ::Open3.pipeline_start(*cmds, compile_redirection_args)
|
206
|
-
@in_wr&.write(@_in_data) if @_in_data
|
207
|
-
@in_wr&.close
|
208
|
-
|
209
|
-
if @out_rd
|
210
|
-
@out_rd_reader = StreamReader.new(@out_rd, bufsize: @_capture ? @_out_bufsize : nil, &proc { |chunk|
|
211
|
-
@stdout_data << chunk unless @_no_out
|
212
|
-
@_capture&.call(chunk, nil)
|
213
|
-
})
|
214
|
-
end
|
215
|
-
if @err_rd
|
216
|
-
@err_rd_reader = StreamReader.new(@err_rd, bufsize: @_capture ? @_err_bufsize : nil, &proc { |chunk|
|
217
|
-
@stderr_data << chunk unless @_no_err
|
218
|
-
@_capture&.call(nil, chunk)
|
219
|
-
})
|
220
|
-
end
|
221
|
-
ensure
|
222
|
-
@in_rd&.close
|
223
|
-
@out_wr&.close
|
224
|
-
@err_wr&.close
|
225
|
-
end
|
226
|
-
|
227
|
-
def handle_return_code
|
228
|
-
return if @_ok_code.include?(@exit_code)
|
229
|
-
message = format("\n\n RAN: %s\n\n STDOUT:\n%s\n STDERR:\n%s\n", @prog_with_args, @stdout_data, @stderr_data)
|
230
|
-
raise Exceptions::CommandReturnFailureError.new(@exit_code, message)
|
231
|
-
end
|
232
|
-
|
233
|
-
def run_in_background
|
234
|
-
spawn
|
235
|
-
end
|
236
|
-
|
237
|
-
def run_in_foreground
|
238
|
-
spawn
|
239
|
-
wait(timeout: @_timeout)
|
240
|
-
handle_return_code
|
241
|
-
end
|
242
|
-
end
|
243
|
-
end
|