exeggutor 0.1.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 +7 -0
- data/lib/exeggutor.rb +129 -0
- metadata +57 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 0b9eb0de91468b1ba1d04da72cac44b57eaadd3553719d86910d71f88af67746
|
|
4
|
+
data.tar.gz: 77713dec1ae04c17322d25563abf517dc406c34f61661e1fd483abf9a307f123
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: edfbd0cbe10c2b38812b8a92adfa91566cdd5f4823a4d4e9030a516c7b0c352c465796c7c30b78744db33501d3cf832acf1570076aa47a1db0826621a5d0ab16
|
|
7
|
+
data.tar.gz: a7bdcd0f7f915dfce050b7cb84ec3ffe92ac46304524e2f51440eeef02417209458f35562256b7e74807795501d893a24eed97cf947481686d5f7bd3a1c7e064
|
data/lib/exeggutor.rb
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
require 'open3'
|
|
2
|
+
require 'shellwords'
|
|
3
|
+
|
|
4
|
+
module Exeggutor
|
|
5
|
+
# Represents the result of a process execution.
|
|
6
|
+
#
|
|
7
|
+
# @attr_reader stdout [String] The standard output of the process.
|
|
8
|
+
# @attr_reader stderr [String] The standard error of the process.
|
|
9
|
+
# @attr_reader exit_code [Integer] The exit code of the process.
|
|
10
|
+
class ProcessResult
|
|
11
|
+
attr_reader :stdout, :stderr, :exit_code
|
|
12
|
+
|
|
13
|
+
# @private
|
|
14
|
+
def initialize(stdout:, stderr:, exit_code:)
|
|
15
|
+
@stdout = stdout
|
|
16
|
+
@stderr = stderr
|
|
17
|
+
@exit_code = exit_code
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Checks if the process was successful.
|
|
21
|
+
#
|
|
22
|
+
# @return [Boolean] True if the exit code is 0, otherwise false.
|
|
23
|
+
def success?
|
|
24
|
+
exit_code == 0
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Represents an error that occurs during a process execution.
|
|
29
|
+
# The error contains a {ProcessResult} object with details about the process.
|
|
30
|
+
#
|
|
31
|
+
# @attr_reader result [ProcessResult] The result of the process execution.
|
|
32
|
+
class ProcessError < StandardError
|
|
33
|
+
attr_reader :result
|
|
34
|
+
|
|
35
|
+
# @private
|
|
36
|
+
def initialize(result)
|
|
37
|
+
@result = result
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# @private
|
|
42
|
+
def self.run_popen3(args, env)
|
|
43
|
+
if env
|
|
44
|
+
Open3.popen3(env, [args[0], args[0]], *args.drop(1))
|
|
45
|
+
else
|
|
46
|
+
Open3.popen3([args[0], args[0]], *args.drop(1))
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Executes a command with the provided arguments and options
|
|
51
|
+
#
|
|
52
|
+
# @param args [Array<String>] The command and its arguments as an array.
|
|
53
|
+
# @param can_fail [Boolean] If false, raises a ProcessError on failure.
|
|
54
|
+
# @param show_stdout [Boolean] If true, prints stdout to the console in real-time.
|
|
55
|
+
# @param show_stderr [Boolean] If true, prints stderr to the console in real-time.
|
|
56
|
+
# @param cwd [String, nil] The working directory to run the command in. If nil, uses the current working directory.
|
|
57
|
+
# @param stdin_data [String, nil] Input data to pass to the command's stdin. If nil, doesn't pass any data to stdin.
|
|
58
|
+
# @param env_vars [Hash{String => String}, nil] A hashmap containing environment variable overrides,
|
|
59
|
+
# or `nil` if no overrides are desired
|
|
60
|
+
#
|
|
61
|
+
# @return [ProcessResult] An object containing process info such as stdout, stderr, and exit code. Waits for the command to complete to return.
|
|
62
|
+
#
|
|
63
|
+
# @raise [ProcessError] If the command fails and `can_fail` is false.
|
|
64
|
+
def self.run!(args, can_fail: false, show_stdout: false, show_stderr: false, env: nil, cwd: nil, stdin_data: nil)
|
|
65
|
+
# TODO: expand "~"? popen3 doesn't expand it by default
|
|
66
|
+
if cwd
|
|
67
|
+
stdin_stream, stdout_stream, stderr_stream, wait_thread = Dir.chdir(cwd) { Exeggutor::run_popen3(args, env) }
|
|
68
|
+
else
|
|
69
|
+
stdin_stream, stdout_stream, stderr_stream, wait_thread = Exeggutor::run_popen3(args, env)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
stdin_stream.write(stdin_data) if stdin_data
|
|
73
|
+
stdin_stream.close
|
|
74
|
+
|
|
75
|
+
stderr_stream.sync = true # Match terminals more closely
|
|
76
|
+
|
|
77
|
+
stdout_str = +'' # Using unfrozen string
|
|
78
|
+
stderr_str = +''
|
|
79
|
+
|
|
80
|
+
# Start readers for both stdout and stderr
|
|
81
|
+
stdout_thread = Thread.new do
|
|
82
|
+
while (line = stdout_stream.gets)
|
|
83
|
+
|
|
84
|
+
stdout_str << line
|
|
85
|
+
print line if show_stdout
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
stderr_thread = Thread.new do
|
|
90
|
+
while (line = stderr_stream.gets)
|
|
91
|
+
stderr_str << line
|
|
92
|
+
warn line if show_stderr
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Wait for process completion
|
|
97
|
+
exit_status = wait_thread.value
|
|
98
|
+
|
|
99
|
+
# Ensure all IO is complete
|
|
100
|
+
stdout_thread.join
|
|
101
|
+
stderr_thread.join
|
|
102
|
+
|
|
103
|
+
# Close open pipes
|
|
104
|
+
stdout_stream.close
|
|
105
|
+
stderr_stream.close
|
|
106
|
+
|
|
107
|
+
result = ProcessResult.new(
|
|
108
|
+
stdout: stdout_str.force_encoding('UTF-8'),
|
|
109
|
+
stderr: stderr_str.force_encoding('UTF-8'),
|
|
110
|
+
exit_code: exit_status.exitstatus
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
if !can_fail && !result.success?
|
|
114
|
+
error_str = <<~ERROR_STR
|
|
115
|
+
Command failed: #{args.shelljoin}
|
|
116
|
+
Exit code: #{result.exit_code}
|
|
117
|
+
stdout: #{result.stdout}
|
|
118
|
+
stderr: #{result.stderr}
|
|
119
|
+
ERROR_STR
|
|
120
|
+
raise ProcessError.new(result), error_str
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
result
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def run!(...)
|
|
128
|
+
Exeggutor::run!(...)
|
|
129
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: exeggutor
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Michael Eisel
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2025-02-23 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: minitest
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '0'
|
|
20
|
+
type: :development
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '0'
|
|
27
|
+
description:
|
|
28
|
+
email: michael.eisel@gmail.com
|
|
29
|
+
executables: []
|
|
30
|
+
extensions: []
|
|
31
|
+
extra_rdoc_files: []
|
|
32
|
+
files:
|
|
33
|
+
- lib/exeggutor.rb
|
|
34
|
+
homepage: https://github.com/michaeleisel/Exeggutor
|
|
35
|
+
licenses:
|
|
36
|
+
- MIT
|
|
37
|
+
metadata: {}
|
|
38
|
+
post_install_message:
|
|
39
|
+
rdoc_options: []
|
|
40
|
+
require_paths:
|
|
41
|
+
- lib
|
|
42
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '0'
|
|
47
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
48
|
+
requirements:
|
|
49
|
+
- - ">="
|
|
50
|
+
- !ruby/object:Gem::Version
|
|
51
|
+
version: '0'
|
|
52
|
+
requirements: []
|
|
53
|
+
rubygems_version: 3.5.3
|
|
54
|
+
signing_key:
|
|
55
|
+
specification_version: 4
|
|
56
|
+
summary: A Simple, Capable, and Unified Interface for Running Subprocesses in Ruby
|
|
57
|
+
test_files: []
|