lib/mattock/command-line.rb in mattock-0.3.0 vs lib/mattock/command-line.rb in mattock-0.3.1
- old
+ new
@@ -25,16 +25,20 @@
return true
rescue
return false
end
+ def format_streams
+ "stdout:#{stdout.empty? ? "[empty]\n" : "\n#{stdout}"}stderr:#{stderr.empty? ? "[empty]\n" : "\n#{stderr}"}---"
+ end
+
def must_succeed!
case exit_code
when 0
return exit_code
else
- fail "Command #{@command.inspect} failed with exit status #{exit_code}: \n#{streams.inspect}"
+ fail "Command #{@command.inspect} failed with exit status #{exit_code}: \n#{format_streams}"
end
end
end
class CommandLine
@@ -60,15 +64,19 @@
def initialize(executable, *options)
@executable = executable
@options = options
@redirections = []
+ @env = {}
yield self if block_given?
end
- attr_accessor :name, :executable, :options
+ attr_accessor :name, :executable, :options, :env
+ attr_reader :redirections
+ alias_method :command_environment, :env
+
def verbose
Rake.verbose && Rake.verbose != Rake::FileUtilsExt::DEFAULT
end
def name
@@ -77,10 +85,16 @@
def command
([executable] + options_composition + @redirections).join(" ")
end
+ def string_format
+ (command_environment.map do |key, value|
+ [key, value].join("=")
+ end + [command]).join(" ")
+ end
+
def options_composition
options
end
def redirect_to(stream, path)
@@ -105,23 +119,63 @@
def redirect_stdin(path)
redirect_from(path, 0)
end
- def self.execute(command)
- pipe = IO.popen(command)
- pid = pipe.pid
+ def spawn_process
+ host_stdout, cmd_stdout = IO.pipe
+ host_stderr, cmd_stderr = IO.pipe
+
+ pid = Process.spawn(command_environment, command, :out => cmd_stdout, :err => cmd_stderr)
+ cmd_stdout.close
+ cmd_stderr.close
+
+ return pid, host_stdout, host_stderr
+ end
+
+ def collect_result(pid, host_stdout, host_stderr)
pid, status = Process.wait2(pid)
- result = CommandRunResult.new(command, status, {1 => pipe.read})
- pipe.close
+
+ stdout = host_stdout.read
+ stderr = host_stderr.read
+ result = CommandRunResult.new(command, status, {1 => stdout, 2 => stderr})
+ host_stdout.close
+ host_stderr.close
+
return result
end
+ #If I wasn't worried about writing my own limited shell, I'd say e.g.
+ #Pipeline would be an explicit chain of pipes... which is probably as
+ #originally intended :/
+ def execute
+ collect_result(*spawn_process)
+ end
+
+ def background
+ pid, out, err = spawn_process
+ at_exit do
+ kill_process(pid)
+ Process.detach(pid)
+ end
+ return pid, out, err
+ end
+
+ def kill_process(pid)
+ Process.kill("INT", pid)
+ end
+
+ def complete(pid, out, err)
+ kill_process(pid)
+ collect_result(pid, out, err)
+ end
+
def run
- print command + " " if verbose
- result = self.class.execute(command)
- print "=> #{result.exit_code}" if verbose
+ print string_format + " "
+ result = execute
+ puts "=> #{result.exit_code}"
+ puts result.format_streams if verbose
return result
ensure
puts if verbose
end
@@ -133,40 +187,60 @@
run.must_succeed!
end
end
module CommandLineDSL
- def cmd(*args)
- CommandLine.new(*args)
+ def cmd(*args, &block)
+ CommandLine.new(*args, &block)
end
+
+ def escaped_command(*args, &block)
+ ShellEscaped.new(CommandLine.new(*args, &block))
+ end
end
class ShellEscaped < CommandLine
def initialize(cmd)
@escaped = cmd
end
def command
- "'" + @escaped.command.gsub(/'/,"\'") + "'"
+ "'" + @escaped.string_format.gsub(/'/,"\'") + "'"
end
+ def command_environment
+ {}
+ end
+
def name
@name || @escaped.name
end
+
+ def to_s
+ command
+ end
end
class CommandChain < CommandLine
def initialize
@commands = []
- yield self if block_given?
+ super(nil)
end
attr_reader :commands
def add(cmd)
yield cmd if block_given?
@commands << cmd
self
+ end
+
+ #Honestly this is sub-optimal - biggest driver for considering the
+ #mini-shell approach here.
+ def command_environment
+ @commands.reverse.inject({}) do |env, command|
+ env.merge(command.command_environment)
+ end
end
def name
@name || @commands.last.name
end