Project

caliph

0.0
No commit activity in last 3 years
No release in over 3 years
TDD-suitable Ruby tool for generating command-line commands via an OOP interface.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies
 Project Readme

Caliph

Caliph - a Ruby tool for generating and executing command-line commands.

Does your Ruby script or app need to generate commands to run at the CLI, to run with system() or similar? Want to generate them in a clean, testable Ruby api? How about automatically capturing output, exit code, and other goodies?

Because doing the system programming to accomplish all this is really kind of a pain, we've bundled all of these into a single gem.

Code Climate Build Status Dependency Status

Usage Examples

# An elaborate way to print a file listing:

shell = Caliph.new
long_listing = shell.run("ls", "-la")
if long_listing.succeeded?)
  puts long_listing.stdout
end

As ever, it's a contrived example, but you can see that you use {Caliph.new} to create a shell, and then call {Caliph::Shell#run} (or it's cousins) to build and execute commands, and then use methods on {Caliph::CommandRunResult} to examine their status and results. Pretty simple.

Create a command

# 'ls -la'
Caliph::CommandLine.new('ls', '-la')

# 'ls -la', another way
cmd = Caliph::CommandLine.new('ls')
cmd.options << '-la'

# Of course, there's a DSL
include Caliph::CommandLineDSL

# synonymous with Caliph::CommandLine.new('cat', '/etc/passwd')
cmd('cat', '/etc/passwd')

Add environment variables

# RAILS_ENV=production rake db:migrate
command = cmd('rake', 'db:migrate')
command.env['RAILS_ENV'] = 'production

Run any of the above by giving them to a shell

shell.run(command)

# Shell yields commands for configuration prior to running

shell.run("rake", "db:migrate") do |migrate|
  migrate.env['RAILS_ENV'] = "production"
  migrate.redirect_stdout("/tmp/prod.log")
end

See commands as they'd be run

{Caliph::CommandLine#string_format} or #to_s returns the entire command. {Caliph::CommandLine#command} returns just the command portion without prepended environment variables, which might be handy if you're passing the command to ssh or sudo and need to handle ENV differently.

command = cmd('java', 'my_file.jar')
command.env['JAVA_HOME'] = '~/java_files'

command.string_format # => "JAVA_HOME='~/java_files' java my_file.jar"

command.command # => "java my_file.jar"

Chaining commands

{Caliph::CommandChain} and related classes implement chained commands. If you've mixed in {Caliph::CommandLineDSL}, you can use operators &, |, and - for conditional, pipe, and path-chaining, respectively.

# Pipe Chain
# find . -name '*.sw.' | xargs rm
cmd('find', '.', "-name '*.sw.'") | cmd('xargs', 'rm')


# && - style conditional chain
# cd /tmp/trash && rm -rf *
cmd("cd", "/tmp/trash") & %w{rm -rf *}

# Double-hyphen separated commands
# sudo -- gem install bundler
cmd("sudo") - ["gem", "install", "bundler"]

Redirecting Output

# redirect STDOUT
# echo "foo" 1>some_file.txt
cmd('echo').redirect_stdout('some_file.txt')

# redirect STDERR
# echo "foo" 2>error_file.txt
cmd('echo').redirect_stderr('error_file.txt')

# chain redirects
# curl http://LRDesign.com 1>page.html 2>progress.txt
cmd('curl', 'http://LRDesign.com').redirect_stdout('page.html').redirect_stderr('progress.txt')

# redirect STDOUT and STDERR to the same destination with one command
# rm -rf 1>/dev/null 2>/dev/null
cmd('rm', '-rf').redirect_both('/dev/null')

Execute commands and capture the output

Several instance methods on {CommandLine} and {CommandChain} are provided for executing commands.

  • run Run the command and wait for the result. Returns a CommandRunResult instance.
  • execute Same as run, but terser, with no additional output added to STDOUT.
  • run_as_replacement Run the command in a new process and kill this process.
  • run_detached Run the command as a new background process. It can continue even if the caller terminates.
  • run_in_background Run the command as a background process, but kill it if the caller terminates
# find all vim swap files and wait for result
results = shell.run(cmd("find", '.', "-name *.sw.'"))

# delete all vim swap files in a parallel process
find_swaps = cmd('find', '.', "-name '*.sw.'") | cmd('xargs', 'rm')
shell.run_in_background(find_swaps)

# launch a server, terminating this process.  Useful for wrapper scripts!
launcher = cmd('pg_ctl', 'start -l', 'logfile')
shell.run_as_replacement(launcher)

Examine the results of a command

{Caliph::Shell#execute} returns a {Caliph::CommandRunResult} instance. {CommandRunResult} has the following useful instance methods:

  • stdout A String containing the contents of STDOUT.
  • stderr A String containing the contents of STDERR.
  • exit_code The exit code of the command
  • succeded? True if exit_code is 0.
  • must_succeed! Calls fail with an error message if the command did not exit successfully.

Testing code that uses Caliph

Caliph includes some useful classes for mocking out the command line environment for purposes of testing. See {Caliph::MockCommandResult} and {Caliph::CommandLine} in lib/caliph/testing for more info.

Further documentation on testing coming soon!

Acknowledgements

The first version of these classes were originally written as part of Mattock by Judson Lester: https://github.com/nyarly/mattock

Credits

Evan Dorn and Judson Lester of Logical Reality Design, Inc.