process_executer 1.1.2 → 1.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 939beda136b7c1bffc2a3e16edda8083125cc5db3acc49f2dcec7837a96d510b
4
- data.tar.gz: bea06af73eeff96c1583fdc422b0fe4fdd29f0964f10b6e88c9ee8174d94c5b6
3
+ metadata.gz: d2bd50be1f3683bc2c8210451c45e270e7072992ec48897a8bbb16cf10790c8b
4
+ data.tar.gz: e0d1ba5a7c5571b0059db55537726b68baf6017a66941883953ece63f5a58b53
5
5
  SHA512:
6
- metadata.gz: 3cb8172471ed879e039904a731c40f23c0705643b78cfd0df7ce5d4671ae45db2c65308a9cf54416173fb4ff10d87a23e00e93a52b534aa15f453306c04be848
7
- data.tar.gz: 3f25c2cb40a0689a04f26c83d865ddafe35e56cca8d9593f3916c3c5b7b3d41c136f243721978c7b040f9ec4eb53a34902ba01c0877c0dab074102916ddecfc8
6
+ metadata.gz: bdaab34abbd99de650933f367eedcb40e7a2538d1f48ab8eda6f5df5da3d5f1748543eb28f54f21cc3557c64d34575001da2158caf2f30cc768bec88f657e16c
7
+ data.tar.gz: 920227450b1da0c56aa3987a2ff1bbeb2c73cd093d04c1318ebb39d02302a408682d0d40c1668eb1f9b37d71f58fe47a6c4b101b75e701fdbaf3db2cdb845b26
data/.commitlintrc.yml ADDED
@@ -0,0 +1,16 @@
1
+ ---
2
+ extends: '@commitlint/config-conventional'
3
+
4
+ rules:
5
+ # See: https://commitlint.js.org/reference/rules.html
6
+ #
7
+ # Rules are made up by a name and a configuration array. The configuration array contains:
8
+ #
9
+ # * Severity [0..2]: 0 disable rule, 1 warning if violated, or 2 error if violated
10
+ # * Applicability [always|never]: never inverts the rule
11
+ # * Value: value to use for this rule
12
+ #
13
+ # Run `npx commitlint --print-config` to see the current setting for all rules.
14
+ #
15
+ body-leading-blank: [2, 'always']
16
+ footer-leading-blank: [2, 'always']
data/.husky/commit-msg ADDED
@@ -0,0 +1 @@
1
+ npx --no-install commitlint --edit "$1"
data/.rubocop.yml CHANGED
@@ -1,23 +1,7 @@
1
- AllCops:
2
- NewCops: enable
3
- # Output extra information for each offense to make it easier to diagnose:
4
- DisplayCopNames: true
5
- DisplayStyleGuide: true
6
- ExtraDetails: true
7
- SuggestExtensions: false
8
- # RuboCop enforces rules depending on the oldest version of Ruby which
9
- # your project supports:
10
- TargetRubyVersion: 3.0
11
-
12
- # The default max line length is 80 characters
13
- Layout/LineLength:
14
- Max: 120
1
+ inherit_gem:
2
+ main_branch_shared_rubocop_config: config/rubocop.yml
15
3
 
16
- # The DSL for RSpec and the gemspec file make it very hard to limit block length:
17
- Metrics/BlockLength:
18
- Exclude:
19
- - "spec/**/*_spec.rb"
20
- - "*.gemspec"
21
-
22
- Gemspec/DevelopmentDependencies:
23
- Enabled: false
4
+ AllCops:
5
+ # Pin this project to Ruby 3.1 in case the shared config above is upgraded to 3.2
6
+ # or later.
7
+ TargetRubyVersion: 3.1
data/.tool-versions CHANGED
@@ -1 +1 @@
1
- ruby 3.3.5
1
+ ruby 3.3.5
data/.yardopts CHANGED
@@ -3,4 +3,4 @@
3
3
  --markup-provider=redcarpet
4
4
  --markup markdown
5
5
  - CHANGELOG.md
6
- - LICENSE.md
6
+ - LICENSE.txt
data/CHANGELOG.md CHANGED
@@ -5,25 +5,81 @@ 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
- ## v1.1.2 (2024-09-26)
9
-
10
- [Full Changelog](https://github.com/main-branch/process_executer/compare/v1.1.1..v1.1.2)
11
-
12
- Changes since v1.1.1:
13
-
14
- * 2006301 Re-add require for 'forwardable'
15
-
16
- ## v1.1.1 (2024-09-25)
17
-
18
- [Full Changelog](https://github.com/main-branch/process_executer/compare/v1.1.0..v1.1.1)
8
+ ## v1.3.0 (2025-02-26)
9
+
10
+ [Full Changelog](https://github.com/main-branch/process_executer/compare/v1.2.0..v1.3.0)
11
+
12
+ Changes since v1.2.0:
13
+
14
+ * d1e189b build: add Ruby 3.4 to the CI workflow
15
+ * e805dfc feat: implement ProcessExecuter.run_command
16
+ * bad822f fix: update the yard build in the rake file and update included files
17
+ * 6fbdc5e feat: allow #spawn to accept file descriptors for redirection destination
18
+ * d745685 test: make it so that tests do not give unnecessary output
19
+
20
+ ## v1.2.0 (2024-10-10)
21
+
22
+ [Full Changelog](https://github.com/main-branch/process_executer/compare/v1.1.2..v1.2.0)
23
+
24
+ Changes since v1.1.2:
25
+
26
+ * 35663c9 chore: reset main branch to 1.x
27
+ * 39913bc build: remove semver pr label check
28
+ * 8ae8e34 build: enforce conventional commit message formatting
29
+ * f5b8c51 Release v2.0.0.pre1
30
+ * 8e15c39 Re-add require for 'forwardable'
31
+ * 4bba06e Fix flakey test that checks for thread to die
32
+ * 83bfd78 Remove unused require for 'forwardable' and 'ostruct'
33
+ * ea3ea3c Use shared Rubocop config
34
+ * ecd2cb5 Update copyright notice in this project
35
+ * 7d5bfe1 Update links in gemspec
36
+ * 797de91 Add Slack badge for this project in README
37
+ * 591b716 Update “Build Status” link the README
38
+ * 2fcd001 Update yardopts with new standard options
39
+ * 4e1de47 Standardize YARD and Markdown Lint configurations
40
+ * 929c680 Set JRuby --debug option when running tests in GitHub Actions workflows
41
+ * 71049cb Finish Integration of simplecov-rspec into the project
42
+ * 4fb44bb Update continuous integration and experimental ruby builds
43
+ * 289645c Depend on v1 of semver_pr_label_check
44
+ * 3c4d988 Update code climate test coverage reporter version
45
+ * 04103b4 Simplify how the experimental ruby builds are triggered
46
+ * 35840a4 Use a reusable workflow for the Semver PR label check
47
+ * 0d887f0 Update code climate test coverage reporter version
48
+ * bb7f73b Rename the experimental build workflow
49
+ * 035ce8a Fix the experimental CI Build workflow
50
+ * 3d739f4 Move CI builds using experimental Rubies to a different workflow
51
+ * c5ef6b0 Integrate simplecov-rspec to ensure code covage in CI builds
52
+ * f33707e Update development dependencies and examples (#45)
53
+
54
+ ## v2.0.0.pre1 (2024-09-26)
55
+
56
+ [Full Changelog](https://github.com/main-branch/process_executer/compare/v1.1.0..v2.0.0.pre1)
19
57
 
20
58
  Changes since v1.1.0:
21
59
 
22
- * c3f063a Use a later version of ruby for development
23
- * d4eeb1e Use latest version of the actions/checkout to v4
24
- * eb4dad7 Fix flakey test on JRuby
25
- * ac9417a Update CI build to run on v1.x branch
26
- * 014e70f Remove unused require for 'forwardable' and 'ostruct' (for v1.x)
60
+ * 8e15c39 Re-add require for 'forwardable'
61
+ * 4bba06e Fix flakey test that checks for thread to die
62
+ * 83bfd78 Remove unused require for 'forwardable' and 'ostruct'
63
+ * ea3ea3c Use shared Rubocop config
64
+ * ecd2cb5 Update copyright notice in this project
65
+ * 7d5bfe1 Update links in gemspec
66
+ * 797de91 Add Slack badge for this project in README
67
+ * 591b716 Update “Build Status” link the README
68
+ * 2fcd001 Update yardopts with new standard options
69
+ * 4e1de47 Standardize YARD and Markdown Lint configurations
70
+ * 929c680 Set JRuby --debug option when running tests in GitHub Actions workflows
71
+ * 71049cb Finish Integration of simplecov-rspec into the project
72
+ * 4fb44bb Update continuous integration and experimental ruby builds
73
+ * 289645c Depend on v1 of semver_pr_label_check
74
+ * 3c4d988 Update code climate test coverage reporter version
75
+ * 04103b4 Simplify how the experimental ruby builds are triggered
76
+ * 35840a4 Use a reusable workflow for the Semver PR label check
77
+ * 0d887f0 Update code climate test coverage reporter version
78
+ * bb7f73b Rename the experimental build workflow
79
+ * 035ce8a Fix the experimental CI Build workflow
80
+ * 3d739f4 Move CI builds using experimental Rubies to a different workflow
81
+ * c5ef6b0 Integrate simplecov-rspec to ensure code covage in CI builds
82
+ * f33707e Update development dependencies and examples (#45)
27
83
 
28
84
  ## v1.1.0 (2024-02-02)
29
85
 
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2022 James Couball
3
+ Copyright (c) 2024 James Couball
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -3,18 +3,23 @@
3
3
  [![Gem Version](https://badge.fury.io/rb/process_executer.svg)](https://badge.fury.io/rb/process_executer)
4
4
  [![Documentation](https://img.shields.io/badge/Documentation-Latest-green)](https://rubydoc.info/gems/process_executer/)
5
5
  [![Change Log](https://img.shields.io/badge/CHANGELOG-Latest-green)](https://rubydoc.info/gems/process_executer/file/CHANGELOG.md)
6
- [![Build Status](https://github.com/main-branch/process_executer/workflows/CI%20Build/badge.svg?branch=main)](https://github.com/main-branch/process_executer/actions?query=workflow%3ACI%20Build)
6
+ [![Build Status](https://github.com/main-branch/process_executer/actions/workflows/continuous-integration.yml/badge.svg)](https://github.com/main-branch/process_executer/actions/workflows/continuous-integration.yml)
7
7
  [![Maintainability](https://api.codeclimate.com/v1/badges/0b5c67e5c2a773009cd0/maintainability)](https://codeclimate.com/github/main-branch/process_executer/maintainability)
8
8
  [![Test Coverage](https://api.codeclimate.com/v1/badges/0b5c67e5c2a773009cd0/test_coverage)](https://codeclimate.com/github/main-branch/process_executer/test_coverage)
9
+ [![Conventional
10
+ Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-%23FE5196?logo=conventionalcommits&logoColor=white)](https://conventionalcommits.org)
11
+ [![Slack](https://img.shields.io/badge/slack-main--branch/process__executer-yellow.svg?logo=slack)](https://main-branch.slack.com/archives/C07NG2BPG8Y)
9
12
 
10
13
  * [Usage](#usage)
14
+ * [ProcessExecuter.run](#processexecuterrun)
11
15
  * [ProcessExecuter::MonitoredPipe](#processexecutermonitoredpipe)
12
16
  * [ProcessExecuter.spawn](#processexecuterspawn)
13
17
  * [Installation](#installation)
14
18
  * [Contributing](#contributing)
15
19
  * [Reporting Issues](#reporting-issues)
16
20
  * [Developing](#developing)
17
- * [Submitting Pull Requests](#submitting-pull-requests)
21
+ * [Commit message guidelines](#commit-message-guidelines)
22
+ * [Pull request guidelines](#pull-request-guidelines)
18
23
  * [Releasing](#releasing)
19
24
  * [License](#license)
20
25
 
@@ -25,6 +30,36 @@ gem is hosted on RubyGems.org. Read below of an overview and several examples.
25
30
 
26
31
  This gem contains the following important classes:
27
32
 
33
+ ### ProcessExecuter.run
34
+
35
+ `ProcessExecuter.run` execute the given command as a subprocess blocking until it is finished.
36
+
37
+ A Result object is returned which includes the process's status and output.
38
+
39
+ Supports the same features as
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.
44
+
45
+ This command takes two forms:
46
+
47
+ 1. When passing a single string the command is passed to a shell:
48
+
49
+ `ProcessExecuter.run([env, ] command_line, options = {}) ->` {ProcessExecuter::Command::Result}
50
+
51
+ 2. When passing an array of strings the command is run directly (bypassing the shell):
52
+
53
+ `ProcessExecuter.run([env, ] exe_path, *args, options = {}) ->` {ProcessExecuter::Command::Result}
54
+
55
+ Argument env, if given, is a hash that affects ENV for the new process; see
56
+ [Execution
57
+ Environment](https://docs.ruby-lang.org/en/3.3/Process.html#module-Process-label-Execution+Environment).
58
+
59
+ Argument options is a hash of options for the new process; see the options listed below.
60
+
61
+ See comprehensive examples in the YARD documentation for this method.
62
+
28
63
  ### ProcessExecuter::MonitoredPipe
29
64
 
30
65
  `ProcessExecuter::MonitoredPipe` streams data sent through a pipe to one or more writers.
@@ -116,59 +151,34 @@ allow you to experiment.
116
151
 
117
152
  To install this gem onto your local machine, run `bundle exec rake install`.
118
153
 
119
- ### Submitting Pull Requests
120
-
121
- In order for a pull request to be merged, it must be approved by a code owner and
122
- include a semver label.
123
-
124
- The approval must be done using the Github PR Review process by a code owner defined
125
- in the project's CODEOWNERS file.
126
-
127
- The semver label indicates the type of change so that the gem version can be
128
- increments according to semver rules prior to release. One and only one of the
129
- following labels must added to the PR:
130
-
131
- * **`major-change`**
154
+ ### Commit message guidelines
132
155
 
133
- Use when the PR includes incompatible API or functional changes.
156
+ All commit messages must follow the [Conventional Commits
157
+ standard](https://www.conventionalcommits.org/en/v1.0.0/). This helps us maintain a
158
+ clear and structured commit history, automate versioning, and generate changelogs
159
+ effectively.
134
160
 
135
- This typically occurs when the changes introduced could break existing code that
136
- depends on this gem. For example, removing a public method, changing a method's
137
- signature, or altering the expected behavior of a method in a way that would
138
- require changes in the dependent code.
161
+ To ensure compliance, this project includes:
139
162
 
140
- * **`minor-change`**
163
+ * A git commit-msg hook that validates your commit messages before they are accepted.
141
164
 
142
- Use when the PR adds functionality in a backward-compatible manner.
165
+ To activate the hook, you must have node installed and run `npm install`.
143
166
 
144
- This includes adding new features, enhancements, or deprecating existing features
145
- as long as the deprecation itself doesn't break compatibility.
167
+ * A GitHub Actions workflow that will enforce the Conventional Commit standard as
168
+ part of the continuous integration pipeline.
146
169
 
147
- It's also common to include substantial improvements or optimizations in this
148
- category, as long as they don't alter the expected behavior of the existing API.
170
+ Any commit message that does not conform to the Conventional Commits standard will
171
+ cause the workflow to fail and not allow the PR to be merged.
149
172
 
150
- * **`patch-change`**
173
+ ### Pull request guidelines
151
174
 
152
- Use when the PR includes small user-facing changes that are backward-compatible and
153
- do not introduce new functionality.
154
-
155
- This includes bug fixes or other internal changes that do not affect the API such
156
- as refactoring code, improving performance, or updating user documentation.
157
-
158
- * **`internal-change`**
159
-
160
- Use when the PR includes changes that are NOT user facing and will NOT require a
161
- release.
162
-
163
- This includes updates to developer documentation, comments, GitHub Actions, minor
164
- refactorings, and fixing Rubocop offenses.
175
+ All pull requests must be merged using rebase merges. This ensures that commit
176
+ messages from the feature branch are preserved in the release branch, keeping the
177
+ history clean and meaningful.
165
178
 
166
179
  ### Releasing
167
180
 
168
- To release a new version, first determine the proper semver increment based on the
169
- PRs included in the release.
170
-
171
- Then in the root directory of this project with the `main` branch checked out, run
181
+ In the root directory of this project with the `main` branch checked out, run
172
182
  the following command:
173
183
 
174
184
  ```shell
data/Rakefile CHANGED
@@ -7,7 +7,7 @@ desc 'Run the same tasks that the CI build will run'
7
7
  if RUBY_PLATFORM == 'java'
8
8
  task default: %w[spec rubocop bundle:audit build]
9
9
  else
10
- task default: %w[spec rubocop yard yard:audit yard:coverage bundle:audit build]
10
+ task default: %w[spec rubocop yard bundle:audit build]
11
11
  end
12
12
 
13
13
  # Bundler Audit
@@ -45,38 +45,38 @@ CLEAN << 'rspec-report.xml'
45
45
 
46
46
  require 'rubocop/rake_task'
47
47
 
48
- RuboCop::RakeTask.new do |t|
49
- t.options = %w[
50
- --format progress
51
- --format json --out rubocop-report.json
52
- ]
53
- end
48
+ RuboCop::RakeTask.new
54
49
 
55
- CLEAN << 'rubocop-report.json'
50
+ # YARD
56
51
 
57
52
  unless RUBY_PLATFORM == 'java'
58
- # YARD
53
+ # yard:build
59
54
 
60
55
  require 'yard'
61
- YARD::Rake::YardocTask.new do |t|
62
- t.files = %w[lib/**/*.rb examples/**/*]
56
+
57
+ YARD::Rake::YardocTask.new('yard:build') do |t|
58
+ t.files = %w[lib/**/*.rb]
59
+ t.stats_options = ['--list-undoc']
63
60
  end
64
61
 
65
62
  CLEAN << '.yardoc'
66
63
  CLEAN << 'doc'
67
64
 
68
- # Yardstick
65
+ # yard:audit
69
66
 
70
67
  desc 'Run yardstick to show missing YARD doc elements'
71
68
  task :'yard:audit' do
72
69
  sh "yardstick 'lib/**/*.rb'"
73
70
  end
74
71
 
75
- # Yardstick coverage
72
+ # yard:coverage
76
73
 
77
74
  require 'yardstick/rake/verify'
78
75
 
79
76
  Yardstick::Rake::Verify.new(:'yard:coverage') do |verify|
80
77
  verify.threshold = 100
78
+ verify.require_exact_threshold = false
81
79
  end
80
+
81
+ task yard: %i[yard:build yard:audit yard:coverage]
82
82
  end
@@ -0,0 +1,170 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rubocop:disable Layout/LineLength
4
+
5
+ module ProcessExecuter
6
+ module Command
7
+ # Base class for all ProcessExecuter::Command errors
8
+ #
9
+ # It is recommended to rescue `ProcessExecuter::Command::Error` to catch any
10
+ # runtime error raised by this gem unless you need more specific error handling.
11
+ #
12
+ # Custom errors are arranged in the following class hierarchy:
13
+ #
14
+ # ```text
15
+ # ::StandardError
16
+ # └─> Error
17
+ # ├─> CommandError
18
+ # │ ├─> FailedError
19
+ # │ └─> SignaledError
20
+ # │ └─> TimeoutError
21
+ # └─> ProcessIOError
22
+ # ```
23
+ #
24
+ # | Error Class | Description |
25
+ # | --- | --- |
26
+ # | `Error` | This catch-all error serves as the base class for other custom errors. |
27
+ # | `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. |
29
+ # | `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). |
31
+ # | `ProcessIOError` | Raised when an error was encountered reading or writing to the command's subprocess. |
32
+ #
33
+ # @example Rescuing any error
34
+ # begin
35
+ # ProcessExecuter.run_command('git', 'status')
36
+ # rescue ProcessExecuter::Command::Error => e
37
+ # puts "An error occurred: #{e.message}"
38
+ # end
39
+ #
40
+ # @example Rescuing a timeout error
41
+ # begin
42
+ # timeout_duration = 0.1 # seconds
43
+ # ProcessExecuter.run_command('sleep', '1', timeout: timeout_duration)
44
+ # rescue ProcessExecuter::TimeoutError => e # Catch the more specific error first!
45
+ # puts "Command took too long and timed out: #{e}"
46
+ # rescue ProcessExecuter::Error => e
47
+ # puts "Some other error occured: #{e}"
48
+ # end
49
+ #
50
+ # @api public
51
+ #
52
+ class Error < ::StandardError; end
53
+
54
+ # Raised when a command fails or exits because of an uncaught signal
55
+ #
56
+ # The command executed, status, stdout, and stderr are available from this
57
+ # object.
58
+ #
59
+ # The Gem will raise a more specific error for each type of failure:
60
+ #
61
+ # * {FailedError}: when the command exits with a non-zero status
62
+ # * {SignaledError}: when the command exits because of an uncaught signal
63
+ # * {TimeoutError}: when the command times out
64
+ #
65
+ # @api public
66
+ #
67
+ class CommandError < ProcessExecuter::Command::Error
68
+ # Create a CommandError object
69
+ #
70
+ # @example
71
+ # `exit 1` # set $? appropriately for this example
72
+ # result = ProcessExecuter::Command::Result.new(%w[git status], $?, 'stdout', 'stderr')
73
+ # error = ProcessExecuter::Command::CommandError.new(result)
74
+ # error.to_s #=> '["git", "status"], status: pid 89784 exit 1, stderr: "stderr"'
75
+ #
76
+ # @param result [Result] The result of the command including the command,
77
+ # status, stdout, and stderr
78
+ #
79
+ def initialize(result)
80
+ @result = result
81
+ super(error_message)
82
+ end
83
+
84
+ # The human readable representation of this error
85
+ #
86
+ # @example
87
+ # error.error_message #=> '["git", "status"], status: pid 89784 exit 1, stderr: "stderr"'
88
+ #
89
+ # @return [String]
90
+ #
91
+ def error_message
92
+ "#{result.command}, status: #{result}, stderr: #{result.stderr_to_s.inspect}"
93
+ end
94
+
95
+ # @attribute [r] result
96
+ #
97
+ # The result of the command including the command, its status and its output
98
+ #
99
+ # @example
100
+ # error.result #=> #<ProcessExecuter::Command::Result:0x00007f9b1b8b3d20>
101
+ #
102
+ # @return [Result]
103
+ #
104
+ attr_reader :result
105
+ end
106
+
107
+ # Raised when the command returns a non-zero exitstatus
108
+ #
109
+ # @api public
110
+ #
111
+ class FailedError < ProcessExecuter::Command::CommandError; end
112
+
113
+ # Raised when the command exits because of an uncaught signal
114
+ #
115
+ # @api public
116
+ #
117
+ class SignaledError < ProcessExecuter::Command::CommandError; end
118
+
119
+ # Raised when the command takes longer than the configured timeout
120
+ #
121
+ # @example
122
+ # result.status.timeout? #=> true
123
+ #
124
+ # @api public
125
+ #
126
+ class TimeoutError < ProcessExecuter::Command::SignaledError
127
+ # Create a TimeoutError object
128
+ #
129
+ # @example
130
+ # command = %w[sleep 10]
131
+ # timeout_duration = 1
132
+ # status = ProcessExecuter.spawn(*command, timeout: timeout_duration)
133
+ # result = Result.new(command, status, 'stdout', 'err output')
134
+ # error = TimeoutError.new(result, timeout_duration)
135
+ # error.error_message
136
+ # #=> '["sleep", "10"], status: pid 70144 SIGKILL (signal 9), stderr: "err output", timed out after 1s'
137
+ #
138
+ # @param result [Result] The result of the command including the git command,
139
+ # status, stdout, and stderr
140
+ #
141
+ # @param timeout_duration [Numeric] The duration the subprocess was allowed
142
+ # to run before being terminated
143
+ #
144
+ def initialize(result, timeout_duration)
145
+ @timeout_duration = timeout_duration
146
+ super(result)
147
+ end
148
+
149
+ # The amount of time the subprocess was allowed to run before being killed
150
+ #
151
+ # @example
152
+ # `kill -9 $$` # set $? appropriately for this example
153
+ # result = Result.new(%w[git status], $?, '', "killed")
154
+ # error = TimeoutError.new(result, 10)
155
+ # error.timeout_duration #=> 10
156
+ #
157
+ # @return [Numeric]
158
+ #
159
+ attr_reader :timeout_duration
160
+ end
161
+
162
+ # Raised when the output of a command can not be read
163
+ #
164
+ # @api public
165
+ #
166
+ class ProcessIOError < ProcessExecuter::Command::Error; end
167
+ end
168
+ end
169
+
170
+ # rubocop:enable Layout/LineLength
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'delegate'
4
+
5
+ module ProcessExecuter
6
+ module Command
7
+ # A wrapper around {ProcessExecuter::Status} which adds captured command output
8
+ #
9
+ # This class is used to represent the result of a subprocess execution, combining
10
+ # the process status with the captured output for easier access and manipulation.
11
+ #
12
+ # Features:
13
+ # * Provides access to the process's status, stdout, and stderr.
14
+ # * Allows conversion of stdout and stderr buffers to strings.
15
+ #
16
+ # @example Create a Result object
17
+ # status = ProcessExecuter.spawn(*command, timeout:, out:, err:)
18
+ # result = ProcessExecuter::Command::Result.new(command, status, out_buffer.string, err_buffer.string)
19
+ #
20
+ # @api public
21
+ #
22
+ class Result < SimpleDelegator
23
+ # Create a new Result object
24
+ # @example
25
+ # status = ProcessExecuter.spawn(*command, timeout:, out:, err:)
26
+ # Result.new(command, status, out_buffer.string, err_buffer.string)
27
+ # @param command [Array<String>] The command that was executed
28
+ # @param status [ProcessExecuter::Status] The status of the process
29
+ # @param stdout [String] The stdout output from the process
30
+ # @param stderr [String] The stderr output from the process
31
+ def initialize(command, status, stdout, stderr)
32
+ super(status)
33
+ @command = command
34
+ @stdout = stdout
35
+ @stderr = stderr
36
+ end
37
+
38
+ # The command that was run
39
+ # @example
40
+ # result.command #=> %w[git status]
41
+ # @return [Array<String>]
42
+ attr_reader :command
43
+
44
+ # The captured stdout output from the process
45
+ # @example
46
+ # result.stdout #=> "On branch master\nnothing to commit, working tree clean\n"
47
+ # @return [String]
48
+ attr_reader :stdout
49
+
50
+ # The captured stderr output from the process
51
+ # @example
52
+ # result.stderr #=> "ERROR: file not found"
53
+ # @return [String]
54
+ attr_reader :stderr
55
+
56
+ # Return the stdout output as a string
57
+ # @example When stdout is a StringIO containing "Hello World"
58
+ # result.stdout_to_s #=> "Hello World"
59
+ # @example When stdout is a File object
60
+ # result.stdout_to_s #=> #<File:/tmp/output.txt>
61
+ # @return [String, Object] Returns a String if stdout is a StringIO; otherwise, returns the stdout object
62
+ def stdout_to_s
63
+ stdout.respond_to?(:string) ? stdout.string : stdout
64
+ end
65
+
66
+ # Return the stderr output as a string
67
+ # @example When stderr is a StringIO containing "Hello World"
68
+ # result.stderr_to_s #=> "Hello World"
69
+ # @example When stderr is a File object
70
+ # result.stderr_to_s #=> #<File:/tmp/output.txt>
71
+ # @return [String, Object] Returns a String if stderr is a StringIO; otherwise, returns the stderr object
72
+ def stderr_to_s
73
+ stderr.respond_to?(:string) ? stderr.string : stderr
74
+ end
75
+ end
76
+ end
77
+ end