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

capture_command(executable, command, capture: :merge) click to toggle source

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
execute_command(executable, command, raise_on_failure = true) click to toggle source

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
which(program) click to toggle source

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

popen3(bin, command, stdout, stderr) click to toggle source
# 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
reader(input, output) click to toggle source
# 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

executable(name) click to toggle source

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