brew/Library/Homebrew/cask/lib/hbc/system_command.rb

190 lines
4.8 KiB
Ruby
Raw Normal View History

2016-08-18 22:11:42 +03:00
require "open3"
require "vendor/plist/plist"
2017-11-22 23:12:13 +01:00
require "shellwords"
2016-08-18 22:11:42 +03:00
2016-10-04 15:24:58 +02:00
require "extend/io"
2018-07-16 22:46:02 +02:00
require "extend/hash_validator"
using HashValidator
2016-10-04 15:24:58 +02:00
2016-09-24 13:52:43 +02:00
module Hbc
class SystemCommand
2017-10-12 19:57:23 +02:00
extend Predicable
2016-08-18 22:11:42 +03:00
def self.run(executable, **options)
new(executable, **options).run!
2016-09-24 13:52:43 +02:00
end
2016-08-18 22:11:42 +03:00
def self.run!(command, **options)
2017-10-12 19:57:23 +02:00
run(command, **options, must_succeed: true)
2016-09-24 13:52:43 +02:00
end
2016-08-18 22:11:42 +03:00
2016-09-24 13:52:43 +02:00
def run!
@processed_output = { stdout: "", stderr: "" }
odebug command.shelljoin
2016-09-24 13:52:43 +02:00
each_output_line do |type, line|
case type
when :stdout
puts line.chomp if print_stdout?
2016-09-24 13:52:43 +02:00
processed_output[:stdout] << line
when :stderr
$stderr.puts Formatter.error(line.chomp) if print_stderr?
2016-09-24 13:52:43 +02:00
processed_output[:stderr] << line
end
2016-08-18 22:11:42 +03:00
end
2017-10-12 19:57:23 +02:00
assert_success if must_succeed?
2016-09-24 13:52:43 +02:00
result
end
2016-08-18 22:11:42 +03:00
def initialize(executable, args: [], sudo: false, input: [], print_stdout: false, print_stderr: true, must_succeed: false, env: {}, **options)
2016-09-24 13:52:43 +02:00
@executable = executable
2017-10-12 19:57:23 +02:00
@args = args
@sudo = sudo
@input = input
@print_stdout = print_stdout
@print_stderr = print_stderr
@must_succeed = must_succeed
2018-07-16 22:46:02 +02:00
options.assert_valid_keys!(:chdir)
2016-09-24 13:52:43 +02:00
@options = options
@env = env
@env.keys.grep_v(/^[\w&&\D]\w*$/) do |name|
raise ArgumentError, "Invalid variable name: '#{name}'"
end
2017-10-12 19:57:23 +02:00
end
def command
[*sudo_prefix, *env_args, executable.to_s, *expanded_args]
2016-09-24 13:52:43 +02:00
end
2016-08-18 22:11:42 +03:00
2016-09-24 13:52:43 +02:00
private
attr_reader :executable, :args, :input, :options, :processed_output, :processed_status, :env
2017-10-12 19:57:23 +02:00
attr_predicate :sudo?, :print_stdout?, :print_stderr?, :must_succeed?
def env_args
return [] if env.empty?
variables = env.map do |name, value|
sanitized_name = Shellwords.escape(name)
sanitized_value = Shellwords.escape(value)
"#{sanitized_name}=#{sanitized_value}"
end
["env", *variables]
end
def sudo_prefix
return [] unless sudo?
askpass_flags = ENV.key?("SUDO_ASKPASS") ? ["-A"] : []
["/usr/bin/sudo", *askpass_flags, "-E", "--"]
2016-09-24 13:52:43 +02:00
end
2016-08-18 22:11:42 +03:00
2016-09-24 13:52:43 +02:00
def assert_success
2017-09-24 19:24:46 +01:00
return if processed_status&.success?
2017-06-27 11:55:23 +02:00
raise CaskCommandFailedError.new(command, processed_output[:stdout], processed_output[:stderr], processed_status)
2016-09-24 13:52:43 +02:00
end
2016-08-18 22:11:42 +03:00
def expanded_args
@expanded_args ||= args.map do |arg|
2016-09-24 13:52:43 +02:00
if arg.respond_to?(:to_path)
File.absolute_path(arg)
elsif arg.is_a?(Integer) || arg.is_a?(Float)
arg.to_s
2016-09-24 13:52:43 +02:00
else
arg.to_str
2016-09-24 13:52:43 +02:00
end
2016-10-23 14:44:14 +02:00
end
2016-09-24 13:52:43 +02:00
end
2016-08-18 22:11:42 +03:00
2016-09-24 13:52:43 +02:00
def each_output_line(&b)
executable, *args = command
2017-12-01 22:07:21 +01:00
2016-09-24 13:52:43 +02:00
raw_stdin, raw_stdout, raw_stderr, raw_wait_thr =
Open3.popen3([executable, executable], *args, **options)
2016-08-18 22:11:42 +03:00
2017-03-08 03:03:49 +01:00
write_input_to(raw_stdin)
2016-09-24 13:52:43 +02:00
raw_stdin.close_write
each_line_from [raw_stdout, raw_stderr], &b
2016-08-18 22:11:42 +03:00
2016-09-24 13:52:43 +02:00
@processed_status = raw_wait_thr.value
end
2016-08-18 22:11:42 +03:00
2016-09-24 13:52:43 +02:00
def write_input_to(raw_stdin)
2017-10-12 19:57:23 +02:00
[*input].each(&raw_stdin.method(:print))
2016-09-24 13:52:43 +02:00
end
2016-08-18 22:11:42 +03:00
2016-09-24 13:52:43 +02:00
def each_line_from(sources)
loop do
readable_sources, = IO.select(sources)
readable_sources = readable_sources.reject(&:eof?)
break if readable_sources.empty?
readable_sources.each do |source|
2016-09-24 13:52:43 +02:00
begin
line = source.readline_nonblock || ""
type = (source == sources[0]) ? :stdout : :stderr
yield(type, line)
2016-09-24 13:52:43 +02:00
rescue IO::WaitReadable, EOFError
next
end
2016-08-18 22:11:42 +03:00
end
end
2016-09-24 13:52:43 +02:00
sources.each(&:close_read)
2016-08-18 22:11:42 +03:00
end
2016-09-24 13:52:43 +02:00
def result
Result.new(command,
processed_output[:stdout],
processed_output[:stderr],
processed_status.exitstatus)
end
2016-08-18 22:11:42 +03:00
2016-09-24 13:52:43 +02:00
class Result
attr_accessor :command, :stdout, :stderr, :exit_status
2016-08-18 22:11:42 +03:00
2016-09-24 13:52:43 +02:00
def initialize(command, stdout, stderr, exit_status)
@command = command
@stdout = stdout
@stderr = stderr
@exit_status = exit_status
end
2016-08-18 22:11:42 +03:00
2016-09-24 13:52:43 +02:00
def success?
@exit_status.zero?
end
2016-08-18 22:11:42 +03:00
def plist
@plist ||= begin
output = stdout
2016-08-18 22:11:42 +03:00
if /\A(?<garbage>.*?)<\?\s*xml/m =~ output
output = output.sub(/\A#{Regexp.escape(garbage)}/m, "")
warn_plist_garbage(garbage)
end
2016-08-18 22:11:42 +03:00
if %r{<\s*/\s*plist\s*>(?<garbage>.*?)\Z}m =~ output
output = output.sub(/#{Regexp.escape(garbage)}\Z/, "")
warn_plist_garbage(garbage)
end
2016-08-18 22:11:42 +03:00
Plist.parse_xml(output)
2016-09-24 13:52:43 +02:00
end
end
def warn_plist_garbage(garbage)
return unless ARGV.verbose?
return unless garbage =~ /\S/
opoo "Received non-XML output from #{Formatter.identifier(command.first)}:"
$stderr.puts garbage.strip
end
private :warn_plist_garbage
2016-08-18 22:11:42 +03:00
end
end
end