module BB::Unix

Unix utilities.

Public Class Methods

run_each(script, opts = {}) click to toggle source

Run each line of a script in a shell.

@param [Hash] script Script @return [Hash] Exit status (depends on mode)

# File lib/blackbox/unix.rb, line 16
def run_each(script, opts = {})
  opts = {
    quiet: false,
    failfast: true,
    spinner: nil,
    stream: false
  }.merge(opts)

  @minispinlock ||= Mutex.new
  script.lines.each_with_index do |line, i|
    line.chomp!
    case line[0]
    when '#'
      puts "\n" + line.bright unless opts[:quiet]
    when ':'
      opts[:quiet] = true if line == ':quiet'
      opts[:failfast] = false if line == ':return'
      opts[:spinner]  = nil   if line == ':nospinner'
      if line == ':stream'
        opts[:stream] = true
        opts[:quiet] = false
      end
    end
    next if line.empty? || ['#', ':'].include?(line[0])

    status = nil
    if opts[:stream]
      puts "\n> ".color(:green) + line.color(:black).bright
      rows, cols = STDIN.winsize
      @minispin_disable = false
      @minispin_last_char_at = Time.now
      @tspin ||= Thread.new do
        i = 0
        loop do
          break if @minispin_last_char_at == :end
          if Time.now - @minispin_last_char_at < 0.23 || @minispin_disable
            sleep 0.1
            next
          end
          @minispinlock.synchronize do
            next if @minispin_disable
            print "\e[?25l"
            print Paint[' ', '#000', Lol.rainbow(1, i / 3.0)]
            sleep 0.12
            print 8.chr
            print ' '
            print 8.chr
            i += 1
            print "\e[?25h"
          end
        end
      end

      PTY.spawn("stty rows #{rows} cols #{cols}; " + line) do |r, _w, pid|
        begin
          until r.eof?
            c = r.getc
            @minispinlock.synchronize do
              print c
              @minispin_last_char_at = Time.now
              c = c.encode('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: "\e") # barf.
              # hold on when we are (likely) inside an escape sequence
              @minispin_disable = true  if c.ord == 27 || c.ord < 9
              @minispin_disable = false if c =~ /[A-Za-z]/ || [13, 10].include?(c.ord)
            end
          end
        rescue Errno::EIO
          # Linux raises EIO on EOF, cf.
          # https://github.com/ruby/ruby/blob/57fb2199059cb55b632d093c2e64c8a3c60acfbb/ext/pty/pty.c#L519
          nil
        end

        _pid, status = Process.wait2(pid)
        @minispin_last_char_at = :end
        @tspin.join
        @tspin = nil
      end
    else
      opts[:spinner]&.call(true)
      output, status = Open3.capture2e(line)
      opts[:spinner]&.call(false)
      color = status.exitstatus == 0 ? :green : :red
      if status.exitstatus != 0 || !opts[:quiet]
        puts "\n> ".color(color) + line.color(:black).bright
        puts output
      end
    end
    next unless status.exitstatus != 0
    puts "Error, exit #{status.exitstatus}: #{line} (L#{i})".color(:red).bright

    exit status.exitstatus if opts[:failfast]
    return status.exitstatus
  end
  0
end