class Snowglobe::CommandRunner

Constants

TimeoutError

Attributes

args[R]
command_prefix[RW]
env[R]
options[R]
reader[R]
retries[RW]
run_quickly[RW]
run_successfully[RW]
status[R]
timeout[RW]
wrapper[R]
writer[R]

Public Class Methods

new( *args, env: {}, directory: Dir.pwd, run_successfully: false, **options ) { |self| ... } click to toggle source
# File lib/snowglobe/command_runner.rb, line 23
def initialize(
  *args,
  env: {},
  directory: Dir.pwd,
  run_successfully: false,
  **options
)
  @reader, @writer = IO.pipe
  @options = options.merge(err: [:child, :out], out: writer)

  @args = args
  @env = normalize_env(env)
  self.directory = directory
  @run_successfully = run_successfully

  @wrapper = ->(block) { block.call }
  @command_prefix = ""
  @run_quickly = false
  @retries = 1
  @num_times_run = 0
  @timeout = 20

  yield self if block_given?
end
run(*args, **options, &block) click to toggle source
# File lib/snowglobe/command_runner.rb, line 11
def self.run(*args, **options, &block)
  new(*args, **options, &block).tap(&:call)
end
run!(*args, **options, &block) click to toggle source
# File lib/snowglobe/command_runner.rb, line 15
def self.run!(*args, **options, &block)
  run(*args, run_successfully: true, **options, &block)
end

Public Instance Methods

around_command(&block) click to toggle source
# File lib/snowglobe/command_runner.rb, line 60
def around_command(&block)
  @wrapper = block
end
call() click to toggle source
# File lib/snowglobe/command_runner.rb, line 70
def call
  possibly_retrying do
    possibly_running_quickly do
      run_with_debugging

      if run_successfully && !success?
        fail!
      end
    end
  end

  self
end
directory() click to toggle source
# File lib/snowglobe/command_runner.rb, line 48
def directory
  options[:chdir]
end
directory=(directory) click to toggle source
# File lib/snowglobe/command_runner.rb, line 52
def directory=(directory)
  if directory.nil?
    raise ArgumentError, "Must provide a directory"
  end

  options[:chdir] = directory
end
elided_output() click to toggle source
# File lib/snowglobe/command_runner.rb, line 97
def elided_output
  lines = output.split(/\n/)
  new_lines = lines[0..4]

  if lines.size > 10
    new_lines << "(...#{lines.size - 10} more lines...)"
  end

  new_lines << lines[-5..-1]
  new_lines.join("\n")
end
exit_status() click to toggle source
# File lib/snowglobe/command_runner.rb, line 111
def exit_status
  status.exitstatus
end
fail!() click to toggle source
# File lib/snowglobe/command_runner.rb, line 115
    def fail!
      raise <<-MESSAGE
Command #{formatted_command.inspect} exited with status #{exit_status}.
Output:
#{divider("START") + output + divider("END")}
      MESSAGE
    end
formatted_command() click to toggle source
# File lib/snowglobe/command_runner.rb, line 64
def formatted_command
  [formatted_env, Shellwords.join(command)].
    reject(&:empty?).
    join(" ")
end
has_output?(expected_output) click to toggle source
# File lib/snowglobe/command_runner.rb, line 123
def has_output?(expected_output)
  if expected_output.is_a?(Regexp)
    output =~ expected_output
  else
    output.include?(expected_output)
  end
end
output() click to toggle source
# File lib/snowglobe/command_runner.rb, line 90
def output
  @_output ||= begin
    stop
    without_colors(reader.read)
  end
end
stop() click to toggle source
# File lib/snowglobe/command_runner.rb, line 84
def stop
  unless writer.closed?
    writer.close
  end
end

Private Instance Methods

command() click to toggle source
# File lib/snowglobe/command_runner.rb, line 143
def command
  ([command_prefix] + args).flatten.flat_map do |word|
    Shellwords.split(word)
  end
end
debug() { || ... } click to toggle source
# File lib/snowglobe/command_runner.rb, line 226
def debug
  if debugging_enabled?
    puts yield
  end
end
debugging_enabled?() click to toggle source
# File lib/snowglobe/command_runner.rb, line 222
def debugging_enabled?
  ENV["DEBUG_COMMANDS"] == "1"
end
divider(title = "") click to toggle source
# File lib/snowglobe/command_runner.rb, line 206
def divider(title = "")
  total_length = 72
  start_length = 3

  string = ""
  string << ("-" * start_length)
  string << title
  string << "-" * (total_length - start_length - title.length)
  string << "\n"
  string
end
formatted_env() click to toggle source
# File lib/snowglobe/command_runner.rb, line 149
def formatted_env
  env.map { |key, value| "#{key}=#{value.inspect}" }.join(" ")
end
normalize_env(env) click to toggle source
# File lib/snowglobe/command_runner.rb, line 137
def normalize_env(env)
  env.reduce({}) do |hash, (key, value)|
    hash.merge(key.to_s => value)
  end
end
possibly_retrying() { || ... } click to toggle source
# File lib/snowglobe/command_runner.rb, line 192
def possibly_retrying
  @num_times_run += 1
  yield
rescue StandardError => error
  debug { "#{error.class}: #{error.message}" }

  if @num_times_run < @retries
    sleep @num_times_run
    retry
  else
    raise error
  end
end
possibly_running_quickly() { || ... } click to toggle source
# File lib/snowglobe/command_runner.rb, line 172
def possibly_running_quickly(&block)
  if run_quickly
    begin
      Timeout.timeout(timeout, &block)
    rescue Timeout::Error
      stop

      message =
        "Command timed out after #{timeout} seconds: " +
        "#{formatted_command}\n" +
        "Output:\n" +
        output

      raise TimeoutError, message
    end
  else
    yield
  end
end
run() click to toggle source
# File lib/snowglobe/command_runner.rb, line 153
def run
  pid = spawn(env, *command, options)
  Process.waitpid(pid)
  @status = $?
end
run_with_debugging() click to toggle source
# File lib/snowglobe/command_runner.rb, line 163
def run_with_debugging
  debug { "\n\e[33mChanging to directory:\e[0m #{directory}" }
  debug { "\e[32mRunning command:\e[0m #{formatted_command}" }

  run_with_wrapper

  debug { "\n" + divider("START") + output + divider("END") }
end
run_with_wrapper() click to toggle source
# File lib/snowglobe/command_runner.rb, line 159
def run_with_wrapper
  wrapper.call(method(:run))
end
without_colors(string) click to toggle source
# File lib/snowglobe/command_runner.rb, line 218
def without_colors(string)
  string.gsub(/\e\[\d+(?:;\d+)?m(.+?)\e\[0m/, '\1')
end