Travis-CI Build Status

Pivotal Tracker Project

process_helper

Makes it easy to spawn Ruby sub-processes with guaranteed exit status handling, passing of lines to STDIN, and capturing of STDOUT and STDERR streams.

Goals

  • Always raise an exception on unexpected exit status (i.e. return code or $!)
  • Combine and interleave STDOUT and STDERR streams into STDOUT (using Open3.popen2e), so you don't have to worry about how to capture the output of both streams.
  • Provide useful options for suppressing output and including output when an exception is raised due to an unexpected exit status
  • Provide real-time streaming of combined STDOUT/STDERR streams in addition to returning full combined output as a string returned from the method and/or in the exception.
  • Support passing multi-line input to the STDIN stream via arrays of strings.
  • Allow override of the expected exit status(es) (zero is expected by default)
  • Provide short forms of all options for terse, concise usage.

Non-Goals

  • Any explicit support for process forks, multiple threads, or anything other than a single direct child process.
  • Any support for separate handling of STDOUT and STDERR streams

Why Yet Another Ruby Process Wrapper Library?

There's many other libraries to make it easier to work with processes in Ruby (see the Resources section). However, process_helper was created because none of them made it easy to run processes while meeting all of these requirements (redundant details are repeated above in Goals section):

  • Combine STDOUT/STDERR output streams interleaved chronologically as emitted
  • Stream STDOUT/STDERR real-time while process is still running, in addition to returning full output as a string and/or in an exception
  • Guarantee an exception is always raised on an unexpected exit status (and allow specification of multiple nonzero values as expected exit statuses)
  • Can be used very concisely. I.e. All behavior can be invoked via a single mixed-in module with single public method call using terse options with sensible defaults, no need to use IO streams directly or have any blocks or local variables declared.

Installation

Add this line to your application's Gemfile:

gem 'process_helper'

And then execute:

$ bundle

Or install it yourself as:

$ gem install process_helper

Usage

ProcessHelper is a Ruby module you can include in any Ruby code, and then call #process to run a command, like this:

require 'process_helper'
include ProcessHelper
process('echo "Hello"')

By default, ProcessHelper will combine any STDERR and STDOUT, and output it to STDOUT, and also return it as the result of the #process method.

Options

:expected_exit_status (short form :exp_st)

Expected Integer exit status, or array of expected Integer exit statuses. Default value is [0].

An exception will be raised by the ProcessHelper#process method if the actual exit status of the processed command is not (one of) the expected exit status(es).

Here's an example of expecting a nonzero failure exit status which matches the actual exit status (the actual exit status of a failed ls command will be 1 on OSX, 2 on Linux):

# The following will NOT raise an exception:
process('ls /does_not_exist', expected_exit_status: [1,2])

...but it WILL still print the output (in this case STDERR output from the failed ls command) to STDOUT:

ls: /does_not_exist: No such file or directory

Here's a second example of expecting a nonzero failure exit status but the command succeeds:

# The following WILL raise an exception:
process('printf FAIL', expected_exit_status: 1)

Here's the output of the above example:

FAIL
ProcessHelper::UnexpectedExitStatusError: Command succeeded but was expected to fail, pid 62974 exit 0 (expected [1]). Command: `printf FAIL`. Command Output: "FAIL"

:include_output_in_exception (short form :out_ex)

Boolean flag indicating whether output should be included in the message of the Exception (error) which will be raised by the ProcessHelper#process method if the command fails (has an unexpected exit status).

Here's an example of a failing command:

process('ls /does_not_exist', include_output_in_exception: true)

Here's the exception generated by the above example. Notice the "Command Output" with the "...No such file or directory" STDERR output of the failed command:

ProcessHelper::UnexpectedExitStatusError: Command failed, pid 64947 exit 1. Command: `ls /does_not_exist`. Command Output: "ls: /does_not_exist: No such file or directory
"

:puts_output (short form :out)

Valid values are :always, :error, and :never. Default value is :always.

  • :always will always print output to STDOUT
  • :error will only print output to STDOUT if command has an error - i.e. non-zero or unexpected exit status
  • :never will never print output to STDOUT

Warnings if failure output will be suppressed based on options

ProcessHelper will give you a warning if you pass a combination of options that would prevent ANY output from being printed or included in the Exception message if a command were to fail.

For example, in this case there is no output, and the expected exit status includes the actual failure status which is returned, so the warning is printed, and the only place the output will be seen is in the return value of the ProcessHelper#process method:

> process('ls /does_not_exist', expected_exit_status: [1,2], puts_output: :never, include_output_in_exception: false)
WARNING: Check your ProcessHelper options - :puts_output is :never, and :include_output_in_exception is false, so all error output will be suppressed if process fails.
 => "ls: /does_not_exist: No such file or directory\n"

Version

You can see the version of ProcessHelper in the ProcessHelper::VERSION constant:

ProcessHelper::VERSION
=> "0.0.1"

Contributing

  1. Fork it ( https://github.com/thewoolleyman/process_helper/fork )
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. If you are awesome, use git rebase --interactive to ensure you have a single atomic commit on your branch.
  6. Create a new Pull Request

Resources

Other Ruby Process tools/libraries