# frozen_string_literal: true require "fileutils" require "jive/version" require "open3" module Jive class Error < StandardError; end def self.run(tasks) Jive::BatchRunner.new.run(tasks) end module Popen Result = Struct.new(:command, :stdout, :stderr, :status, :duration) def self.popen(command, path = nil, env = {}, &block) result = popen_with_detail(command, path, env, &block) ["#{result.stdout}#{result.stderr}", result.status&.exitstatus] end def self.popen_with_detail(command, path = Dir.pwd, env = {}) FileUtils.mkdir_p(path) unless File.directory?(path) captured_stdout = '' captured_stderr = '' exit_status = nil start = Time.now Open3.popen3(env.merge('PWD' => path), *Array(command), { chdir: path }) do |stdin, stdout, stderr, wait_thr| out_reader = Thread.new { stdout.read } err_reader = Thread.new { stderr.read } yield(stdin) if block_given? stdin.close captured_stdout = out_reader.value captured_stderr = err_reader.value exit_status = wait_thr.value end Result.new(command, captured_stdout, captured_stderr, exit_status, Time.now - start) end end class BatchRunner attr_reader :runner, :stdout def initialize(runner: Runner.new, stdout: STDOUT) @runner = runner @stdout = stdout end def run(tasks, verbose: true) runner.run(tasks) do |command, &run| stdout.puts stdout.puts "$ #{command.join(' ')}" result = run.call stdout.print result.stdout if verbose stdout.print result.stderr if verbose stdout.puts "==> Finished in #{result.duration} seconds" stdout.puts end stdout.puts '===================================================' if runner.all_success_and_clean? stdout.puts 'Passed successfully.' return 0 elsif runner.all_success? stdout.puts 'Passed successfully, but we have warnings:' stdout.puts emit_warnings return 2 else stdout.puts 'Something failed:' emit_warnings emit_errors return 1 end end private def emit_warnings runner.warned_results.each do |result| stdout.puts stdout.puts "**** #{result.command.join(' ')} had the following warning(s):" stdout.puts stdout.puts result.stderr stdout.puts end end def emit_errors runner.failed_results.each do |result| stdout.puts stdout.puts "**** #{result.command.join(' ')} failed with the following error(s):" stdout.puts stdout.puts result.stdout stdout.puts result.stderr stdout.puts end end end class Runner attr_reader :results def initialize @results = [] end def run(commands, &block) commands.each do |command| block.call(command) do cmd_result = Popen.popen_with_detail(command) results << cmd_result cmd_result end end end def all_success_and_clean? all_success? && all_stderr_empty? end def all_success? results.all? { |result| result.status.success? } end def all_stderr_empty? results.all? { |result| result.stderr.empty? } end def failed_results results.reject { |result| result.status.success? } end def warned_results results.select do |result| result.status.success? && !result.stderr.empty? end end end end