#
# Spawns a process and connects pipes to its stdout/stderr and obtain
# its exit code.
#
#
# == Example:
#
# SubProcess.create do | shell |
# # Add some hooks here
# shell.on_stdout do | line |
# log line
# $stdout.puts line
# end
# shell.on_stderr do | line |
# log line
# $stderr.puts line
# end
# shell.on_failure do
# raise "'#{ command }' failed."
# end
#
# # Spawn a subprocess
# shell.exec command
# end
#
#
# == Hooks:
#
# on_stdout:: Executed when a new line arrived from sub-process's stdout.
# on_stderr:: Executed when a new line arrived from sub-process's stderr.
# on_exit:: Executed when sub process exited.
# on_success:: Executed when sub process exited successfully.
# on_failure:: Executed when sub process exited with an error.
#
#
# == Credit:
#
# Author: Yasuhito Takamiya
#
# Copyright (C) 2008-2012 NEC Corporation
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2, as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
module SubProcess
class Command
attr_reader :command
attr_reader :env
def initialize command, env = {}
@command = command
@env = env
end
def start
@env.each_pair do | key, value |
ENV[ key ]= value
end
Kernel.exec @command
end
end
class PipeSet
attr_reader :stdin
attr_reader :stdout
attr_reader :stderr
def initialize stdin, stdout, stderr
@stdin = stdin
@stdout = stdout
@stderr = stderr
end
def close
[ @stdin, @stdout, @stderr ].each do | each |
unless each.closed?
each.close
end
end
end
end
class Process
def initialize
stdin, stdout, stderr = Array.new( 3 ) { IO.pipe }
@child = SubProcess::PipeSet.new( stdin[ 1 ], stdout[ 0 ], stderr[ 0 ] )
@parent = SubProcess::PipeSet.new( stdin[ 0 ], stdout[ 1 ], stderr[ 1 ] )
end
def wait
::Process.wait @pid
end
def popen command, &block
@pid = fork_child( command )
# Parent process
@parent.close
begin
yield @child.stdout, @child.stderr
ensure
@child.close
end
self
end
############################################################################
private
############################################################################
def fork_child command
Kernel.fork do
@child.close
redirect_child_io
command.start
end
end
def redirect_child_io
STDIN.reopen @parent.stdin
STDOUT.reopen @parent.stdout
STDERR.reopen @parent.stderr
@parent.close
end
end
class IoHandlerThread
def initialize io, method
@io = io
@method = method
end
def start
Thread.new( @io, @method ) do | io, method |
while io.gets do
method.call $LAST_READ_LINE
end
end
end
end
class Shell
def self.open debug_options = {}, &block
block.call self.new( debug_options )
end
def initialize debug_options
@debug_options = debug_options
end
def child_status
$CHILD_STATUS
end
def on_stdout &block
@on_stdout = block
end
def on_stderr &block
@on_stderr = block
end
def on_exit &block
@on_exit = block
end
def on_success &block
@on_success = block
end
def on_failure &block
@on_failure = block
end
def exec command, env = { "LC_ALL" => "C" }
on_failure { raise "command #{ command } failed" } unless @on_failure
SubProcess::Process.new.popen SubProcess::Command.new( command, env ) do | stdout, stderr |
handle_child_output stdout, stderr
end.wait
handle_exitstatus
self
end
############################################################################
private
############################################################################
def handle_child_output stdout, stderr
tout = SubProcess::IoHandlerThread.new( stdout, method( :do_stdout ) ).start
terr = SubProcess::IoHandlerThread.new( stderr, method( :do_stderr ) ).start
tout.join
terr.join
end
# run hooks ################################################################
def handle_exitstatus
do_exit
if child_status.exitstatus == 0
do_success
else
do_failure
end
end
def do_stdout line
if @on_stdout
@on_stdout.call line
end
end
def do_stderr line
if @on_stderr
@on_stderr.call line
end
end
def do_failure
if @on_failure
@on_failure.call
end
end
def do_success
if @on_success
@on_success.call
end
end
def do_exit
if @on_exit
@on_exit.call
end
end
end
def create debug_options = {}, &block
Shell.open debug_options, &block
end
module_function :create
end
### Local variables:
### mode: Ruby
### coding: utf-8-unix
### indent-tabs-mode: nil
### End: