127 lines
3.5 KiB
Ruby
127 lines
3.5 KiB
Ruby
require 'open3'
|
|
|
|
class Hbc::SystemCommand
|
|
def self.run(executable, options={})
|
|
command = _process_options(executable, options)
|
|
odebug "Executing: #{command.utf8_inspect}"
|
|
processed_stdout = ''
|
|
processed_stderr = ''
|
|
|
|
raw_stdin, raw_stdout, raw_stderr, raw_wait_thr =
|
|
Open3.popen3(*command.map { |arg| arg.respond_to?(:to_path) ? File.absolute_path(arg) : String(arg) })
|
|
|
|
if options[:input]
|
|
Array(options[:input]).each { |line| raw_stdin.puts line }
|
|
end
|
|
raw_stdin.close_write
|
|
|
|
while line = raw_stdout.gets
|
|
processed_stdout << line
|
|
ohai line.chomp if options[:print_stdout]
|
|
end
|
|
|
|
while line = raw_stderr.gets
|
|
processed_stderr << line
|
|
ohai line.chomp if options[:print_stderr]
|
|
end
|
|
|
|
raw_stdout.close_read
|
|
raw_stderr.close_read
|
|
|
|
processed_status = raw_wait_thr.value
|
|
|
|
_assert_success(processed_status, command, processed_stdout) if options[:must_succeed]
|
|
|
|
Hbc::SystemCommand::Result.new(command, processed_stdout, processed_stderr, processed_status.exitstatus)
|
|
end
|
|
|
|
def self.run!(command, options={})
|
|
run(command, options.merge(:must_succeed => true))
|
|
end
|
|
|
|
def self._process_options(executable, options)
|
|
options.assert_valid_keys :input, :print_stdout, :print_stderr, :args, :must_succeed, :sudo, :bsexec
|
|
sudo_prefix = %w{/usr/bin/sudo -E --}
|
|
bsexec_prefix = [ '/bin/launchctl', 'bsexec', options[:bsexec] == :startup ? '/' : options[:bsexec] ]
|
|
command = [executable]
|
|
options[:print_stderr] = true if !options.key?(:print_stderr)
|
|
command.unshift(*bsexec_prefix) if options[:bsexec]
|
|
command.unshift(*sudo_prefix) if options[:sudo]
|
|
command.concat(options[:args]) if options.key?(:args) and !options[:args].empty?
|
|
command
|
|
end
|
|
|
|
def self._assert_success(status, command, output)
|
|
unless status and status.success?
|
|
raise Hbc::CaskCommandFailedError.new(command.utf8_inspect, output, status)
|
|
end
|
|
end
|
|
end
|
|
|
|
class Hbc::SystemCommand::Result
|
|
|
|
attr_accessor :command, :stdout, :stderr, :exit_status
|
|
|
|
def initialize(command, stdout, stderr, exit_status)
|
|
@command = command
|
|
@stdout = stdout
|
|
@stderr = stderr
|
|
@exit_status = exit_status
|
|
end
|
|
|
|
def plist
|
|
@plist ||= self.class._parse_plist(@command, @stdout.dup)
|
|
end
|
|
|
|
def success?
|
|
@exit_status == 0
|
|
end
|
|
|
|
def merged_output
|
|
@merged_output ||= @stdout + @stderr
|
|
end
|
|
|
|
def to_s
|
|
@stdout
|
|
end
|
|
|
|
def self._warn_plist_garbage(command, garbage)
|
|
return true unless garbage =~ %r{\S}
|
|
external = File.basename(command.first)
|
|
lines = garbage.strip.split("\n")
|
|
opoo "Non-XML stdout from #{external}:"
|
|
STDERR.puts lines.map {|l| " #{l}"}
|
|
end
|
|
|
|
def self._parse_plist(command, output)
|
|
begin
|
|
raise Hbc::CaskError.new("Empty plist input") unless output =~ %r{\S}
|
|
output.sub!(%r{\A(.*?)(<\?\s*xml)}m, '\2')
|
|
_warn_plist_garbage(command, $1) if Hbc.debug
|
|
output.sub!(%r{(<\s*/\s*plist\s*>)(.*?)\Z}m, '\1')
|
|
_warn_plist_garbage(command, $2)
|
|
xml = Plist::parse_xml(output)
|
|
unless xml.respond_to?(:keys) and xml.keys.size > 0
|
|
raise Hbc::CaskError.new(<<-ERRMSG)
|
|
Empty result parsing plist output from command.
|
|
command was:
|
|
#{command.utf8_inspect}
|
|
output we attempted to parse:
|
|
#{output}
|
|
ERRMSG
|
|
end
|
|
xml
|
|
rescue Plist::ParseError => e
|
|
raise Hbc::CaskError.new(<<-ERRMSG)
|
|
Error parsing plist output from command.
|
|
command was:
|
|
#{command.utf8_inspect}
|
|
error was:
|
|
#{e}
|
|
output we attempted to parse:
|
|
#{output}
|
|
ERRMSG
|
|
end
|
|
end
|
|
end
|