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