class Fulmar::Shell

Implements simple access to shell commands

Constants

DEFAULT_BUFFER_SIZE
DEFAULT_OPTIONS
VERSION

Attributes

debug[RW]
interactive[RW]
last_error[RW]
last_output[RW]
path[R]
quiet[RW]
strict[RW]

Public Class Methods

new(path = '.', host = 'localhost') click to toggle source
# File lib/fulmar/shell.rb, line 24
def initialize(path = '.', host = 'localhost')
  @host = host.nil? ? 'no_hostname_set' : host
  @path = (path.nil? || path.empty?) ? '.' : path
  @path = File.expand_path(@path) if local?
  reset_output
  @debug = false
  @quiet = true
  @strict = false
  @interactive = false
  @clean_environment = [] # list of things to clean from environment variables
end

Public Instance Methods

buffer_size(size) click to toggle source
# File lib/fulmar/shell.rb, line 78
def buffer_size(size)
  reset_output(size)
end
interactive=(interactive) click to toggle source
# File lib/fulmar/shell.rb, line 36
def interactive=(interactive)
  @interactive = interactive
  @quiet = false if interactive
end
local?() click to toggle source
# File lib/fulmar/shell.rb, line 70
def local?
  @host == 'localhost'
end
path=(path) click to toggle source
# File lib/fulmar/shell.rb, line 74
def path=(path)
  @path = local? ? File.expand_path(path) : path
end
run(command, options = DEFAULT_OPTIONS) click to toggle source
# File lib/fulmar/shell.rb, line 41
def run(command, options = DEFAULT_OPTIONS)
  reset_output(@last_output.max_size)
  command = [command] if command.class == String

  # is a custom path given?
  path = if options[:in]
           # is it absolute?
           (Pathname.new options[:in]).absolute? ? options[:in] : "#{@path}/#{options[:in]}"
         else
           @path
         end

  options[:error_message] ||= 'Last shell command returned an error.'

  command.unshift "cd \"#{path}\""

  # invoke a login shell?
  shell_command = shell_command(options[:login])

  @clean_environment << 'bundler' if options[:escape_bundler]

  if local?
    execute("#{shell_command} '#{escape_for_sh(command.join(' && '))}'", options[:error_message])
  else
    remote_command = escape_for_sh("#{shell_command} '#{escape_for_sh(command.join(' && '))}'")
    execute("ssh #{@host} '#{remote_command}'", options[:error_message])
  end
end

Protected Instance Methods

dump_error_message(command) click to toggle source
# File lib/fulmar/shell.rb, line 163
def dump_error_message(command)
  STDERR.puts command unless @debug
  STDERR.puts 'Command output (stdout):'
  @last_output.each { |line| STDERR.puts line }
  STDERR.puts ''
  STDERR.puts 'Command error message (stderr):'
  @last_error.each { |line| STDERR.puts line }
end
environment() click to toggle source
# File lib/fulmar/shell.rb, line 93
def environment
  env = ENV.clone
  if @clean_environment.include? 'bundler'
    bundler_variable_parts = %w(ruby gem_ bundle)
    # Remove any variables which contain the words above
    env.delete_if { |key| bundler_variable_parts.select { |part| key.downcase.include?(part) }.any? }
    env['PATH'] = path_without_bundler
  end
  puts "Environment: #{env.inspect}" if @debug
  env
end
escape_for_sh(text) click to toggle source
# File lib/fulmar/shell.rb, line 159
def escape_for_sh(text)
  text.gsub('\\', '\\\\').gsub("'", "'\\\\''")
end
execute(command, error_message) click to toggle source
# File lib/fulmar/shell.rb, line 109
def execute(command, error_message)
  if @interactive
    execute_interactive(command, error_message)
  else
    execute_quiet(command, error_message)
  end
end
execute_interactive(command, error_message) click to toggle source
# File lib/fulmar/shell.rb, line 117
def execute_interactive(command, error_message)
  unless system(command)
    puts "\n\n#{error_message}"
  end
end
execute_quiet(command, error_message) click to toggle source

Run the command and capture the output

# File lib/fulmar/shell.rb, line 124
def execute_quiet(command, error_message)
  # Ladies and gentleman: More debug, please!
  puts command if @debug
  return_value = -1

  Open3.popen3(environment, command) do |_stdin, stdout, stderr, wait_thread|
    Thread.new do
      stdout.each do |line|
        @last_output << line.strip
        puts line unless @quiet
      end
    end

    Thread.new do
      stderr.each do |line|
        @last_error << line
        puts line unless @quiet
      end
    end

    _stdin.close

    return_value = wait_thread.value

    if @strict and return_value.exitstatus != 0
      dump_error_message(command)
      fail error_message
    end
  end

  puts "Program exited with status #{return_value.exitstatus}." if @debug

  return_value.exitstatus == 0
end
path_without_bundler() click to toggle source
# File lib/fulmar/shell.rb, line 105
def path_without_bundler
  ENV['PATH'].split(':').reject { |path| path.include?('ruby') || path.include?('gems') }.join(':')
end
reset_output(size = DEFAULT_BUFFER_SIZE) click to toggle source
# File lib/fulmar/shell.rb, line 84
def reset_output(size = DEFAULT_BUFFER_SIZE)
  @last_output = Fulmar::RingBuffer.new(size)
  @last_error = Fulmar::RingBuffer.new(size)
end
shell_command(login) click to toggle source
# File lib/fulmar/shell.rb, line 89
def shell_command(login)
  login ? "env -i HOME=\"#{ENV['HOME']}\" LANG=\"#{ENV['LANG']}\" bash -lc" : 'bash -c'
end