process_executer 3.2.3 → 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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.commitlintrc.yml +27 -6
  3. data/.release-please-manifest.json +1 -1
  4. data/CHANGELOG.md +49 -0
  5. data/README.md +177 -134
  6. data/lib/process_executer/commands/run.rb +124 -0
  7. data/lib/process_executer/commands/run_with_capture.rb +148 -0
  8. data/lib/process_executer/commands/spawn_with_timeout.rb +163 -0
  9. data/lib/process_executer/commands.rb +11 -0
  10. data/lib/process_executer/destinations/child_redirection.rb +5 -4
  11. data/lib/process_executer/destinations/close.rb +5 -4
  12. data/lib/process_executer/destinations/destination_base.rb +73 -0
  13. data/lib/process_executer/destinations/file_descriptor.rb +10 -6
  14. data/lib/process_executer/destinations/file_path.rb +12 -6
  15. data/lib/process_executer/destinations/file_path_mode.rb +10 -6
  16. data/lib/process_executer/destinations/file_path_mode_perms.rb +12 -5
  17. data/lib/process_executer/destinations/io.rb +10 -5
  18. data/lib/process_executer/destinations/monitored_pipe.rb +10 -5
  19. data/lib/process_executer/destinations/stderr.rb +8 -4
  20. data/lib/process_executer/destinations/stdout.rb +8 -4
  21. data/lib/process_executer/destinations/tee.rb +24 -17
  22. data/lib/process_executer/destinations/writer.rb +12 -7
  23. data/lib/process_executer/destinations.rb +32 -17
  24. data/lib/process_executer/errors.rb +50 -26
  25. data/lib/process_executer/monitored_pipe.rb +128 -59
  26. data/lib/process_executer/options/base.rb +118 -82
  27. data/lib/process_executer/options/option_definition.rb +5 -1
  28. data/lib/process_executer/options/run_options.rb +13 -12
  29. data/lib/process_executer/options/run_with_capture_options.rb +156 -0
  30. data/lib/process_executer/options/spawn_options.rb +31 -30
  31. data/lib/process_executer/options/{spawn_and_wait_options.rb → spawn_with_timeout_options.rb} +11 -7
  32. data/lib/process_executer/options.rb +3 -1
  33. data/lib/process_executer/result.rb +35 -77
  34. data/lib/process_executer/result_with_capture.rb +62 -0
  35. data/lib/process_executer/version.rb +2 -1
  36. data/lib/process_executer.rb +384 -346
  37. data/process_executer.gemspec +11 -2
  38. data/release-please-config.json +16 -2
  39. metadata +18 -8
  40. data/lib/process_executer/destination_base.rb +0 -83
  41. data/lib/process_executer/runner.rb +0 -144
@@ -1,17 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'destination_base'
4
+
3
5
  module ProcessExecuter
4
6
  module Destinations
5
7
  # Handles file paths with specific open modes
6
8
  #
7
9
  # @api private
8
- class FilePathMode < ProcessExecuter::DestinationBase
10
+ class FilePathMode < DestinationBase
9
11
  # Initializes a new file path with mode destination handler
10
12
  #
11
- # Opens the file at the given path with the specified mode.
13
+ # Redirects to the file at destination via `open(destination[0], destination[1], 0644)`
12
14
  #
13
15
  # @param destination [Array<String, String>] array with file path and mode
14
- # @return [FilePathMode] a new file path with mode destination handler
15
16
  # @raise [Errno::ENOENT] if the file path is invalid
16
17
  # @raise [ArgumentError] if the mode is invalid
17
18
  def initialize(destination)
@@ -26,13 +27,16 @@ module ProcessExecuter
26
27
 
27
28
  # Writes data to the file
28
29
  #
30
+ # @example
31
+ # mode_handler = ProcessExecuter::Destinations::FilePathMode.new(["output.log", "a"])
32
+ # mode_handler.write("Appended log entry")
33
+ #
29
34
  # @param data [String] the data to write
35
+ #
30
36
  # @return [Integer] the number of bytes written
37
+ #
31
38
  # @raise [IOError] if the file is closed
32
39
  #
33
- # @example
34
- # mode_handler = ProcessExecuter::Destinations::FilePathMode.new(["output.log", "a"])
35
- # mode_handler.write("Appended log entry")
36
40
  def write(data)
37
41
  super
38
42
  file.write data
@@ -1,19 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'destination_base'
4
+
3
5
  module ProcessExecuter
4
6
  module Destinations
5
7
  # Handles file paths with specific open modes and permissions
6
8
  #
7
9
  # @api private
8
- class FilePathModePerms < ProcessExecuter::DestinationBase
10
+ class FilePathModePerms < DestinationBase
9
11
  # Initializes a new file path with mode and permissions destination handler
10
12
  #
11
13
  # Opens the file at the given path with the specified mode and permissions.
12
14
  #
13
15
  # @param destination [Array<String, String, Integer>] array with file path, mode, and permissions
14
- # @return [FilePathModePerms] a new handler instance
16
+ #
15
17
  # @raise [Errno::ENOENT] if the file path is invalid
18
+ #
16
19
  # @raise [ArgumentError] if the mode is invalid
20
+ #
17
21
  def initialize(destination)
18
22
  super
19
23
  @file = File.open(destination[0], destination[1], destination[2])
@@ -26,13 +30,16 @@ module ProcessExecuter
26
30
 
27
31
  # Writes data to the file
28
32
  #
33
+ # @example
34
+ # perms_handler = ProcessExecuter::Destinations::FilePathModePerms.new(["output.log", "w", 0644])
35
+ # perms_handler.write("Log entry with specific permissions")
36
+ #
29
37
  # @param data [String] the data to write
38
+ #
30
39
  # @return [Integer] the number of bytes written
40
+ #
31
41
  # @raise [IOError] if the file is closed
32
42
  #
33
- # @example
34
- # perms_handler = ProcessExecuter::Destinations::FilePathModePerms.new(["output.log", "w", 0644])
35
- # perms_handler.write("Log entry with specific permissions")
36
43
  def write(data)
37
44
  super
38
45
  file.write data
@@ -1,21 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'destination_base'
4
+
3
5
  module ProcessExecuter
4
6
  module Destinations
5
7
  # Handles IO objects
6
8
  #
7
9
  # @api private
8
- class IO < ProcessExecuter::DestinationBase
10
+ class IO < DestinationBase
9
11
  # Writes data to the IO object
10
12
  #
11
- # @param data [String] the data to write
12
- # @return [Integer] the number of bytes written
13
- # @raise [IOError] if the IO object is closed
14
- #
15
13
  # @example
16
14
  # io = File.open('file.txt', 'w')
17
15
  # io_handler = ProcessExecuter::Destinations::IO.new(io)
18
16
  # io_handler.write("Hello world")
17
+ #
18
+ # @param data [String] the data to write
19
+ #
20
+ # @return [Integer] the number of bytes written
21
+ #
22
+ # @raise [IOError] if the IO object is closed
23
+ #
19
24
  def write(data)
20
25
  super
21
26
  destination.write data
@@ -1,20 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'destination_base'
4
+
3
5
  module ProcessExecuter
4
6
  module Destinations
5
7
  # Handles monitored pipes
6
8
  #
7
9
  # @api private
8
- class MonitoredPipe < ProcessExecuter::DestinationBase
10
+ class MonitoredPipe < DestinationBase
9
11
  # Writes data to the monitored pipe
10
12
  #
11
- # @param data [String] the data to write
12
- # @return [Object] the return value of the pipe's write method
13
- #
14
13
  # @example
15
- # pipe = ProcessExecuter::MonitoredPipe.new
14
+ # stringio_dest = StringIO.new
15
+ # pipe = ProcessExecuter::MonitoredPipe.new(stringio_dest)
16
16
  # pipe_handler = ProcessExecuter::Destinations::MonitoredPipe.new(pipe)
17
17
  # pipe_handler.write("Data to pipe")
18
+ #
19
+ # @param data [String] the data to write
20
+ #
21
+ # @return [Integer] the number of bytes written
22
+ #
18
23
  def write(data)
19
24
  super
20
25
  destination.write data
@@ -1,19 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'destination_base'
4
+
3
5
  module ProcessExecuter
4
6
  module Destinations
5
7
  # Handles standard error redirection
6
8
  #
7
9
  # @api private
8
- class Stderr < ProcessExecuter::DestinationBase
10
+ class Stderr < DestinationBase
9
11
  # Writes data to standard error
10
12
  #
11
- # @param data [String] the data to write
12
- # @return [Integer] the number of bytes written
13
- #
14
13
  # @example
15
14
  # stderr_handler = ProcessExecuter::Destinations::Stderr.new(:err)
16
15
  # stderr_handler.write("Error message")
16
+ #
17
+ # @param data [String] the data to write
18
+ #
19
+ # @return [Integer] the number of bytes written
20
+ #
17
21
  def write(data)
18
22
  super
19
23
  $stderr.write data
@@ -1,19 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'destination_base'
4
+
3
5
  module ProcessExecuter
4
6
  module Destinations
5
7
  # Handles standard output redirection
6
8
  #
7
9
  # @api private
8
- class Stdout < ProcessExecuter::DestinationBase
10
+ class Stdout < DestinationBase
9
11
  # Writes data to standard output
10
12
  #
11
- # @param data [String] the data to write
12
- # @return [Integer] the number of bytes written
13
- #
14
13
  # @example
15
14
  # stdout_handler = ProcessExecuter::Destinations::Stdout.new(:out)
16
15
  # stdout_handler.write("Hello world")
16
+ #
17
+ # @param data [String] the data to write
18
+ #
19
+ # @return [Integer] the number of bytes written
20
+ #
17
21
  def write(data)
18
22
  super
19
23
  $stdout.write data
@@ -1,47 +1,54 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'destination_base'
4
+
3
5
  module ProcessExecuter
4
6
  module Destinations
5
- # Handles destination for writing to multiple destinations
7
+ # Handles a destination for writing to multiple destinations
6
8
  #
7
9
  # The destination is an array with the first element being :tee and the rest
8
10
  # being the destinations.
9
11
  #
10
12
  # @api private
11
- class Tee < ProcessExecuter::DestinationBase
12
- # Initializes a new file path with mode and permissions destination handler
13
+ class Tee < DestinationBase
14
+ # Initializes a destination handler for writing to multiple output destinations
15
+ #
16
+ # @param destination [Array<Symbol, Object...>] array in the form [:tee, destination...]
13
17
  #
14
- # Opens the file at the given path with the specified mode and permissions.
18
+ # @raise [ArgumentError] if a child destination is invalid or incompatible
15
19
  #
16
- # @param destination [Array<String, String, Integer>] array with file path, mode, and permissions
17
- # @return [FilePathModePerms] a new handler instance
18
- # @raise [Errno::ENOENT] if the file path is invalid
19
- # @raise [ArgumentError] if the mode is invalid
20
20
  def initialize(destination)
21
21
  super
22
22
  @child_destinations = destination[1..].map { |dest| ProcessExecuter::Destinations.factory(dest) }
23
23
  end
24
24
 
25
- # The opened file object
25
+ # An array of child destinations
26
+ #
27
+ # @return [Array<ProcessExecuter::Destinations::DestinationBase>]
28
+ # An array of the child destination handlers
26
29
  #
27
- # @return [File] the opened file
28
30
  attr_reader :child_destinations
29
31
 
30
- # Writes data to the file
32
+ # Writes data each of the {child_destinations}
33
+ #
34
+ # @example
35
+ # tee = ProcessExecuter::Destinations::Tee.new([:tee, "output1.log", "output2.log"])
36
+ # tee.write("Log entry with specific permissions")
37
+ # tee.close # Important to close the tee to ensure all data is flushed
31
38
  #
32
39
  # @param data [String] the data to write
33
- # @return [Integer] the number of bytes written
40
+ #
41
+ # @return [Integer] the number of bytes in the input data (which is written to each destination)
42
+ #
34
43
  # @raise [IOError] if the file is closed
35
44
  #
36
- # @example
37
- # perms_handler = ProcessExecuter::Destinations::FilePathModePerms.new(["output.log", "w", 0644])
38
- # perms_handler.write("Log entry with specific permissions")
39
45
  def write(data)
40
46
  super
41
47
  child_destinations.each { |dest| dest.write(data) }
48
+ data.bytesize
42
49
  end
43
50
 
44
- # Closes the file if it's open
51
+ # Closes the child_destinations
45
52
  #
46
53
  # @return [void]
47
54
  def close
@@ -51,7 +58,7 @@ module ProcessExecuter
51
58
  # Determines if this class can handle the given destination
52
59
  #
53
60
  # @param destination [Object] the destination to check
54
- # @return [Boolean] true if destination is an Array with path, mode, and permissions
61
+ # @return [Boolean] true if destination is an Array in the form [:tee, destination...]
55
62
  def self.handles?(destination)
56
63
  destination.is_a?(Array) && destination.size > 1 && destination[0] == :tee
57
64
  end
@@ -1,21 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'destination_base'
4
+
3
5
  module ProcessExecuter
4
6
  module Destinations
5
- # Handles generic objects that respond to write
7
+ # Handles generic objects that respond to `#write`
6
8
  #
7
9
  # @api private
8
- class Writer < ProcessExecuter::DestinationBase
10
+ class Writer < DestinationBase
9
11
  # Writes data to the destination object
10
12
  #
11
- # @param data [String] the data to write
12
- # @return [Object] the return value of the destination's write method
13
- # @raise [NoMethodError] if the destination doesn't respond to write
14
- #
15
13
  # @example
16
14
  # buffer = StringIO.new
17
15
  # writer_handler = ProcessExecuter::Destinations::Writer.new(buffer)
18
16
  # writer_handler.write("Hello world")
17
+ #
18
+ # @param data [String] the data to write
19
+ #
20
+ # @return [Integer] the number of bytes written
21
+ #
19
22
  def write(data)
20
23
  super
21
24
  destination.write data
@@ -24,7 +27,9 @@ module ProcessExecuter
24
27
  # Determines if this class can handle the given destination
25
28
  #
26
29
  # @param destination [Object] the destination to check
27
- # @return [Boolean] true if destination responds to write but is not an IO with fileno
30
+ #
31
+ # @return [Boolean] true if destination responds to #write and is not an IO object with a #fileno
32
+ #
28
33
  def self.handles?(destination)
29
34
  destination.respond_to?(:write) && (!destination.respond_to?(:fileno) || destination.fileno.nil?)
30
35
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'destinations/child_redirection'
4
+ require_relative 'destinations/destination_base'
4
5
  require_relative 'destinations/file_descriptor'
5
6
  require_relative 'destinations/file_path'
6
7
  require_relative 'destinations/file_path_mode'
@@ -15,54 +16,68 @@ require_relative 'destinations/writer'
15
16
  module ProcessExecuter
16
17
  # Collection of destination handler implementations
17
18
  #
18
- # @api public
19
+ # @api private
19
20
  module Destinations
20
21
  # Creates appropriate destination objects based on the given destination
21
22
  #
22
23
  # This factory method dynamically finds and instantiates the appropriate
23
24
  # destination class for handling the provided destination.
24
25
  #
26
+ # @example
27
+ # ProcessExecuter::Destinations.factory(1) #=> Returns a Stdout instance
28
+ # ProcessExecuter::Destinations.factory("output.log") #=> Returns a FilePath instance
29
+ #
25
30
  # @param destination [Object] the destination to create a handler for
26
- # @return [DestinationBase] an instance of the appropriate destination handler
27
- # @raise [ArgumentError] if no matching destination class is found
28
31
  #
29
- # @example
30
- # ProcessExecuter.destination_factory(1) #=> Returns a Stdout instance
31
- # ProcessExecuter.destination_factory("output.log") #=> Returns a FilePath instance
32
+ # @return [ProcessExecuter::Destinations::DestinationBase] an instance of the
33
+ # appropriate destination handler
34
+ #
35
+ # @raise [ProcessExecuter::ArgumentError] if no matching destination class is found
36
+ #
32
37
  def self.factory(destination)
33
38
  matching_class = matching_destination_class(destination)
34
39
  return matching_class.new(destination) if matching_class
35
40
 
36
- raise ArgumentError, 'wrong exec redirect action'
41
+ raise ProcessExecuter::ArgumentError, "Destination #{destination.inspect} is not compatible with MonitoredPipe"
37
42
  end
38
43
 
39
- # Determines if the given destination is compatible with a monitored pipe
44
+ # Determines if the given destination type can be managed by a {MonitoredPipe}
45
+ #
46
+ # Returns true if {MonitoredPipe} can forward data to this destination type.
40
47
  #
41
- # If true, this destination should not be wrapped in a monitored pipe.
48
+ # Returns false otherwise (e.g., for destinations like :close or [:child, fd]
49
+ # which have special meaning to Process.spawn and are not simply data sinks for
50
+ # {MonitoredPipe}).
42
51
  #
43
52
  # @example
44
- # ProcessExecuter::Destinations.compatible_with_monitored_pipe?(1) #=> true
45
- # ProcessExecuter::Destinations.compatible_with_monitored_pipe?([:child, 6]) #=> false
46
- # ProcessExecuter::Destinations.compatible_with_monitored_pipe?([:close]) #=> false
53
+ # ProcessExecuter::Destinations.compatible_with_monitored_pipe?(1)
54
+ # #=> true
55
+ # ProcessExecuter::Destinations.compatible_with_monitored_pipe?([:child, 6])
56
+ # #=> false
57
+ # ProcessExecuter::Destinations.compatible_with_monitored_pipe?(:close)
58
+ # #=> false
47
59
  #
48
60
  # @param destination [Object] the destination to check
49
- # @return [Boolean, nil] true if the destination is compatible with a monitored pipe
50
- # @raise [ArgumentError] if no matching destination class is found
51
- # @api public
61
+ #
62
+ # @return [Boolean] true if {MonitoredPipe} can forward data to this destination type
63
+ #
52
64
  def self.compatible_with_monitored_pipe?(destination)
53
65
  matching_class = matching_destination_class(destination)
54
66
  matching_class&.compatible_with_monitored_pipe?
55
67
  end
56
68
 
57
69
  # Determines the destination class that can handle the given destination
70
+ #
58
71
  # @param destination [Object] the destination to check
59
- # @return [Class] the destination class that can handle the given destination
60
- # @api private
72
+ #
73
+ # @return [Class, nil] the handler class for the given destination or `nil` if no match
74
+ #
61
75
  def self.matching_destination_class(destination)
62
76
  destination_classes =
63
77
  ProcessExecuter::Destinations.constants
64
78
  .map { |const| ProcessExecuter::Destinations.const_get(const) }
65
79
  .select { |const| const.is_a?(Class) }
80
+ .reject { |klass| klass == ProcessExecuter::Destinations::DestinationBase }
66
81
 
67
82
  destination_classes.find { |klass| klass.handles?(destination) }
68
83
  end
@@ -1,18 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # rubocop:disable Layout/LineLength
4
-
5
3
  module ProcessExecuter
6
- # Base class for all ProcessExecuter::Command errors
4
+ # rubocop:disable Layout/LineLength
5
+
6
+ # Base class for all {ProcessExecuter} errors
7
7
  #
8
- # It is recommended to rescue `ProcessExecuter::Error` to catch any
9
- # runtime error raised by this gem unless you need more specific error handling.
8
+ # It is recommended to rescue {ProcessExecuter::Error} to catch any runtime error
9
+ # raised by this gem unless you need more specific error handling.
10
10
  #
11
11
  # Custom errors are arranged in the following class hierarchy:
12
12
  #
13
13
  # ```text
14
14
  # ::StandardError
15
15
  # └─> Error
16
+ # ├─> ArgumentError
16
17
  # ├─> CommandError
17
18
  # │ ├─> FailedError
18
19
  # │ └─> SignaledError
@@ -24,16 +25,17 @@ module ProcessExecuter
24
25
  # | Error Class | Description |
25
26
  # | --- | --- |
26
27
  # | `Error` | This catch-all error serves as the base class for other custom errors. |
28
+ # | `ArgumentError` | Raised when an invalid argument is passed to a method. |
27
29
  # | `CommandError` | A subclass of this error is raised when there is a problem executing a command. |
28
- # | `FailedError` | Raised when the command exits with a non-zero status code. |
30
+ # | `FailedError` | Raised when the command exits with a non-zero exit status. |
29
31
  # | `SignaledError` | Raised when the command is terminated as a result of receiving a signal. This could happen if the process is forcibly terminated or if there is a serious system error. |
30
- # | `TimeoutError` | This is a specific type of `SignaledError` that is raised when the command times out and is killed via the SIGKILL signal. Raised when the operation takes longer than the specified timeout duration (if provided). |
32
+ # | `TimeoutError` | This is a specific type of `SignaledError` that is raised when the command times out and is killed via the SIGKILL signal. |
31
33
  # | `ProcessIOError` | Raised when an error was encountered reading or writing to the command's subprocess. |
32
- # | `SpawnError` | Raised when the process could not execute. Check the |
34
+ # | `SpawnError` | Raised when the process could not execute. Check the `#cause` for the original exception from `Process.spawn`. |
33
35
  #
34
36
  # @example Rescuing any error
35
37
  # begin
36
- # ProcessExecuter.run_command('git', 'status')
38
+ # ProcessExecuter.run('git', 'status')
37
39
  # rescue ProcessExecuter::Error => e
38
40
  # puts "An error occurred: #{e.message}"
39
41
  # end
@@ -41,23 +43,37 @@ module ProcessExecuter
41
43
  # @example Rescuing a timeout error
42
44
  # begin
43
45
  # timeout_after = 0.1 # seconds
44
- # ProcessExecuter.run_command('sleep', '1', timeout_after:)
46
+ # ProcessExecuter.run('sleep', '1', timeout_after:)
45
47
  # rescue ProcessExecuter::TimeoutError => e # Catch the more specific error first!
46
48
  # puts "Command took too long and timed out: #{e}"
47
49
  # rescue ProcessExecuter::Error => e
48
- # puts "Some other error occured: #{e}"
50
+ # puts "Some other error occurred: #{e}"
49
51
  # end
50
52
  #
51
53
  # @api public
52
54
  #
53
55
  class Error < ::StandardError; end
54
56
 
57
+ # rubocop:enable Layout/LineLength
58
+
59
+ # Raised when an invalid argument is passed to a method
60
+ #
61
+ # @example Raising ProcessExecuter::ArgumentError due to invalid option value
62
+ # begin
63
+ # ProcessExecuter.run('echo Hello', timeout_after: 'not_a_number')
64
+ # rescue ProcessExecuter::ArgumentError => e
65
+ # e.message #=> 'timeout_after must be nil or a non-negative real number but was "not_a_number"'
66
+ # end
67
+ #
68
+ # @api public
69
+ #
70
+ class ArgumentError < ProcessExecuter::Error; end
71
+
55
72
  # Raised when a command fails or exits because of an uncaught signal
56
73
  #
57
- # The command executed, status, stdout, and stderr are available from this
58
- # object.
74
+ # The command executed and its result are available from this object.
59
75
  #
60
- # The Gem will raise a more specific error for each type of failure:
76
+ # This gem will raise a more specific error for each type of failure:
61
77
  #
62
78
  # * {FailedError}: when the command exits with a non-zero status
63
79
  # * {SignaledError}: when the command exits because of an uncaught signal
@@ -70,12 +86,18 @@ module ProcessExecuter
70
86
  #
71
87
  # @example
72
88
  # `exit 1` # set $? appropriately for this example
73
- # result = ProcessExecuter::Result.new(%w[git status], $?, 'stdout', 'stderr')
89
+ # result_data = {
90
+ # command: ['exit 1'],
91
+ # options: ProcessExecuter::Options::RunOptions.new,
92
+ # timed_out: false,
93
+ # elapsed_time: 0.01
94
+ # }
95
+ # result = ProcessExecuter::Result.new($?, **result_data)
74
96
  # error = ProcessExecuter::CommandError.new(result)
75
- # error.to_s #=> '["git", "status"], status: pid 89784 exit 1, stderr: "stderr"'
97
+ # error.to_s #=> '["exit 1"], status: pid 29686 exit 1'
76
98
  #
77
- # @param result [Result] The result of the command including the command,
78
- # status, stdout, and stderr
99
+ # @param result [ProcessExecuter::Result] The result of the command including the
100
+ # command and exit status
79
101
  #
80
102
  def initialize(result)
81
103
  @result = result
@@ -85,12 +107,12 @@ module ProcessExecuter
85
107
  # The human readable representation of this error
86
108
  #
87
109
  # @example
88
- # error.error_message #=> '["git", "status"], status: pid 89784 exit 1, stderr: "stderr"'
110
+ # error.error_message #=> '["git", "status"], status: pid 89784 exit 1'
89
111
  #
90
112
  # @return [String]
91
113
  #
92
114
  def error_message
93
- "#{result.command}, status: #{result}, stderr: #{result.stderr.inspect}"
115
+ "#{result.command}, status: #{result}"
94
116
  end
95
117
 
96
118
  # @attribute [r] result
@@ -100,12 +122,12 @@ module ProcessExecuter
100
122
  # @example
101
123
  # error.result #=> #<ProcessExecuter::Result:0x00007f9b1b8b3d20>
102
124
  #
103
- # @return [Result]
125
+ # @return [ProcessExecuter::Result]
104
126
  #
105
127
  attr_reader :result
106
128
  end
107
129
 
108
- # Raised when the command returns a non-zero exitstatus
130
+ # Raised when the command returns a non-zero exit status
109
131
  #
110
132
  # @api public
111
133
  #
@@ -120,13 +142,17 @@ module ProcessExecuter
120
142
  # Raised when the command takes longer than the configured timeout_after
121
143
  #
122
144
  # @example
123
- # result.timed_out? #=> true
145
+ # begin
146
+ # ProcessExecuter.spawn_with_timeout('sleep 1', timeout_after: 0.1)
147
+ # rescue ProcessExecuter::TimeoutError => e
148
+ # puts "Command timed out: #{e.result.command}"
149
+ # end
124
150
  #
125
151
  # @api public
126
152
  #
127
153
  class TimeoutError < ProcessExecuter::SignaledError; end
128
154
 
129
- # Raised when the output of a command can not be read
155
+ # Raised if an exception occurred while processing subprocess output
130
156
  #
131
157
  # @api public
132
158
  #
@@ -140,5 +166,3 @@ module ProcessExecuter
140
166
  #
141
167
  class SpawnError < ProcessExecuter::Error; end
142
168
  end
143
-
144
- # rubocop:enable Layout/LineLength