brew/Library/Homebrew/cask/lib/hbc/system_command.rb
Mike McQuaid ee253e465b Vendor all Homebrew's gems.
Homebrew's actually ended up using a fair few gems. While we want to
avoid Bundler at runtime (and this PR still does that, in fact uses
Bundler even less at runtime than it did before) writing our own version
to use at build-time seems redundant.
2017-05-07 13:52:57 +01:00

175 lines
4.7 KiB
Ruby

require "open3"
require "shellwords"
require "plist"
require "extend/io"
require "hbc/utils/hash_validator"
module Hbc
class SystemCommand
attr_reader :command
def self.run(executable, options = {})
new(executable, options).run!
end
def self.run!(command, options = {})
run(command, options.merge(must_succeed: true))
end
def run!
@processed_output = { stdout: "", stderr: "" }
odebug "Executing: #{expanded_command.utf8_inspect}"
each_output_line do |type, line|
case type
when :stdout
processed_output[:stdout] << line
ohai line.chomp if options[:print_stdout]
when :stderr
processed_output[:stderr] << line
ohai line.chomp if options[:print_stderr]
end
end
assert_success if options[:must_succeed]
result
end
def initialize(executable, options)
@executable = executable
@options = options
process_options!
end
private
attr_reader :executable, :options, :processed_output, :processed_status
def process_options!
options.extend(HashValidator)
.assert_valid_keys :input, :print_stdout, :print_stderr, :args, :must_succeed, :sudo
sudo_prefix = %w[/usr/bin/sudo -E --]
sudo_prefix = sudo_prefix.insert(1, "-A") unless ENV["SUDO_ASKPASS"].nil?
@command = [executable]
options[:print_stderr] = true unless options.key?(:print_stderr)
@command.unshift(*sudo_prefix) if options[:sudo]
@command.concat(options[:args]) if options.key?(:args) && !options[:args].empty?
@command[0] = Shellwords.shellescape(@command[0]) if @command.size == 1
nil
end
def assert_success
return if processed_status && processed_status.success?
raise CaskCommandFailedError.new(command.utf8_inspect, processed_output[:stdout], processed_output[:stderr], processed_status)
end
def expanded_command
@expanded_command ||= command.map do |arg|
if arg.respond_to?(:to_path)
File.absolute_path(arg)
else
String(arg)
end
end
end
def each_output_line(&b)
raw_stdin, raw_stdout, raw_stderr, raw_wait_thr =
Open3.popen3(*expanded_command)
write_input_to(raw_stdin)
raw_stdin.close_write
each_line_from [raw_stdout, raw_stderr], &b
@processed_status = raw_wait_thr.value
end
def write_input_to(raw_stdin)
[*options[:input]].each { |line| raw_stdin.print line }
end
def each_line_from(sources)
loop do
readable_sources = IO.select(sources)[0]
readable_sources.delete_if(&:eof?).first(1).each do |source|
type = (source == sources[0] ? :stdout : :stderr)
begin
yield(type, source.readline_nonblock || "")
rescue IO::WaitReadable, EOFError
next
end
end
break if readable_sources.empty?
end
sources.each(&:close_read)
end
def result
Result.new(command,
processed_output[:stdout],
processed_output[:stderr],
processed_status.exitstatus)
end
end
end
module Hbc
class SystemCommand
class 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.zero?
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 =~ /\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)
raise CaskError, "Empty plist input" unless output =~ /\S/
output.sub!(/\A(.*?)(<\?\s*xml)/m, '\2')
_warn_plist_garbage(command, Regexp.last_match[1]) if CLI.debug?
output.sub!(%r{(<\s*/\s*plist\s*>)(.*?)\Z}m, '\1')
_warn_plist_garbage(command, Regexp.last_match[2])
xml = Plist.parse_xml(output)
unless xml.respond_to?(:keys) && !xml.keys.empty?
raise CaskError, <<-EOS
Empty result parsing plist output from command.
command was:
#{command.utf8_inspect}
output we attempted to parse:
#{output}
EOS
end
xml
end
end
end
end