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.
Files changed (3) hide show
  1. checksums.yaml +7 -0
  2. data/lib/exeggutor.rb +129 -0
  3. 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: []