Cheetah
=======
[![Travis Build](https://travis-ci.org/openSUSE/cheetah.svg?branch=master)](https://travis-ci.org/openSUSE/cheetah)
[![Code Climate](https://codeclimate.com/github/openSUSE/cheetah/badges/gpa.svg)](https://codeclimate.com/github/openSUSE/cheetah)
[![Coverage Status](https://img.shields.io/coveralls/openSUSE/cheetah.svg)](https://coveralls.io/r/openSUSE/cheetah?branch=master)


Your swiss army knife for executing external commands in Ruby safely and
conveniently.

Examples
--------

```ruby
# Run a command and capture its output
files = Cheetah.run("ls", "-la", stdout: :capture)

# Run a command and capture its output into a stream
File.open("files.txt", "w") do |stdout|
  Cheetah.run("ls", "-la", stdout: stdout)
end

# Run a command and handle errors
begin
  Cheetah.run("rm", "/etc/passwd")
rescue Cheetah::ExecutionFailed => e
  puts e.message
  puts "Standard output: #{e.stdout}"
  puts "Error output:    #{e.stderr}"
end
```

Features
--------

  * Easy passing of command input
  * Easy capturing of command output (standard, error, or both)
  * Piping commands together
  * 100% secure (shell expansion is impossible by design)
  * Raises exceptions on errors (no more manual status code checks)
    but allows to specify which non-zero codes are not an error
  * Thread-safety
  * Allows overriding environment variables
  * Optional logging for easy debugging
  * Running on changed root ( requires chroot permission )

Non-features
------------

  * Handling of interactive commands

Installation
------------

    $ gem install cheetah

Usage
-----

First, require the library:

```ruby
require "cheetah"
```

You can now use the `Cheetah.run` method to run commands.

### Running Commands

To run a command, just specify it together with its arguments:

```ruby
Cheetah.run("tar", "xzf", "foo.tar.gz")

Cheetah converts each argument to a string using `#to_s`.

```
### Passing Input

Using the `:stdin` option you can pass a string to command's standard input:

```ruby
Cheetah.run("python", stdin: source_code)
```

If the input is big you may want to avoid passing it in one huge string. In that
case, pass an `IO` as a value of the `:stdin` option. The command will read its
input from it gradually.

```ruby
File.open("huge_program.py") do |stdin|
  Cheetah.run("python", stdin: stdin)
end
```

### Capturing Output

To capture command's standard output, set the `:stdout` option to `:capture`.
You will receive the output as a return value of the call:

```ruby
files = Cheetah.run("ls", "-la", stdout: :capture)
```

The same technique works with the error output — just use the `:stderr` option.
If you specify capturing of both outputs, the return value will be a two-element
array:

```ruby
results, errors = Cheetah.run("grep", "-r", "User", ".", stdout: => :capture, stderr: => :capture)
```

If the output is big you may want to avoid capturing it into a huge string. In
that case, pass an `IO` as a value of the `:stdout` or `:stderr` option. The
command will write its output into it gradually.

```ruby
File.open("files.txt", "w") do |stdout|
  Cheetah.run("ls", "-la", stdout: stdout)
end
```

### Piping Commands

You can pipe multiple commands together and execute them as one. Just specify
the commands together with their arguments as arrays:

```ruby
processes = Cheetah.run(["ps", "aux"], ["grep", "ruby"], stdout: :capture)
```

### Error Handling

If the command can't be executed for some reason or returns an unexpected non-zero exit
status, Cheetah raises an exception with detailed information about the failure:

```ruby
# Run a command and handle errors
begin
  Cheetah.run("rm", "/etc/passwd")
rescue Cheetah::ExecutionFailed => e
  puts e.message
  puts "Standard output: #{e.stdout}"
  puts "Error output:    #{e.stderr}"
  puts "Exit status:     #{e.status.exitstatus}"
end
```
### Logging

For debugging purposes, you can use a logger. Cheetah will log the command, its
status, input and both outputs to it:

```ruby
Cheetah.run("ls -l", logger: logger)
```

### Overwriting env

If the command needs adapted environment variables, use the :env option.
Passed hash is used to update existing env (for details see ENV.update).
Nil value means unset variable. Environment is restored to its original state after
running the command.

```ruby
  Cheetah.run("env", env: { "LC_ALL" => "C" })
```

### Expecting Non-zero Exit Status

If command is expected to return valid a non-zero exit status like `grep` command
which return `1` if given regexp is not found, then option `:allowed_exitstatus`
can be used:

```ruby
# Run a command, handle exitstatus  and handle errors
begin
  exitstatus = Cheetah.run("grep", "userA", "/etc/passwd", allowed_exitstatus: 1)
  if exitstates == 0
    puts "found"
  else
    puts "not found"
  end
rescue Cheetah::ExecutionFailed => e
  puts e.message
  puts "Standard output: #{e.stdout}"
  puts "Error output:    #{e.stderr}"
  puts "Exit status:     #{e.status.exitstatus}"
end
```

Exit status is returned as last element of result. If it is only captured thing,
then it is return without array.
Supported input for `allowed_exitstatus` are anything supporting include, fixnum
or nil for no allowed existatus.

```ruby
# allowed inputs
allowed_exitstatus: 1
allowed_exitstatus: 1..5
allowed_exitstatus: [1, 2]
allowed_exitstatus: object_with_include_method
allowed_exitstatus: nil
```

### Setting Defaults

To avoid repetition, you can set global default value of any option passed too
`Cheetah.run`:

```ruby
# If you're tired of passing the :logger option all the time...
Cheetah.default_options = { :logger => my_logger }
Cheetah.run("./configure")
Cheetah.run("make")
Cheetah.run("make", "install")
Cheetah.default_options = {}
```

### Changing Working Directory

If diferent working directory is needed for running program, then suggested
usage is to enclose call into `Dir.chdir` method.

```ruby
Dir.chdir("/workspace") do
  Cheetah.run("make")
end
```

### Changing System Root

If a command needs to be executed in different system root then the `:chroot`
option can be used:

```ruby
Cheetah.run("/usr/bin/inspect", chroot: "/mnt/target_system")
```

### More Information

For more information, see the
[API documentation](http://rubydoc.info/github/openSUSE/cheetah/frames).

Compatibility
-------------

Cheetah should run well on any Unix system with Ruby 2.0.0, 2.1 and 2.2. Non-Unix
systems and different Ruby implementations/versions may work too but they were
not tested.

Authors
-------

  * [David Majda](http://github.com/dmajda)
  * [Josef Reidinger](http://github.com/jreidinger)
