process_executer 3.2.4 → 4.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/.release-please-manifest.json +1 -1
- data/CHANGELOG.md +41 -0
- data/README.md +177 -134
- data/lib/process_executer/commands/run.rb +124 -0
- data/lib/process_executer/commands/run_with_capture.rb +148 -0
- data/lib/process_executer/commands/spawn_with_timeout.rb +163 -0
- data/lib/process_executer/commands.rb +11 -0
- data/lib/process_executer/destinations/child_redirection.rb +5 -4
- data/lib/process_executer/destinations/close.rb +5 -4
- data/lib/process_executer/destinations/destination_base.rb +73 -0
- data/lib/process_executer/destinations/file_descriptor.rb +10 -6
- data/lib/process_executer/destinations/file_path.rb +12 -6
- data/lib/process_executer/destinations/file_path_mode.rb +10 -6
- data/lib/process_executer/destinations/file_path_mode_perms.rb +12 -5
- data/lib/process_executer/destinations/io.rb +10 -5
- data/lib/process_executer/destinations/monitored_pipe.rb +10 -5
- data/lib/process_executer/destinations/stderr.rb +8 -4
- data/lib/process_executer/destinations/stdout.rb +8 -4
- data/lib/process_executer/destinations/tee.rb +24 -17
- data/lib/process_executer/destinations/writer.rb +12 -7
- data/lib/process_executer/destinations.rb +32 -17
- data/lib/process_executer/errors.rb +50 -26
- data/lib/process_executer/monitored_pipe.rb +128 -59
- data/lib/process_executer/options/base.rb +118 -82
- data/lib/process_executer/options/option_definition.rb +5 -1
- data/lib/process_executer/options/run_options.rb +13 -12
- data/lib/process_executer/options/run_with_capture_options.rb +156 -0
- data/lib/process_executer/options/spawn_options.rb +31 -30
- data/lib/process_executer/options/{spawn_and_wait_options.rb → spawn_with_timeout_options.rb} +11 -7
- data/lib/process_executer/options.rb +3 -1
- data/lib/process_executer/result.rb +35 -77
- data/lib/process_executer/result_with_capture.rb +62 -0
- data/lib/process_executer/version.rb +2 -1
- data/lib/process_executer.rb +384 -346
- data/process_executer.gemspec +11 -2
- metadata +18 -8
- data/lib/process_executer/destination_base.rb +0 -83
- data/lib/process_executer/runner.rb +0 -144
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f8f23dd316f574cbf2194c9e75b825f03e34c86719d38ba626c22ce26c8d213b
|
4
|
+
data.tar.gz: ccc618656594d909774c872706fc27d8440bf6a26170d678ad3058fef3cf6310
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0e06d8087a256e103b0352bb4f6e4215cdd0d3e7810fb3206337c211aa45d309d7deaa8e760c41815fe81171ac9258a441c6a8f22b5a4c4fa2465c4c28f59314
|
7
|
+
data.tar.gz: 7dc33006e39f0f35399741017cb584f132ef89ea50c0dd8081556a12c2dd48ad19a0bf8adbe915f9be241d54374a3d0856ffe54d4c778d1b2b0bc9e4eeef44b3
|
data/CHANGELOG.md
CHANGED
@@ -5,6 +5,47 @@ All notable changes to the process_executer gem will be documented in this file.
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
7
7
|
|
8
|
+
## [4.0.0](https://github.com/main-branch/process_executer/compare/v3.2.4...v4.0.0) (2025-06-05)
|
9
|
+
|
10
|
+
|
11
|
+
### ⚠ BREAKING CHANGES
|
12
|
+
|
13
|
+
* Users who call ProcessExecuter::Options::Base#with even if from a derived class will need to update to use #merge instead.
|
14
|
+
* Users depending on `Result#stdout` or `Result#stderr` will either have to capture this output manually themselves or change from `spawn_and_wait`/`run` to `run_with_capture`.
|
15
|
+
* calls to `ProcessExecuter.spawn_with_timeout_with_options` and `ProcessExecuter.run_with_options` have been removed. Use `ProcessExecuter.spawn_with_timeout` and `ProcessExecuter.run` instead.
|
16
|
+
* Users who use ProcessExecuter.spawn_and_wait will need to update their calls to spawn_with_timeout. In addition, the following items will need to be updated if used by the user of this gem:
|
17
|
+
* ProcessExecuter.spawn_and_wait_with_options
|
18
|
+
* ProcessExecuter::SpawnAndWaitOptions
|
19
|
+
* In places where users of this gem rescued ::ArgumentError, they will have to change the rescued class to ProcessExecuter::ArgumentError.
|
20
|
+
|
21
|
+
### Features
|
22
|
+
|
23
|
+
* Add `ProcessExecuter.run_with_capture` ([d9e97fe](https://github.com/main-branch/process_executer/commit/d9e97fe7728a0c7fce9520ad5ba9568782243f70))
|
24
|
+
* Add encoding, stdout_encoding, stderr_encoding options to RunWithCaptureOptions ([83eaa93](https://github.com/main-branch/process_executer/commit/83eaa93417e810c1a1b569616c19d8facdc2b2f4))
|
25
|
+
* Add ProcessExecuter::ArgumentError and raise it instead of ::ArgumentError ([860fc5a](https://github.com/main-branch/process_executer/commit/860fc5a224f86dd4ff525de32b643fc261e456f6))
|
26
|
+
* Ensure that all data written by MonitoredPipe is ASCII-8BIT encoded ([8753006](https://github.com/main-branch/process_executer/commit/87530066280ed91afc208714514df845e4455b6b))
|
27
|
+
* Make run_with_capture encode captured stdout and stderr based on encoding options ([75c3d92](https://github.com/main-branch/process_executer/commit/75c3d922fa74a17b61b415fe50acd9763a00524f))
|
28
|
+
* Remove #spawn_with_timeout_with_options and #run_with_options methods ([446cb51](https://github.com/main-branch/process_executer/commit/446cb510a634ff5171df6b0fcb2d426cb3f9ed9e))
|
29
|
+
* Remove Result#stdout and Result#stderr ([2dcad47](https://github.com/main-branch/process_executer/commit/2dcad47bb921170070f8d4bae1ce07244547db5c))
|
30
|
+
* Rename ProcessExecuter::Options::Base#with to #merge ([7e8c28e](https://github.com/main-branch/process_executer/commit/7e8c28e33b99945187d272766134e30b5746ddc8))
|
31
|
+
* Rename ProcessExecuter.spawn_and_wait to spawn_with_timeout ([b9d19e7](https://github.com/main-branch/process_executer/commit/b9d19e792234996f78c7cd63b22047bb7474a06d))
|
32
|
+
|
33
|
+
|
34
|
+
### Bug Fixes
|
35
|
+
|
36
|
+
* Fix new rubocop offense Style/EmptyStringInsideInterpolation ([bb610af](https://github.com/main-branch/process_executer/commit/bb610af96519cccd2fe1be62e61b1531711e5d9b))
|
37
|
+
|
38
|
+
|
39
|
+
### Other Changes
|
40
|
+
|
41
|
+
* Add a JRuby 10 build to the continuous integration workflow ([7a939ba](https://github.com/main-branch/process_executer/commit/7a939ba5bf7b291555a4259db7040b6cb96f494b))
|
42
|
+
* Document the new encoding options on ProcessExecuter.run_with_capture ([c86ce62](https://github.com/main-branch/process_executer/commit/c86ce627b8c081981a92414c35eafb85b99e3201))
|
43
|
+
* Ensure that binary data is correctly written to file destinations ([0d2db54](https://github.com/main-branch/process_executer/commit/0d2db54d2b9c4354cb880a17a6b683dc8b5f8424))
|
44
|
+
* Fix indentation in README ([1837e7a](https://github.com/main-branch/process_executer/commit/1837e7a5cee56ccc7f3ebdc7f71aab43d1a56ab4))
|
45
|
+
* Internally refactor classes for clarity and update documentation ([da1db96](https://github.com/main-branch/process_executer/commit/da1db9697e5c2be371d308d5790e44dcccf8e40b))
|
46
|
+
* Remove unneeded :nocov: blocks ([7a1fcf5](https://github.com/main-branch/process_executer/commit/7a1fcf500b89d7fe8e7254ba4fa20e38f0b46d45))
|
47
|
+
* Update the README with all the changes for the latest release ([38206a5](https://github.com/main-branch/process_executer/commit/38206a57d26dcca3437611291ab3ccdc1d5a442f))
|
48
|
+
|
8
49
|
## [3.2.4](https://github.com/main-branch/process_executer/compare/v3.2.3...v3.2.4) (2025-04-18)
|
9
50
|
|
10
51
|
|
data/README.md
CHANGED
@@ -15,176 +15,190 @@ It has additional features like capturing output, handling timeouts, streaming o
|
|
15
15
|
to multiple destinations, and providing detailed result information.
|
16
16
|
|
17
17
|
This README documents the HEAD version of process_executer which may contain
|
18
|
-
|
18
|
+
unreleased information. To see the README for the version you are using, consult
|
19
19
|
RubyGems.org. Go to the [process_executer page in
|
20
20
|
RubyGems.org](https://rubygems.org/gems/process_executer), select your version, and
|
21
21
|
then click the "Documentation" link.
|
22
22
|
|
23
23
|
## Requirements
|
24
24
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
## Table of
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
25
|
+
- Ruby 3.1.0 or later
|
26
|
+
- Compatible with MRI 3.1+, TruffleRuby 24+, and JRuby 9.4+
|
27
|
+
- Works on Mac, Linux, and Windows platforms
|
28
|
+
|
29
|
+
## Table of contents
|
30
|
+
|
31
|
+
- [Requirements](#requirements)
|
32
|
+
- [Table of contents](#table-of-contents)
|
33
|
+
- [Usage](#usage)
|
34
|
+
- [Key methods](#key-methods)
|
35
|
+
- [ProcessExecuter::MonitoredPipe](#processexecutermonitoredpipe)
|
36
|
+
- [Encoding](#encoding)
|
37
|
+
- [Encoding summary](#encoding-summary)
|
38
|
+
- [Encoding details](#encoding-details)
|
39
|
+
- [Breaking Changes](#breaking-changes)
|
40
|
+
- [2.x](#2x)
|
41
|
+
- [`ProcessExecuter.spawn`](#processexecuterspawn)
|
42
|
+
- [`ProcessExecuter.run`](#processexecuterrun)
|
43
|
+
- [`ProcessExecuter::Result`](#processexecuterresult)
|
44
|
+
- [Other](#other)
|
45
|
+
- [3.x](#3x)
|
46
|
+
- [`ProcessExecuter.run`](#processexecuterrun-1)
|
47
|
+
- [4.x](#4x)
|
48
|
+
- [`ProcessExecuter.spawn_and_wait`](#processexecuterspawn_and_wait)
|
49
|
+
- [`ProcessExecuter::Result`](#processexecuterresult-1)
|
50
|
+
- [`ProcessExecuter.spawn_and_wait_with_options`](#processexecuterspawn_and_wait_with_options)
|
51
|
+
- [`ProcessExecuter.run_with_options`](#processexecuterrun_with_options)
|
52
|
+
- [Other](#other-1)
|
53
|
+
- [Installation](#installation)
|
54
|
+
- [Contributing](#contributing)
|
55
|
+
- [Reporting Issues](#reporting-issues)
|
56
|
+
- [Developing](#developing)
|
57
|
+
- [Commit message guidelines](#commit-message-guidelines)
|
58
|
+
- [Pull request guidelines](#pull-request-guidelines)
|
59
|
+
- [Releasing](#releasing)
|
60
|
+
- [License](#license)
|
54
61
|
|
55
62
|
## Usage
|
56
63
|
|
57
64
|
[Full YARD documentation](https://rubydoc.info/gems/process_executer/) for this gem
|
58
65
|
is hosted on RubyGems.org. Read below for an overview and several examples.
|
59
66
|
|
60
|
-
|
67
|
+
### Key methods
|
61
68
|
|
62
|
-
|
69
|
+
ℹ️ See [the ProcessExecuter module
|
70
|
+
documentation](https://rubydoc.info/gems/process_executer/ProcessExecuter) for
|
71
|
+
more details and examples of using the methods described here.
|
63
72
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
from the subprocess.
|
73
|
+
The `ProcessExecuter` module provides extended versions of
|
74
|
+
[Process.spawn](https://docs.ruby-lang.org/en/3.4/Process.html#method-c-spawn) that
|
75
|
+
block while the command is executing. These methods provide enhanced features such as
|
76
|
+
timeout handling, more flexible redirection options, logging, error raising, and
|
77
|
+
output capturing.
|
70
78
|
|
71
|
-
|
79
|
+
The interface of these methods is the same as the standard library
|
80
|
+
[Process.spawn](https://docs.ruby-lang.org/en/3.4/Process.html#method-c-spawn)
|
81
|
+
method but with additional options.
|
72
82
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
83
|
+
These methods are:
|
84
|
+
|
85
|
+
| Method | Description |
|
86
|
+
|--------|-------------|
|
87
|
+
| `ProcessExecuter.spawn_with_timeout` | Extends [Process.spawn](https://docs.ruby-lang.org/en/3.4/Process.html#method-c-spawn) to run a command and wait (with timeout) for it to finish |
|
88
|
+
| `ProcessExecuter.run` | Extends `spawn_with_timeout` with more flexible redirection and other options. |
|
89
|
+
| `ProcessExecuter.run_with_capture` | Extends `run` with capture of stdout and stderr |
|
90
|
+
|
91
|
+
See the `ProcessExecuter::Error` class for the error architecture for this module.
|
78
92
|
|
79
93
|
### ProcessExecuter::MonitoredPipe
|
80
94
|
|
81
|
-
|
82
|
-
|
83
|
-
|
95
|
+
ℹ️ See [the ProcessExecuter::MonitoredPipe class
|
96
|
+
documentation](https://rubydoc.info/gems/process_executer/ProcessExecuter/MonitoredPipe)
|
97
|
+
for more details and examples of using this class.
|
84
98
|
|
85
|
-
|
86
|
-
the
|
99
|
+
`ProcessExecuter::MonitoredPipe` was created to expand the output redirection options
|
100
|
+
for `Process.spawn` and methods derived from it within the `ProcessExecuter` module.
|
87
101
|
|
88
|
-
|
89
|
-
|
90
|
-
|
102
|
+
This class's initializer accepts any compatible redirection destination supported by
|
103
|
+
`Process.spawn` (this is the `value` part of the file redirection option described in
|
104
|
+
[the File Redirection section of
|
105
|
+
`Process.spawn`](https://docs.ruby-lang.org/en/3.4/Process.html#module-Process-label-File+Redirection+-28File+Descriptor-29).
|
91
106
|
|
92
|
-
|
93
|
-
|
107
|
+
In addition to the standard redirection destinations, `MonitoredPipe` also
|
108
|
+
supports these additional types of destinations:
|
94
109
|
|
95
|
-
|
96
|
-
require 'stringio'
|
97
|
-
require 'process_executer'
|
98
|
-
|
99
|
-
output_buffer = StringIO.new
|
100
|
-
out_pipe = ProcessExecuter::MonitoredPipe.new(output_buffer)
|
101
|
-
pid, status = Process.wait2(Process.spawn('echo "Hello World"', out: out_pipe))
|
102
|
-
out_pipe.close # Close the pipe so all the data is flushed and resources are not leaked
|
103
|
-
output_buffer.string #=> "Hello World\n"
|
104
|
-
```
|
110
|
+
- **Arbitrary writers**
|
105
111
|
|
106
|
-
|
107
|
-
|
108
|
-
for long XML or JSON output.
|
112
|
+
You can redirect subprocess output to any Ruby object that implements the
|
113
|
+
`#write` method. This is particularly useful for:
|
109
114
|
|
110
|
-
|
115
|
+
- capturing command output in in-memory buffers like `StringIO`,
|
116
|
+
- sending command output to custom logging objects that do not have a file descriptor, and
|
117
|
+
- processing with a streaming parser to parse and process command output as the
|
118
|
+
command runs
|
111
119
|
|
112
|
-
|
113
|
-
require 'stringio'
|
114
|
-
require 'process_executer'
|
115
|
-
|
116
|
-
output_buffer = StringIO.new
|
117
|
-
output_file = File.open('process.out', 'w')
|
118
|
-
out_pipe = ProcessExecuter::MonitoredPipe.new([:tee, output_buffer, output_file])
|
119
|
-
pid, status = Process.wait2(Process.spawn('echo "Hello World"', out: out_pipe))
|
120
|
-
out_pipe.close
|
121
|
-
output_file.close
|
122
|
-
output_buffer.string #=> "Hello World\n"
|
123
|
-
File.read('process.out') #=> "Hello World\n"
|
124
|
-
```
|
120
|
+
- **Multiple destinations**
|
125
121
|
|
126
|
-
|
122
|
+
MonitoredPipe supports duplicating (or "teeing") output to multiple
|
123
|
+
destinations simultaneously. This is achieved by providing an array in the
|
124
|
+
format `[:tee, destination1, destination2, ...]`, where each `destination` can
|
125
|
+
be any value that `MonitoredPipe` itself supports (including another tee or
|
126
|
+
MonitoredPipe).
|
127
127
|
|
128
|
-
|
128
|
+
### Encoding
|
129
129
|
|
130
|
-
|
131
|
-
[Process::Status](https://docs.ruby-lang.org/en/3.3/Process/Status.html) so it
|
132
|
-
supports the same interface with the following additions:
|
130
|
+
#### Encoding summary
|
133
131
|
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
wrapped by a `MonitoredPipe`)
|
142
|
-
* `#stderr`: the captured stderr from the subprocess (if the stderr destination was
|
143
|
-
wrapped by a `MonitoredPipe`)
|
132
|
+
The gem's core (`MonitoredPipe`) passes through raw bytes from the subprocess without
|
133
|
+
attempting to interpret or transcode them. `ProcessExecuter.run_with_capture` allows
|
134
|
+
text encodings to be specified for the captured stdout and stderr (defaulting to
|
135
|
+
`UTF-8`). For these outputs, the raw bytes are interpreted as being in that specified
|
136
|
+
encoding. The original byte sequence is preserved and the resulting captured string
|
137
|
+
is tagged with the target encoding. No transcoding between different text encodings
|
138
|
+
(e.g., `Latin-1` to `UTF-8`) is performed.
|
144
139
|
|
145
|
-
|
140
|
+
#### Encoding details
|
146
141
|
|
147
|
-
`ProcessExecuter
|
148
|
-
|
149
|
-
|
142
|
+
`ProcessExecuter::MonitoredPipe` is encoding agnostic. Bytes pass through this class
|
143
|
+
from the subprocesses output to the destination object as a stream of unaltered
|
144
|
+
bytes. No transcoding is applied. Strings written to the destination are tagged for
|
145
|
+
the ASCII-8BIT (aka BINARY) encoding.
|
150
146
|
|
151
|
-
|
152
|
-
|
153
|
-
|
147
|
+
`ProcessExecuter` methods `.spawn_with_timeout`, `.run`, and `.run_with_capture` are
|
148
|
+
also encoding agnostic except with one exception: the user can specify the assumed
|
149
|
+
encoding for strings returned from `ResultWithCapture#stdout` and
|
150
|
+
`ResultWithCapture#stderr`.
|
154
151
|
|
155
|
-
|
156
|
-
`:timeout_after`, the process is killed by sending it the SIGKILL signal. The
|
157
|
-
returned Result object's `timed_out?` attribute will return `true`. For example:
|
152
|
+
As a convenience, the captured output is assumed to be UTF-8 by default:
|
158
153
|
|
159
154
|
```ruby
|
160
|
-
result = ProcessExecuter.
|
161
|
-
result.
|
162
|
-
result.
|
163
|
-
result.timed_out? #=> true
|
155
|
+
result = ProcessExecuter.run_with_capture('pwd')
|
156
|
+
result.stdout #=> "/Users/James/projects/process_executer\n"
|
157
|
+
result.stdout.encoding #=> #<Encoding::UTF-8>
|
164
158
|
```
|
165
159
|
|
166
|
-
|
167
|
-
|
168
|
-
subprocess output from its `#stdout` and `#stderr` methods.
|
160
|
+
You can changed the assumed encoding for the captured stdout and stderr via options
|
161
|
+
passed to `#run_with_capture`:
|
169
162
|
|
170
|
-
|
163
|
+
```ruby
|
164
|
+
# Set the assumed encoding for both stdout and stderr
|
165
|
+
result = ProcessExecuter.run_with_capture('pwd', encoding: Encoding::BINARY)
|
166
|
+
result.stdout #=> "/Users/James/projects/process_executer\n"
|
167
|
+
result.stdout.encoding #=> #<Encoding:BINARY (ASCII-8BIT)>
|
168
|
+
|
169
|
+
# You can set the assumed encoding separately for stdout and stderr
|
170
|
+
# Encoding may be different for each
|
171
|
+
result = ProcessExecuter.run_with_capture('pwd', stdout_encoding: 'BINARY', stderr_encoding: 'UTF-8')
|
172
|
+
result.stdout.encoding #=> #<Encoding:BINARY (ASCII-8BIT)>
|
173
|
+
result.stderr.encoding #=> #<Encoding:UTF-8>
|
174
|
+
```
|
171
175
|
|
172
|
-
|
173
|
-
|
176
|
+
It is possible that the bytes captured are not valid in the given encoding. The user
|
177
|
+
will need to check the `#valid_encoding?` method to know for sure.
|
174
178
|
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
+
```ruby
|
180
|
+
File.binwrite('output.txt', "\xFF\xFE") # little-endian BOM marker is not valid UTF-8
|
181
|
+
result = ProcessExecuter.run_with_capture('cat output.txt')
|
182
|
+
result.stdout #=> "\xFF\xFE"
|
183
|
+
result.stdout.encoding #=> #<Encoding:UTF-8>
|
184
|
+
result.stdout.valid_encoding? #=> false
|
185
|
+
```
|
179
186
|
|
180
|
-
|
181
|
-
|
187
|
+
Encoding options accept any encoding objects returned by `Encoding.list` or their
|
188
|
+
String equivalent given by `#to_s`:
|
182
189
|
|
183
190
|
```ruby
|
184
|
-
|
185
|
-
result.stdout #=> "Hello World\n"
|
191
|
+
Encoding::UTF_8.to_s #=> 'UTF-8'
|
186
192
|
```
|
187
193
|
|
194
|
+
Changing the assumed encoding DOES NOT cause transcoding. It simply interprets the
|
195
|
+
bytes captured as the given encoding.
|
196
|
+
|
197
|
+
These encoding options ONLY affect the internally captured stdout and stderr for
|
198
|
+
`ProcessExecuter::run_with_capture`. If you give an `out:` or `err:` option, these
|
199
|
+
will result in BINARY encoded strings and you will need to handle setting the right
|
200
|
+
encoding or transcoding after collecting the output.
|
201
|
+
|
188
202
|
## Breaking Changes
|
189
203
|
|
190
204
|
### 2.x
|
@@ -193,26 +207,26 @@ This major release focused on changes to the interface to make it more understan
|
|
193
207
|
|
194
208
|
#### `ProcessExecuter.spawn`
|
195
209
|
|
196
|
-
|
197
|
-
|
210
|
+
- This method was renamed to `ProcessExecuter.spawn_with_timeout`
|
211
|
+
- The `:timeout` option was renamed to `:timeout_after`
|
198
212
|
|
199
213
|
#### `ProcessExecuter.run`
|
200
214
|
|
201
|
-
|
215
|
+
- The `:timeout` option was renamed to `:timeout_after`
|
202
216
|
|
203
217
|
#### `ProcessExecuter::Result`
|
204
218
|
|
205
|
-
|
219
|
+
- The `#timeout` method was renamed to `#timed_out`
|
206
220
|
|
207
221
|
#### Other
|
208
222
|
|
209
|
-
|
223
|
+
- Dropped support for Ruby 3.0
|
210
224
|
|
211
225
|
### 3.x
|
212
226
|
|
213
227
|
#### `ProcessExecuter.run`
|
214
228
|
|
215
|
-
|
229
|
+
- The `:merge` option was removed
|
216
230
|
|
217
231
|
This was removed because `Process.spawn` already provides this functionality but in
|
218
232
|
a different way. To merge, you will need to define a redirection where the source
|
@@ -224,7 +238,7 @@ This major release focused on changes to the interface to make it more understan
|
|
224
238
|
|
225
239
|
will merge stdout and stderr from the subprocess into the file output.txt.
|
226
240
|
|
227
|
-
|
241
|
+
- Stdout and stderr redirections no longer default to new instances of `StringIO`
|
228
242
|
|
229
243
|
Calls to `ProcessExecuter.run` that do not define a redirection for stdout or
|
230
244
|
stderr will have to add explicit redirection(s) in order to capture the output.
|
@@ -233,6 +247,35 @@ This major release focused on changes to the interface to make it more understan
|
|
233
247
|
an explicit redirection is not given for stdout and stderr, this output will be
|
234
248
|
passed through to the parent process's stdout and stderr.
|
235
249
|
|
250
|
+
### 4.x
|
251
|
+
|
252
|
+
#### `ProcessExecuter.spawn_and_wait`
|
253
|
+
|
254
|
+
`ProcessExecuter.spawn_and_wait` has been renamed to `ProcessExecuter.spawn_with_timeout`.
|
255
|
+
|
256
|
+
#### `ProcessExecuter::Result`
|
257
|
+
|
258
|
+
`Result#stdout` and `Result#stderr` were removed. Users depending on these methods
|
259
|
+
will either have to capture this output themselves or change from using
|
260
|
+
`.spawn_and_wait`/`.run` to `.run_with_capture` which returns a `ResultWithCapture`
|
261
|
+
object.
|
262
|
+
|
263
|
+
#### `ProcessExecuter.spawn_and_wait_with_options`
|
264
|
+
|
265
|
+
`ProcessExecuter.spawn_and_wait_with_options` has been removed. Instead call
|
266
|
+
`ProcessExecuter.spawn_with_timeout` which is overloaded to take the same method
|
267
|
+
arguments.
|
268
|
+
|
269
|
+
#### `ProcessExecuter.run_with_options`
|
270
|
+
|
271
|
+
`ProcessExecuter.run_with_options` has been removed. Instead call
|
272
|
+
`ProcessExecuter.run` which is overloaded to take the same method arguments.
|
273
|
+
|
274
|
+
#### Other
|
275
|
+
|
276
|
+
In places where users of this gem rescued `::ArgumentError`, they will have to change
|
277
|
+
the rescued class to `ProcessExecuter::ArgumentError`.
|
278
|
+
|
236
279
|
## Installation
|
237
280
|
|
238
281
|
Install the gem and add to the application's Gemfile by executing:
|
@@ -271,11 +314,11 @@ effectively.
|
|
271
314
|
|
272
315
|
To ensure compliance, this project includes:
|
273
316
|
|
274
|
-
|
317
|
+
- A git commit-msg hook that validates your commit messages before they are accepted.
|
275
318
|
|
276
|
-
To activate the hook, you must have
|
319
|
+
To activate the hook, you must have Node.js installed and run `npm install`.
|
277
320
|
|
278
|
-
|
321
|
+
- A GitHub Actions workflow that will enforce the Conventional Commit standard as
|
279
322
|
part of the continuous integration pipeline.
|
280
323
|
|
281
324
|
Any commit message that does not conform to the Conventional Commits standard will
|
@@ -0,0 +1,124 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../errors'
|
4
|
+
require_relative 'spawn_with_timeout'
|
5
|
+
|
6
|
+
module ProcessExecuter
|
7
|
+
module Commands
|
8
|
+
# Run a command and return the {ProcessExecuter::Result}
|
9
|
+
#
|
10
|
+
# Extends {ProcessExecuter::Commands::SpawnWithTimeout} to provide the core functionality for
|
11
|
+
# {ProcessExecuter.run}.
|
12
|
+
#
|
13
|
+
# It accepts all [Process.spawn execution
|
14
|
+
# options](https://docs.ruby-lang.org/en/3.4/Process.html#module-Process-label-Execution+Options)
|
15
|
+
# plus the additional options `timeout_after`, `raise_errors` and `logger`.
|
16
|
+
#
|
17
|
+
# This class wraps any stdout or stderr redirection destinations in a {MonitoredPipe}.
|
18
|
+
# This allows any class that implements `#write` to be used as an output redirection
|
19
|
+
# destination. This means that you can redirect to a StringIO which is not possible
|
20
|
+
# with `Process.spawn`.
|
21
|
+
#
|
22
|
+
# @api private
|
23
|
+
#
|
24
|
+
class Run < SpawnWithTimeout
|
25
|
+
# Run a command and return the result
|
26
|
+
#
|
27
|
+
# Wrap the stdout and stderr redirection destinations in pipes and then execute
|
28
|
+
# the command.
|
29
|
+
#
|
30
|
+
# @example
|
31
|
+
# options = ProcessExecuter::Options::RunOptions.new(raise_errors: true)
|
32
|
+
# result = ProcessExecuter::Commands::Run.new('echo hello', options).call
|
33
|
+
# result.success? # => true
|
34
|
+
# result.exitstatus # => 0
|
35
|
+
#
|
36
|
+
# @raise [ProcessExecuter::SpawnError] `Process.spawn` raised an error before the
|
37
|
+
# command was run
|
38
|
+
#
|
39
|
+
# @raise [ProcessExecuter::FailedError] If the command ran and failed
|
40
|
+
#
|
41
|
+
# @raise [ProcessExecuter::SignaledError] If the command ran and terminated due to
|
42
|
+
# an unhandled signal
|
43
|
+
#
|
44
|
+
# @raise [ProcessExecuter::TimeoutError] If the command timed out
|
45
|
+
#
|
46
|
+
# @raise [ProcessExecuter::ProcessIOError] If there was an exception while
|
47
|
+
# collecting subprocess output
|
48
|
+
#
|
49
|
+
# @return [ProcessExecuter::Result] The result of the completed subprocess
|
50
|
+
#
|
51
|
+
def call
|
52
|
+
opened_pipes = wrap_stdout_stderr
|
53
|
+
super.tap do
|
54
|
+
log_result
|
55
|
+
raise_errors if options.raise_errors
|
56
|
+
end
|
57
|
+
ensure
|
58
|
+
opened_pipes.each_value(&:close)
|
59
|
+
opened_pipes.each { |option_key, pipe| raise_pipe_error(option_key, pipe) }
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
# Wrap the stdout and stderr redirection options with a MonitoredPipe
|
65
|
+
# @return [Hash<Object, ProcessExecuter::MonitoredPipe>] The opened pipes (the Object is the option key)
|
66
|
+
def wrap_stdout_stderr
|
67
|
+
options.each_with_object({}) do |key_value, opened_pipes|
|
68
|
+
key, value = key_value
|
69
|
+
|
70
|
+
next unless should_wrap?(key, value)
|
71
|
+
|
72
|
+
wrapped_destination = ProcessExecuter::MonitoredPipe.new(value)
|
73
|
+
opened_pipes[key] = wrapped_destination
|
74
|
+
options.merge!({ key => wrapped_destination })
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Should the redirection option be wrapped by a MonitoredPipe
|
79
|
+
# @param key [Object] The option key
|
80
|
+
# @param value [Object] The option value
|
81
|
+
# @return [Boolean] Whether the option should be wrapped
|
82
|
+
def should_wrap?(key, value)
|
83
|
+
(options.stdout_redirection?(key) || options.stderr_redirection?(key)) &&
|
84
|
+
ProcessExecuter::Destinations.compatible_with_monitored_pipe?(value)
|
85
|
+
end
|
86
|
+
|
87
|
+
# Raise an error if the command failed
|
88
|
+
# @return [void]
|
89
|
+
# @raise [ProcessExecuter::FailedError] If the command ran and failed
|
90
|
+
# @raise [ProcessExecuter::SignaledError] If the command ran and terminated due to an unhandled signal
|
91
|
+
# @raise [ProcessExecuter::TimeoutError] If the command timed out
|
92
|
+
def raise_errors
|
93
|
+
raise TimeoutError, result if result.timed_out?
|
94
|
+
raise SignaledError, result if result.signaled?
|
95
|
+
raise FailedError, result unless result.success?
|
96
|
+
end
|
97
|
+
|
98
|
+
# Log the result of running the command
|
99
|
+
# @return [void]
|
100
|
+
def log_result
|
101
|
+
options.logger.info { "PID #{pid}: #{command} exited with status #{result}" }
|
102
|
+
end
|
103
|
+
|
104
|
+
# Raises a ProcessIOError if the given pipe has a recorded exception
|
105
|
+
#
|
106
|
+
# @param option_key [Object] The redirection option key
|
107
|
+
#
|
108
|
+
# For example, `:out`, or an Array like `[:out, :err]` for merged streams.
|
109
|
+
#
|
110
|
+
# @param pipe [ProcessExecuter::MonitoredPipe] The pipe that raised the exception
|
111
|
+
#
|
112
|
+
# @raise [ProcessExecuter::ProcessIOError] If there was an exception while collecting subprocess output
|
113
|
+
#
|
114
|
+
# @return [void]
|
115
|
+
#
|
116
|
+
def raise_pipe_error(option_key, pipe)
|
117
|
+
return unless pipe.exception
|
118
|
+
|
119
|
+
error = ProcessExecuter::ProcessIOError.new("Pipe Exception for #{command}: #{option_key.inspect}")
|
120
|
+
raise(error, cause: pipe.exception)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|