Class: YleTf::System

Inherits:
Object
  • Object
show all
Defined in:
lib/yle_tf/system.rb,
lib/yle_tf/system/io_handlers.rb,
lib/yle_tf/system/output_logger.rb,
lib/yle_tf/system/tf_hook_output_logger.rb

Overview

Helpers to execute system commands with error handling

Defined Under Namespace

Classes: IOHandlers, OutputLogger, TfHookOutputLogger

Constant Summary collapse

ExecuteError =
Class.new(YleTf::Error)
DEFAULT_ERROR_HANDLER =
->(_exit_code, error) { raise error }.freeze
DEFAULT_IO_HANDLERS =
{
  stdin: :dev_null,
  stdout: :info,
  stderr: :error
}.freeze

Class Method Summary collapse

Class Method Details

.attach_input_handler(handler, io, progname) ⇒ Object



74
75
76
77
# File 'lib/yle_tf/system.rb', line 74

def self.attach_input_handler(handler, io, progname)
  io_proc = IOHandlers.input_handler(handler)
  io_proc.call(io, progname)
end

.attach_output_handler(handler, io, progname) ⇒ Object



79
80
81
82
# File 'lib/yle_tf/system.rb', line 79

def self.attach_output_handler(handler, io, progname)
  io_proc = IOHandlers.output_handler(handler)
  io_proc.call(io, progname)
end

.cmd(*args, **opts) ⇒ Object

Executes the command and attaches IO streams



32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/yle_tf/system.rb', line 32

def self.cmd(*args, **opts)
  opts = DEFAULT_IO_HANDLERS.merge(opts)
  env = opts[:env] || {}
  progname = opts.fetch(:progname) { args.first }

  YleTf::Logger.debug { "Calling #{cmd_string(args, env)}" }

  status = Open3.popen3(env, *args, &handle_io(progname, opts))
  verify_exit_status(status, opts[:error_handler], cmd_string(args))
rescue Interrupt, Errno::ENOENT => e
  error(opts[:error_handler], "Failed to execute #{cmd_string(args)}: #{e}")
end

.cmd_string(args, env = nil) ⇒ Object



51
52
53
# File 'lib/yle_tf/system.rb', line 51

def self.cmd_string(args, env = nil)
  "`#{args.shelljoin}`#{" with env '#{env}'" if env && !env.empty?}"
end

.console_cmd(*args, **opts) ⇒ Object



22
23
24
25
26
27
28
29
# File 'lib/yle_tf/system.rb', line 22

def self.console_cmd(*args, **opts)
  env = opts[:env] || {}

  YleTf::Logger.debug { "Calling #{cmd_string(args, env)}" }

  system(env, *args)
  verify_exit_status($CHILD_STATUS, opts[:error_handler], cmd_string(args))
end

.error(handler, error_msg, exit_code = nil) ⇒ Object



91
92
93
94
95
96
# File 'lib/yle_tf/system.rb', line 91

def self.error(handler, error_msg, exit_code = nil)
  YleTf::Logger.debug(error_msg)

  handler ||= DEFAULT_ERROR_HANDLER
  handler.call(exit_code, ExecuteError.new(error_msg))
end

.handle_io(progname, handlers) ⇒ Object



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/yle_tf/system.rb', line 55

def self.handle_io(progname, handlers)
  lambda do |stdin, stdout, stderr, wait_thr|
    in_thr = attach_input_handler(handlers[:stdin], stdin, progname)
    out_thr = [
      attach_output_handler(handlers[:stdout], stdout, progname),
      attach_output_handler(handlers[:stderr], stderr, progname)
    ]

    # Wait for the process to exit
    wait_thr.value.tap do
      YleTf::Logger.debug("`#{progname}` exited, killing input handler thread")
      in_thr.kill if in_thr.is_a?(Thread)

      YleTf::Logger.debug('Waiting for output handler threads to stop')
      ThreadsWait.all_waits(out_thr)
    end
  end
end

.read_cmd(*args, **opts) ⇒ Object



45
46
47
48
49
# File 'lib/yle_tf/system.rb', line 45

def self.read_cmd(*args, **opts)
  buffer = StringIO.new
  cmd(*args, opts.merge(stdout: buffer))
  buffer.string
end

.verify_exit_status(status, handler, cmd) ⇒ Object



84
85
86
87
88
89
# File 'lib/yle_tf/system.rb', line 84

def self.verify_exit_status(status, handler, cmd)
  status.success? ||
    error(handler,
          "Failed to execute #{cmd} (#{status.exitstatus})",
          status.exitstatus)
end