module Fourflusher::Executable
Module which provides support for running executables.
In a class it can be used as:
extend Executable executable :git
This will create two methods `git` and `git!` both accept a command but the later will raise on non successful executions. The methods return the output of the command.
Public Class Methods
Runs the given command, capturing the desired output.
@param [String] bin
The binary to use.
@param [Array<#to_s>] command
The command to send to the binary.
@param [Symbol] capture
Whether it should raise if the command fails.
@raise If the executable could not be located.
@return [(String, Process::Status)]
The desired captured output from the command, and the status from running the command.
# File lib/fourflusher/executable.rb, line 145 def self.capture_command(executable, command, capture: :merge) bin = which(executable) fail Fourflusher::Informative, "Unable to locate the executable `#{executable}`" unless bin require 'open3' command = command.map(&:to_s) case capture when :merge then Open3.capture2e(bin, *command) when :both then Open3.capture3(bin, *command) when :out then Open3.capture2(bin, *command) when :err then Open3.capture3(bin, *command).drop(1) when :none then Open3.capture2(bin, *command).last end end
Executes the given command displaying it if in verbose mode.
@param [String] bin
The binary to use.
@param [Array<#to_s>] command
The command to send to the binary.
@param [Bool] raise_on_failure
Whether it should raise if the command fails.
@raise If the executable could not be located.
@raise If the command fails and the `raise_on_failure` is set to true.
@return [String] the output of the command (STDOUT and STDERR).
# File lib/fourflusher/executable.rb, line 79 def self.execute_command(executable, command, raise_on_failure = true) bin = which(executable) fail Fourflusher::Informative, "Unable to locate the executable `#{executable}`" unless bin command = command.map(&:to_s) full_command = "#{bin} #{command.join(' ')}" if Config.instance.verbose? UI.message("$ #{full_command}") stdout = Indenter.new(STDOUT) stderr = Indenter.new(STDERR) else stdout = Indenter.new stderr = Indenter.new end status = popen3(bin, command, stdout, stderr) stdout = stdout.join stderr = stderr.join output = stdout + stderr unless status.success? if raise_on_failure fail Fourflusher::Informative, "#{full_command}\n\n#{output}" else UI.message("[!] Failed: #{full_command}".red) end end output end
Returns the absolute path to the binary with the given name on the current `PATH`, or `nil` if none is found.
@param [String] program
The name of the program being searched for.
@return [String,Nil] The absolute path to the given program, or `nil` if
it wasn't found in the current `PATH`.
# File lib/fourflusher/executable.rb, line 119 def self.which(program) program = program.to_s ENV['PATH'].split(File::PATH_SEPARATOR).each do |path| bin = File.expand_path(program, path) return bin if File.file?(bin) && File.executable?(bin) end nil end
Private Class Methods
# File lib/fourflusher/executable.rb, line 162 def self.popen3(bin, command, stdout, stderr) require 'open3' Open3.popen3(bin, *command) do |i, o, e, t| stdout_thread = reader(o, stdout) stderr_thread = reader(e, stderr) i.close status = t.value o.flush e.flush # wait for both threads to process the streams stdout_thread.join stderr_thread.join status end end
# File lib/fourflusher/executable.rb, line 182 def self.reader(input, output) Thread.new do buf = '' begin loop do buf << input.readpartial(4096) loop do string, separator, buf = buf.partition(/[\r\n]/) if separator.empty? buf = string break end output << (string << separator) end end rescue EOFError output << (buf << $INPUT_RECORD_SEPARATOR) unless buf.empty? end end end
Public Instance Methods
Creates the methods for the executable with the given name.
@param [Symbol] name
the name of the executable.
@return [void]
# File lib/fourflusher/executable.rb, line 52 def executable(name) define_method(name) do |*command| Executable.execute_command(name, Array(command).flatten, false) end define_method(name.to_s + '!') do |*command| Executable.execute_command(name, Array(command).flatten, true) end end