process_executer 1.3.0 → 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/CHANGELOG.md +31 -0
- data/README.md +171 -61
- data/lib/process_executer/destination_base.rb +83 -0
- data/lib/process_executer/destinations/child_redirection.rb +23 -0
- data/lib/process_executer/destinations/close.rb +23 -0
- data/lib/process_executer/destinations/file_descriptor.rb +36 -0
- data/lib/process_executer/destinations/file_path.rb +56 -0
- data/lib/process_executer/destinations/file_path_mode.rb +60 -0
- data/lib/process_executer/destinations/file_path_mode_perms.rb +61 -0
- data/lib/process_executer/destinations/io.rb +33 -0
- data/lib/process_executer/destinations/monitored_pipe.rb +39 -0
- data/lib/process_executer/destinations/stderr.rb +31 -0
- data/lib/process_executer/destinations/stdout.rb +31 -0
- data/lib/process_executer/destinations/tee.rb +60 -0
- data/lib/process_executer/destinations/writer.rb +33 -0
- data/lib/process_executer/destinations.rb +70 -0
- data/lib/process_executer/errors.rb +134 -0
- data/lib/process_executer/monitored_pipe.rb +40 -57
- data/lib/process_executer/options/base.rb +240 -0
- data/lib/process_executer/options/option_definition.rb +56 -0
- data/lib/process_executer/options/run_options.rb +48 -0
- data/lib/process_executer/options/spawn_and_wait_options.rb +39 -0
- data/lib/process_executer/options/spawn_options.rb +143 -0
- data/lib/process_executer/options.rb +7 -163
- data/lib/process_executer/result.rb +150 -0
- data/lib/process_executer/runner.rb +155 -0
- data/lib/process_executer/version.rb +1 -1
- data/lib/process_executer.rb +254 -93
- metadata +27 -14
- data/.tool-versions +0 -1
- data/lib/process_executer/command/errors.rb +0 -170
- data/lib/process_executer/command/result.rb +0 -77
- data/lib/process_executer/command/runner.rb +0 -167
- data/lib/process_executer/command.rb +0 -12
- data/lib/process_executer/status.rb +0 -70
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a13a780c8c9064d19873068266b40be2a2a8ba7fe1d46866d6e8cb15806d5e53
|
4
|
+
data.tar.gz: e727aab59452dac6ef74819bff2462ad0e1eb56c5b0c1ff1f7e018aed5dbdf77
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6e349893ee5fbf19410e35e2a3a43907ccb1b1610f266788e0bd01b972ccc0e875fa2df95a9f9b90aefa0d8910f535a9d2ad34432332df07dbd58a33d299169d
|
7
|
+
data.tar.gz: 29d7455610df17c93b8616fcff42fe44077f46de3e855eba573b45071e8bd51322240763529e2c5b578191ff20ada709073bac2989e6bbfcca20e0687fac09f5
|
data/CHANGELOG.md
CHANGED
@@ -5,6 +5,37 @@ 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
|
+
## v3.0.0 (2025-03-18)
|
9
|
+
|
10
|
+
[Full Changelog](https://github.com/main-branch/process_executer/compare/v2.0.0..v3.0.0)
|
11
|
+
|
12
|
+
Changes since v2.0.0:
|
13
|
+
|
14
|
+
* 3d337de feat: remove Options setter methods and add `with` method
|
15
|
+
* 706d78a docs: add a list the breaking changes for each major release in the README.md
|
16
|
+
* 2903c80 feat: report all option errors instead of just the first one
|
17
|
+
* 247150d feat!: do not capture stdout and stderr by default in `ProcessExecuter.run`
|
18
|
+
* 4b3ac02 feat: support redirection destinations in the form [:child, fd] and :close
|
19
|
+
* ed4620f docs: update README.md to highlight the important parts of this gem
|
20
|
+
* 48b4695 fix: allow Integer or IO are used as a redirection source
|
21
|
+
* 4424a44 feat!: remove the :merge option from ProcessExecuter.run
|
22
|
+
* 7257e5d chore: allow SpawnOptions to accept Integer and IO redirection sources
|
23
|
+
* 92441d0 chore: move all options related classes to a new Options module
|
24
|
+
* 92c096c chore: remove unneeded test file
|
25
|
+
* 91d0db3 feat: implement all possible redirection destinations
|
26
|
+
* a58af4a fix: fix complexity error reported by CodeClimate
|
27
|
+
* 66d97b7 chore: do not fail the CI build for low coverage on JRuby and TruffleRuby
|
28
|
+
* 2fb0ccf feat: refactor options classes
|
29
|
+
* bcf35d5 chore: do not fail the CI build for low coverage on JRuby and TruffleRuby
|
30
|
+
|
31
|
+
## v2.0.0 (2025-03-03)
|
32
|
+
|
33
|
+
[Full Changelog](https://github.com/main-branch/process_executer/compare/v1.3.0..v2.0.0)
|
34
|
+
|
35
|
+
Changes since v1.3.0:
|
36
|
+
|
37
|
+
* f0836cc feat: refactor the interface to simplify the gem
|
38
|
+
|
8
39
|
## v1.3.0 (2025-02-26)
|
9
40
|
|
10
41
|
[Full Changelog](https://github.com/main-branch/process_executer/compare/v1.2.0..v1.3.0)
|
data/README.md
CHANGED
@@ -10,68 +10,89 @@
|
|
10
10
|
Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-%23FE5196?logo=conventionalcommits&logoColor=white)](https://conventionalcommits.org)
|
11
11
|
[](https://main-branch.slack.com/archives/C07NG2BPG8Y)
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
* [ProcessExecuter::MonitoredPipe](#processexecutermonitoredpipe)
|
16
|
-
* [ProcessExecuter.spawn](#processexecuterspawn)
|
17
|
-
* [Installation](#installation)
|
18
|
-
* [Contributing](#contributing)
|
19
|
-
* [Reporting Issues](#reporting-issues)
|
20
|
-
* [Developing](#developing)
|
21
|
-
* [Commit message guidelines](#commit-message-guidelines)
|
22
|
-
* [Pull request guidelines](#pull-request-guidelines)
|
23
|
-
* [Releasing](#releasing)
|
24
|
-
* [License](#license)
|
25
|
-
|
26
|
-
## Usage
|
13
|
+
ProcessExecuter provides an enhanced API for executing commands in subprocesses,
|
14
|
+
extending Ruby's built-in `Process.spawn` functionality.
|
27
15
|
|
28
|
-
|
29
|
-
|
16
|
+
It has additional features like capturing output, handling timeouts, streaming output
|
17
|
+
to multiple destinations, and providing detailed result information.
|
30
18
|
|
31
|
-
This
|
19
|
+
This README documents the HEAD version of process_executer which may contain
|
20
|
+
unrelease information. To see the README for the version you are using, consult
|
21
|
+
RubyGems.org. Go to the [process_executer page in
|
22
|
+
RubyGems.org](https://rubygems.org/gems/process_executer), select your version, and
|
23
|
+
then click the "Documentation" link.
|
32
24
|
|
33
|
-
|
34
|
-
|
35
|
-
`ProcessExecuter.run` execute the given command as a subprocess blocking until it is finished.
|
25
|
+
## Requirements
|
36
26
|
|
37
|
-
|
27
|
+
* Ruby 3.1.0 or later
|
28
|
+
* Compatible with MRI 3.1+, TruffleRuby 24+, and JRuby 9.4+
|
29
|
+
* Works on Mac, Linux, and Windows platforms
|
38
30
|
|
39
|
-
|
40
|
-
[Process.spawn](https://docs.ruby-lang.org/en/3.3/Process.html#method-c-spawn).
|
41
|
-
In addition, it (1) blocks until the command has exited, (2) captures stdout and
|
42
|
-
stderr to a buffer or file, and (3) can optionally kill the command if it exceeds
|
43
|
-
an timeout.
|
31
|
+
## Table of Contents
|
44
32
|
|
45
|
-
|
33
|
+
* [Requirements](#requirements)
|
34
|
+
* [Table of Contents](#table-of-contents)
|
35
|
+
* [Usage](#usage)
|
36
|
+
* [ProcessExecuter::MonitoredPipe](#processexecutermonitoredpipe)
|
37
|
+
* [ProcessExecuter::Result](#processexecuterresult)
|
38
|
+
* [ProcessExecuter.spawn\_and\_wait](#processexecuterspawn_and_wait)
|
39
|
+
* [ProcessExecuter.run](#processexecuterrun)
|
40
|
+
* [Breaking Changes](#breaking-changes)
|
41
|
+
* [2.x](#2x)
|
42
|
+
* [`ProcessExecuter.spawn`](#processexecuterspawn)
|
43
|
+
* [`ProcessExecuter.run`](#processexecuterrun-1)
|
44
|
+
* [`ProcessExecuter::Result`](#processexecuterresult-1)
|
45
|
+
* [Other](#other)
|
46
|
+
* [3.x](#3x)
|
47
|
+
* [`ProcessExecuter.run`](#processexecuterrun-2)
|
48
|
+
* [Installation](#installation)
|
49
|
+
* [Contributing](#contributing)
|
50
|
+
* [Reporting Issues](#reporting-issues)
|
51
|
+
* [Developing](#developing)
|
52
|
+
* [Commit message guidelines](#commit-message-guidelines)
|
53
|
+
* [Pull request guidelines](#pull-request-guidelines)
|
54
|
+
* [Releasing](#releasing)
|
55
|
+
* [License](#license)
|
46
56
|
|
47
|
-
|
57
|
+
## Usage
|
48
58
|
|
49
|
-
|
59
|
+
[Full YARD documentation](https://rubydoc.info/gems/process_executer/) for this gem
|
60
|
+
is hosted on RubyGems.org. Read below for an overview and several examples.
|
50
61
|
|
51
|
-
|
62
|
+
This gem contains two public classes and two public methods:
|
52
63
|
|
53
|
-
|
64
|
+
Classes:
|
54
65
|
|
55
|
-
|
56
|
-
|
57
|
-
|
66
|
+
* `ProcessExecuter::MonitoredPipe`: allows use of any object with a `#write` method
|
67
|
+
or an array of objects as a redirection destination in `Process.spawn`
|
68
|
+
* `ProcessExecuter::Result`: an extension of `Process::Status` that includes more
|
69
|
+
information about the subprocess including timeout status, the command that was
|
70
|
+
run, the subprocess options given, and (in some cases) stdout and stderr captured
|
71
|
+
from the subprocess.
|
58
72
|
|
59
|
-
|
73
|
+
Methods:
|
60
74
|
|
61
|
-
|
75
|
+
* `ProcessExecuter.spawn_and_wait`: execute a subprocess and wait for it to exit with
|
76
|
+
an optional timeout. Supports the same interface and features as `Process.spawn`.
|
77
|
+
* `ProcessExecuter.run`: builds upon `.spawn_and_wait` adding (1) automatically
|
78
|
+
wrapping stdout and stderr destinations (if given) in a `MonitoredPipe` and (2)
|
79
|
+
raises errors for any problem executing the subprocess (can be turned off).
|
62
80
|
|
63
81
|
### ProcessExecuter::MonitoredPipe
|
64
82
|
|
65
|
-
`ProcessExecuter::MonitoredPipe`
|
83
|
+
`ProcessExecuter::MonitoredPipe` objects can be used as a redirection destination for
|
84
|
+
`Process.spawn` to stream output from a subprocess to one or more destinations.
|
85
|
+
Destinations are given in this class's initializer.
|
66
86
|
|
67
|
-
|
68
|
-
|
87
|
+
The destinations are all the redirection destinations allowed by `Process.spawn` plus
|
88
|
+
the following:
|
69
89
|
|
70
|
-
|
71
|
-
|
90
|
+
* Any object with a #write method even if it does not have a file descriptor (like
|
91
|
+
instances of StringIO)
|
92
|
+
* An array of destinations so that output can be tee'd to several sources
|
72
93
|
|
73
|
-
|
74
|
-
|
94
|
+
Example of capturing stdout to a StringIO (which is not directly possible with
|
95
|
+
`Process.spawn`):
|
75
96
|
|
76
97
|
```ruby
|
77
98
|
require 'stringio'
|
@@ -80,11 +101,15 @@ require 'process_executer'
|
|
80
101
|
output_buffer = StringIO.new
|
81
102
|
out_pipe = ProcessExecuter::MonitoredPipe.new(output_buffer)
|
82
103
|
pid, status = Process.wait2(Process.spawn('echo "Hello World"', out: out_pipe))
|
104
|
+
out_pipe.close # Close the pipe so all the data is flushed and resources are not leaked
|
83
105
|
output_buffer.string #=> "Hello World\n"
|
84
106
|
```
|
85
107
|
|
86
|
-
|
87
|
-
|
108
|
+
Any object that implements `#write` can be used as a destination (not just StringIO).
|
109
|
+
For instance, you can use it to parse process output as a stream which might be useful
|
110
|
+
for long XML or JSON output.
|
111
|
+
|
112
|
+
Example of tee'ing stdout to multiple destinations:
|
88
113
|
|
89
114
|
```ruby
|
90
115
|
require 'stringio'
|
@@ -92,36 +117,121 @@ require 'process_executer'
|
|
92
117
|
|
93
118
|
output_buffer = StringIO.new
|
94
119
|
output_file = File.open('process.out', 'w')
|
95
|
-
out_pipe = ProcessExecuter::MonitoredPipe.new(output_buffer, output_file)
|
120
|
+
out_pipe = ProcessExecuter::MonitoredPipe.new([:tee, output_buffer, output_file])
|
96
121
|
pid, status = Process.wait2(Process.spawn('echo "Hello World"', out: out_pipe))
|
122
|
+
out_pipe.close
|
97
123
|
output_file.close
|
98
124
|
output_buffer.string #=> "Hello World\n"
|
99
125
|
File.read('process.out') #=> "Hello World\n"
|
100
126
|
```
|
101
127
|
|
102
|
-
|
103
|
-
|
104
|
-
|
128
|
+
### ProcessExecuter::Result
|
129
|
+
|
130
|
+
An instance of this class is returned from both `.spawn_and_wait` and `.run`.
|
105
131
|
|
106
|
-
|
132
|
+
This class is an extension of
|
133
|
+
[Process::Status](https://docs.ruby-lang.org/en/3.3/Process/Status.html) so it
|
134
|
+
supports the same interface with the following additions:
|
107
135
|
|
108
|
-
|
109
|
-
|
136
|
+
* `#command`: the command given to `.spawn_and_wait` or `.run`
|
137
|
+
* `#options`: the options given to `.spawn_and_wait` or `.run` (possibly with some
|
138
|
+
changes)
|
139
|
+
* `#timed_out?`: true if the process was killed after running for `:timeout_after`
|
140
|
+
seconds
|
141
|
+
* `#elapsed_time`: the number of seconds the process was running
|
142
|
+
* `#stdout`: the captured stdout from the subprocess (if the stdout destination was
|
143
|
+
wrapped by a `MonitoredPipe`)
|
144
|
+
* `#stderr`: the captured stderr from the subprocess (if the stderr destination was
|
145
|
+
wrapped by a `MonitoredPipe`)
|
110
146
|
|
111
|
-
|
112
|
-
2. A timeout can be specified using the `:timeout` option
|
147
|
+
### ProcessExecuter.spawn_and_wait
|
113
148
|
|
114
|
-
|
115
|
-
|
116
|
-
|
149
|
+
`ProcessExecuter.spawn_and_wait` has the same interface and features as
|
150
|
+
[Process.spawn](https://docs.ruby-lang.org/en/3.3/Process.html#method-c-spawn)
|
151
|
+
with the following differences:
|
152
|
+
|
153
|
+
1. It waits for the subprocess to exit
|
154
|
+
2. A timeout can be specified using the `:timeout_after` option
|
155
|
+
3. It returns a `ProcessExecuter::Result` instead of a `Process::Status`
|
156
|
+
|
157
|
+
If the command does not terminate before the number of seconds specified by
|
158
|
+
`:timeout_after`, the process is killed by sending it the SIGKILL signal. The
|
159
|
+
returned Result object's `timed_out?` attribute will return `true`. For example:
|
117
160
|
|
118
161
|
```ruby
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
162
|
+
result = ProcessExecuter.spawn_and_wait('sleep 10', timeout_after: 0.01)
|
163
|
+
result.signaled? #=> true
|
164
|
+
result.termsig #=> 9
|
165
|
+
result.timed_out? #=> true
|
123
166
|
```
|
124
167
|
|
168
|
+
If the destination for stdout and stderr are wrapped by a
|
169
|
+
ProcessExecuter::MonitoredPipe, the result will return the stdout and stderr
|
170
|
+
subprocess output from its `#stdout` and `#stderr` methods.
|
171
|
+
|
172
|
+
### ProcessExecuter.run
|
173
|
+
|
174
|
+
`ProcessExecuter.run` builds upon `ProcessExecuter.spawn_and_wait` adding the
|
175
|
+
following features:
|
176
|
+
|
177
|
+
* It automatically wraps any given stdout and stderr destination with a
|
178
|
+
MonitoredPipe. The pipe will be closed when the command exits.
|
179
|
+
* It raises an error if there is any problem with the subprocess. This behavior can
|
180
|
+
be turned off with the `raise_errors: false` option.
|
181
|
+
|
182
|
+
```ruby
|
183
|
+
result = ProcessExecuter.run('echo "Hello World"', out: StringIO.new)
|
184
|
+
result.stdout #=> "Hello World\n"
|
185
|
+
```
|
186
|
+
|
187
|
+
## Breaking Changes
|
188
|
+
|
189
|
+
### 2.x
|
190
|
+
|
191
|
+
This major release focused on changes to the interface to make it more understandable.
|
192
|
+
|
193
|
+
#### `ProcessExecuter.spawn`
|
194
|
+
|
195
|
+
* This method was renamed to `ProcessExecuter.spawn_and_wait`
|
196
|
+
* The `:timeout` option was renamed to `:timeout_after`
|
197
|
+
|
198
|
+
#### `ProcessExecuter.run`
|
199
|
+
|
200
|
+
* The `:timeout` option was renamed to `:timeout_after`
|
201
|
+
|
202
|
+
#### `ProcessExecuter::Result`
|
203
|
+
|
204
|
+
* The `#timeout` method was renamed to `#timed_out`
|
205
|
+
|
206
|
+
#### Other
|
207
|
+
|
208
|
+
* Dropped support for Ruby 3.0
|
209
|
+
|
210
|
+
### 3.x
|
211
|
+
|
212
|
+
#### `ProcessExecuter.run`
|
213
|
+
|
214
|
+
* The `:merge` option was removed
|
215
|
+
|
216
|
+
This was removed because `Process.spawn` already provides this functionality but in
|
217
|
+
a different way. To merge, you will need to define a redirection where the source
|
218
|
+
is an array of the file descriptors you want to merge. For instance:
|
219
|
+
|
220
|
+
```Ruby
|
221
|
+
[:out, :err] => 'output.txt'
|
222
|
+
```
|
223
|
+
|
224
|
+
will merge stdout and stderr from the subprocess into the file output.txt.
|
225
|
+
|
226
|
+
* Stdout and stderr redirections are no longer default to a new instance of StringIO
|
227
|
+
|
228
|
+
Calls to `ProcessExecuter.run` that do not define a redirection for stdout or
|
229
|
+
stderr will have to add explicit redirection(s) in order to capture the output.
|
230
|
+
|
231
|
+
This is to align with the functionality in `Process.spawn`. In `Process.spawn`, when
|
232
|
+
an explicit redirection is not given for stdout and stderr, this output will be
|
233
|
+
passed through to the parent process's stdout and stderr.
|
234
|
+
|
125
235
|
## Installation
|
126
236
|
|
127
237
|
Install the gem and add to the application's Gemfile by executing:
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ProcessExecuter
|
4
|
+
# Base class for all destination handlers
|
5
|
+
#
|
6
|
+
# Provides the common interface and functionality for all destination
|
7
|
+
# classes that handle different types of output redirection.
|
8
|
+
#
|
9
|
+
# @api private
|
10
|
+
class DestinationBase
|
11
|
+
# Initializes a new destination handler
|
12
|
+
#
|
13
|
+
# @param destination [Object] the destination to write to
|
14
|
+
# @return [DestinationBase] a new destination handler instance
|
15
|
+
def initialize(destination)
|
16
|
+
@destination = destination
|
17
|
+
@data_written = []
|
18
|
+
end
|
19
|
+
|
20
|
+
# The destination object this handler manages
|
21
|
+
#
|
22
|
+
# @return [Object] the destination object
|
23
|
+
attr_reader :destination
|
24
|
+
|
25
|
+
# The data written to the destination
|
26
|
+
#
|
27
|
+
# @return [Array<String>] the data written to the destination
|
28
|
+
attr_reader :data_written
|
29
|
+
|
30
|
+
# The data written to the destination as a single string
|
31
|
+
# @return [String]
|
32
|
+
def string
|
33
|
+
data_written.join
|
34
|
+
end
|
35
|
+
|
36
|
+
# Writes data to the destination
|
37
|
+
#
|
38
|
+
# This is an abstract method that must be implemented by subclasses.
|
39
|
+
#
|
40
|
+
# @param data [String] the data to write
|
41
|
+
# @return [void]
|
42
|
+
# @raise [NotImplementedError] if the subclass doesn't implement this method
|
43
|
+
def write(data)
|
44
|
+
@data_written << data
|
45
|
+
end
|
46
|
+
|
47
|
+
# Closes the destination if necessary
|
48
|
+
#
|
49
|
+
# By default, this method does nothing. Subclasses should override
|
50
|
+
# this method if they need to perform cleanup.
|
51
|
+
#
|
52
|
+
# @return [void]
|
53
|
+
def close; end
|
54
|
+
|
55
|
+
# Determines if this class can handle the given destination
|
56
|
+
#
|
57
|
+
# This is an abstract class method that must be implemented by subclasses.
|
58
|
+
#
|
59
|
+
# @param destination [Object] the destination to check
|
60
|
+
# @return [Boolean] true if this class can handle the destination
|
61
|
+
# @raise [NotImplementedError] if the subclass doesn't implement this method
|
62
|
+
def self.handles?(destination)
|
63
|
+
raise NotImplementedError
|
64
|
+
end
|
65
|
+
|
66
|
+
# Determines if this destination class can be wrapped by MonitoredPipe
|
67
|
+
#
|
68
|
+
# All destination types can be wrapped by MonitoredPipe unless they explicitly
|
69
|
+
# opt out.
|
70
|
+
#
|
71
|
+
# @return [Boolean]
|
72
|
+
# @api private
|
73
|
+
def self.compatible_with_monitored_pipe? = true
|
74
|
+
|
75
|
+
# Determines if this destination instance can be wrapped by MonitoredPipe
|
76
|
+
#
|
77
|
+
# @return [Boolean]
|
78
|
+
# @api private
|
79
|
+
def compatible_with_monitored_pipe?
|
80
|
+
self.class.compatible_with_monitored_pipe?
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ProcessExecuter
|
4
|
+
module Destinations
|
5
|
+
# Handles generic objects that respond to write
|
6
|
+
#
|
7
|
+
# @api private
|
8
|
+
class ChildRedirection < ProcessExecuter::DestinationBase
|
9
|
+
# Determines if this class can handle the given destination
|
10
|
+
#
|
11
|
+
# @param destination [Object] the destination to check
|
12
|
+
# @return [Boolean] true if destination responds to write but is not an IO with fileno
|
13
|
+
def self.handles?(destination)
|
14
|
+
destination.is_a?(Array) && destination.size == 2 && destination[0] == :child
|
15
|
+
end
|
16
|
+
|
17
|
+
# This class should not be wrapped in a monitored pipe
|
18
|
+
# @return [Boolean]
|
19
|
+
# @api private
|
20
|
+
def self.compatible_with_monitored_pipe? = false
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ProcessExecuter
|
4
|
+
module Destinations
|
5
|
+
# Handles generic objects that respond to write
|
6
|
+
#
|
7
|
+
# @api private
|
8
|
+
class Close < ProcessExecuter::DestinationBase
|
9
|
+
# Determines if this class can handle the given destination
|
10
|
+
#
|
11
|
+
# @param destination [Object] the destination to check
|
12
|
+
# @return [Boolean] true if destination responds to write but is not an IO with fileno
|
13
|
+
def self.handles?(destination)
|
14
|
+
destination == :close
|
15
|
+
end
|
16
|
+
|
17
|
+
# This class should not be wrapped in a monitored pipe
|
18
|
+
# @return [Boolean]
|
19
|
+
# @api private
|
20
|
+
def self.compatible_with_monitored_pipe? = false
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'process_executer/destination_base'
|
4
|
+
|
5
|
+
module ProcessExecuter
|
6
|
+
module Destinations
|
7
|
+
# Handles numeric file descriptors
|
8
|
+
#
|
9
|
+
# @api private
|
10
|
+
class FileDescriptor < ProcessExecuter::DestinationBase
|
11
|
+
# Writes data to the file descriptor
|
12
|
+
#
|
13
|
+
# @param data [String] the data to write
|
14
|
+
# @return [Integer] the number of bytes written
|
15
|
+
# @raise [SystemCallError] if the file descriptor is invalid
|
16
|
+
#
|
17
|
+
# @example
|
18
|
+
# fd_handler = ProcessExecuter::Destinations::FileDescriptor.new(3)
|
19
|
+
# fd_handler.write("Hello world")
|
20
|
+
def write(data)
|
21
|
+
super
|
22
|
+
io = ::IO.open(destination, mode: 'a', autoclose: false)
|
23
|
+
io.write(data)
|
24
|
+
io.close
|
25
|
+
end
|
26
|
+
|
27
|
+
# Determines if this class can handle the given destination
|
28
|
+
#
|
29
|
+
# @param destination [Object] the destination to check
|
30
|
+
# @return [Boolean] true if destination is an Integer that's not stdout/stderr
|
31
|
+
def self.handles?(destination)
|
32
|
+
destination.is_a?(Integer) && ![:out, 1, :err, 2].include?(destination)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ProcessExecuter
|
4
|
+
module Destinations
|
5
|
+
# Handles file path destinations
|
6
|
+
#
|
7
|
+
# @api private
|
8
|
+
class FilePath < ProcessExecuter::DestinationBase
|
9
|
+
# Initializes a new file path destination handler
|
10
|
+
#
|
11
|
+
# Opens the file at the given path for writing.
|
12
|
+
#
|
13
|
+
# @param destination [String] the file path to write to
|
14
|
+
# @return [FilePath] a new file path destination handler
|
15
|
+
# @raise [Errno::ENOENT] if the file path is invalid
|
16
|
+
def initialize(destination)
|
17
|
+
super
|
18
|
+
@file = File.open(destination, 'w', 0o644)
|
19
|
+
end
|
20
|
+
|
21
|
+
# The opened file object
|
22
|
+
#
|
23
|
+
# @return [File] the opened file
|
24
|
+
attr_reader :file
|
25
|
+
|
26
|
+
# Writes data to the file
|
27
|
+
#
|
28
|
+
# @param data [String] the data to write
|
29
|
+
# @return [Integer] the number of bytes written
|
30
|
+
# @raise [IOError] if the file is closed
|
31
|
+
#
|
32
|
+
# @example
|
33
|
+
# file_handler = ProcessExecuter::Destinations::FilePath.new("output.log")
|
34
|
+
# file_handler.write("Log entry")
|
35
|
+
def write(data)
|
36
|
+
super
|
37
|
+
file.write data
|
38
|
+
end
|
39
|
+
|
40
|
+
# Closes the file if it's open
|
41
|
+
#
|
42
|
+
# @return [void]
|
43
|
+
def close
|
44
|
+
file.close unless file.closed?
|
45
|
+
end
|
46
|
+
|
47
|
+
# Determines if this class can handle the given destination
|
48
|
+
#
|
49
|
+
# @param destination [Object] the destination to check
|
50
|
+
# @return [Boolean] true if destination is a String
|
51
|
+
def self.handles?(destination)
|
52
|
+
destination.is_a? String
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ProcessExecuter
|
4
|
+
module Destinations
|
5
|
+
# Handles file paths with specific open modes
|
6
|
+
#
|
7
|
+
# @api private
|
8
|
+
class FilePathMode < ProcessExecuter::DestinationBase
|
9
|
+
# Initializes a new file path with mode destination handler
|
10
|
+
#
|
11
|
+
# Opens the file at the given path with the specified mode.
|
12
|
+
#
|
13
|
+
# @param destination [Array<String, String>] array with file path and mode
|
14
|
+
# @return [FilePathMode] a new file path with mode destination handler
|
15
|
+
# @raise [Errno::ENOENT] if the file path is invalid
|
16
|
+
# @raise [ArgumentError] if the mode is invalid
|
17
|
+
def initialize(destination)
|
18
|
+
super
|
19
|
+
@file = File.open(destination[0], destination[1], 0o644)
|
20
|
+
end
|
21
|
+
|
22
|
+
# The opened file object
|
23
|
+
#
|
24
|
+
# @return [File] the opened file
|
25
|
+
attr_reader :file
|
26
|
+
|
27
|
+
# Writes data to the file
|
28
|
+
#
|
29
|
+
# @param data [String] the data to write
|
30
|
+
# @return [Integer] the number of bytes written
|
31
|
+
# @raise [IOError] if the file is closed
|
32
|
+
#
|
33
|
+
# @example
|
34
|
+
# mode_handler = ProcessExecuter::Destinations::FilePathMode.new(["output.log", "a"])
|
35
|
+
# mode_handler.write("Appended log entry")
|
36
|
+
def write(data)
|
37
|
+
super
|
38
|
+
file.write data
|
39
|
+
end
|
40
|
+
|
41
|
+
# Closes the file if it's open
|
42
|
+
#
|
43
|
+
# @return [void]
|
44
|
+
def close
|
45
|
+
file.close unless file.closed?
|
46
|
+
end
|
47
|
+
|
48
|
+
# Determines if this class can handle the given destination
|
49
|
+
#
|
50
|
+
# @param destination [Object] the destination to check
|
51
|
+
# @return [Boolean] true if destination is an Array with path and mode
|
52
|
+
def self.handles?(destination)
|
53
|
+
destination.is_a?(Array) &&
|
54
|
+
destination.size == 2 &&
|
55
|
+
destination[0].is_a?(String) &&
|
56
|
+
destination[1].is_a?(String)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|