exeggutor 0.1.3 → 0.1.4
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/lib/exeggutor.rb +65 -70
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8d90faa7fb5d339db88d49c085d3a878d92f274595ea95e5a0efead535b3ad48
|
|
4
|
+
data.tar.gz: 7291380ab60f90b974af5e7f32127d1cb45e567bb17455e9978e31ce166fe744
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8f9d68fa39c285bf2d838331fe5ab795c8b606e1ebf694a0df42c1147b6d979d3080959b3c601eff396e04a2790e2e3540a7f8bb66e907bf54c602aacec9c719
|
|
7
|
+
data.tar.gz: 541d557d0340ede5b74a99e2200548ce253e155c7e8af9e55e57fecca5c7346b5cb74520ba83e995eec1d18683215bb64c93b6d0e3bf5fa93b68fde06d3a3814
|
data/lib/exeggutor.rb
CHANGED
|
@@ -2,9 +2,10 @@ require 'open3'
|
|
|
2
2
|
require 'shellwords'
|
|
3
3
|
|
|
4
4
|
module Exeggutor
|
|
5
|
+
|
|
5
6
|
# A handle to a process, with IO handles to communicate with it
|
|
6
7
|
# and a {ProcessResult} object when it's done. It's largely similar to the array
|
|
7
|
-
# of 4 values return by
|
|
8
|
+
# of 4 values return by Open3.popen3. However, it doesn't suffer from that library's
|
|
8
9
|
# dead-locking issue. For example, even if lots of data has been written to stdout that hasn't been
|
|
9
10
|
# read, the subprocess can still write to stdout and stderr without blocking
|
|
10
11
|
class ProcessHandle
|
|
@@ -121,7 +122,7 @@ module Exeggutor
|
|
|
121
122
|
# Represents an error that occurs during a process execution.
|
|
122
123
|
# The error contains a {ProcessResult} object with details about the process.
|
|
123
124
|
#
|
|
124
|
-
# @attr_reader result
|
|
125
|
+
# @attr_reader result {ProcessResult} The result of the process execution.
|
|
125
126
|
class ProcessError < StandardError
|
|
126
127
|
attr_reader :result
|
|
127
128
|
|
|
@@ -143,65 +144,6 @@ module Exeggutor
|
|
|
143
144
|
end
|
|
144
145
|
end
|
|
145
146
|
|
|
146
|
-
# @private
|
|
147
|
-
def self.exeg(args, can_fail: false, show_stdout: false, show_stderr: false, env: nil, chdir: nil, stdin_data: nil)
|
|
148
|
-
raise "args.size must be >= 1" if args.empty?
|
|
149
|
-
|
|
150
|
-
stdin_io, stdout_io, stderr_io, wait_thr = Exeggutor::run_popen3(args, env, chdir)
|
|
151
|
-
stdin_io.write(stdin_data) if stdin_data
|
|
152
|
-
stdin_io.close
|
|
153
|
-
|
|
154
|
-
# Make the streams as synchronous as possible, to minimize the possibility of a surprising lack
|
|
155
|
-
# of output
|
|
156
|
-
stdout_io.sync = true
|
|
157
|
-
stderr_io.sync = true
|
|
158
|
-
|
|
159
|
-
stdout = +''
|
|
160
|
-
stderr = +''
|
|
161
|
-
|
|
162
|
-
# Although there could be more code sharing between this and exeg_async, it would either complicate exeg_async's inner workings
|
|
163
|
-
# or force us to pay the same performance cost that exeg_async does
|
|
164
|
-
remaining_ios = [stdout_io, stderr_io]
|
|
165
|
-
while remaining_ios.size > 0
|
|
166
|
-
readable_ios, = IO.select(remaining_ios)
|
|
167
|
-
for readable_io in readable_ios
|
|
168
|
-
begin
|
|
169
|
-
data = readable_io.read_nonblock(100_000)
|
|
170
|
-
if readable_io == stdout_io
|
|
171
|
-
stdout << data
|
|
172
|
-
$stdout.print(data) if show_stdout
|
|
173
|
-
else
|
|
174
|
-
stderr << data
|
|
175
|
-
$stderr.print(data) if show_stderr
|
|
176
|
-
end
|
|
177
|
-
rescue IO::WaitReadable
|
|
178
|
-
# Shouldn't usually happen because IO.select indicated data is ready, but maybe due to EINTR or something
|
|
179
|
-
next
|
|
180
|
-
rescue EOFError
|
|
181
|
-
remaining_ios.delete(readable_io)
|
|
182
|
-
end
|
|
183
|
-
end
|
|
184
|
-
end
|
|
185
|
-
|
|
186
|
-
result = ProcessResult.new(
|
|
187
|
-
stdout: stdout,
|
|
188
|
-
stderr: stderr,
|
|
189
|
-
exit_code: wait_thr.value.exitstatus,
|
|
190
|
-
pid: wait_thr.pid
|
|
191
|
-
)
|
|
192
|
-
if !can_fail && !result.success?
|
|
193
|
-
error_str = <<~ERROR_STR
|
|
194
|
-
Command failed: #{args.shelljoin}
|
|
195
|
-
Exit code: #{result.exit_code}
|
|
196
|
-
stdout: #{result.stdout}
|
|
197
|
-
stderr: #{result.stderr}
|
|
198
|
-
pid: #{result.pid}
|
|
199
|
-
ERROR_STR
|
|
200
|
-
raise ProcessError.new(result), error_str
|
|
201
|
-
end
|
|
202
|
-
|
|
203
|
-
result
|
|
204
|
-
end
|
|
205
147
|
end
|
|
206
148
|
|
|
207
149
|
# Executes a command with the provided arguments and options. Waits for the process to finish.
|
|
@@ -215,11 +157,66 @@ end
|
|
|
215
157
|
# @param env [Hash{String => String}, nil] A hashmap containing environment variable overrides,
|
|
216
158
|
# or `nil` if no overrides are desired
|
|
217
159
|
#
|
|
218
|
-
# @return
|
|
160
|
+
# @return {ProcessResult} An object containing process info such as stdout, stderr, and exit code.
|
|
219
161
|
#
|
|
220
|
-
# @raise
|
|
221
|
-
def exeg(
|
|
222
|
-
|
|
162
|
+
# @raise {ProcessError} If the command fails and `can_fail` is false.
|
|
163
|
+
def exeg(args, can_fail: false, show_stdout: false, show_stderr: false, env: nil, chdir: nil, stdin_data: nil)
|
|
164
|
+
raise "args.size must be >= 1" if args.empty?
|
|
165
|
+
|
|
166
|
+
stdin_io, stdout_io, stderr_io, wait_thr = Exeggutor::run_popen3(args, env, chdir)
|
|
167
|
+
stdin_io.write(stdin_data) if stdin_data
|
|
168
|
+
stdin_io.close
|
|
169
|
+
|
|
170
|
+
# Make the streams as synchronous as possible, to minimize the possibility of a surprising lack
|
|
171
|
+
# of output
|
|
172
|
+
stdout_io.sync = true
|
|
173
|
+
stderr_io.sync = true
|
|
174
|
+
|
|
175
|
+
stdout = +''
|
|
176
|
+
stderr = +''
|
|
177
|
+
|
|
178
|
+
# Although there could be more code sharing between this and exeg_async, it would either complicate exeg_async's inner workings
|
|
179
|
+
# or force us to pay the same performance cost that exeg_async does
|
|
180
|
+
remaining_ios = [stdout_io, stderr_io]
|
|
181
|
+
while remaining_ios.size > 0
|
|
182
|
+
readable_ios, = IO.select(remaining_ios)
|
|
183
|
+
for readable_io in readable_ios
|
|
184
|
+
begin
|
|
185
|
+
data = readable_io.read_nonblock(100_000)
|
|
186
|
+
if readable_io == stdout_io
|
|
187
|
+
stdout << data
|
|
188
|
+
$stdout.print(data) if show_stdout
|
|
189
|
+
else
|
|
190
|
+
stderr << data
|
|
191
|
+
$stderr.print(data) if show_stderr
|
|
192
|
+
end
|
|
193
|
+
rescue IO::WaitReadable
|
|
194
|
+
# Shouldn't usually happen because IO.select indicated data is ready, but maybe due to EINTR or something
|
|
195
|
+
next
|
|
196
|
+
rescue EOFError
|
|
197
|
+
remaining_ios.delete(readable_io)
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
result = ProcessResult.new(
|
|
203
|
+
stdout: stdout,
|
|
204
|
+
stderr: stderr,
|
|
205
|
+
exit_code: wait_thr.value.exitstatus,
|
|
206
|
+
pid: wait_thr.pid
|
|
207
|
+
)
|
|
208
|
+
if !can_fail && !result.success?
|
|
209
|
+
error_str = <<~ERROR_STR
|
|
210
|
+
Command failed: #{args.shelljoin}
|
|
211
|
+
Exit code: #{result.exit_code}
|
|
212
|
+
stdout: #{result.stdout}
|
|
213
|
+
stderr: #{result.stderr}
|
|
214
|
+
pid: #{result.pid}
|
|
215
|
+
ERROR_STR
|
|
216
|
+
raise ProcessError.new(result), error_str
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
result
|
|
223
220
|
end
|
|
224
221
|
|
|
225
222
|
# Executes a command with the provided arguments and options. Does not wait for the process to finish.
|
|
@@ -229,9 +226,7 @@ end
|
|
|
229
226
|
# @param env [Hash{String => String}, nil] A hashmap containing environment variable overrides,
|
|
230
227
|
# or `nil` if no overrides are desired
|
|
231
228
|
#
|
|
232
|
-
# @return
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
def exeg_async(...)
|
|
236
|
-
Exeggutor::ProcessHandle.new(...)
|
|
229
|
+
# @return {ProcessHandle}
|
|
230
|
+
def exeg_async(args, env: nil, chdir: nil)
|
|
231
|
+
Exeggutor::ProcessHandle.new(args, env: env, chdir: chdir)
|
|
237
232
|
end
|