Class: JustRun

Inherits:
Object
  • Object
show all
Defined in:
lib/justrun.rb

Overview

TODO: Run multiple commands at the same time

Defined Under Namespace

Classes: CommandTimeout, Writer

Class Method Summary collapse

Class Method Details

.command(command, timeout: 0, block_size: 4096*4, buffer_output: false, chdir: Dir.pwd, init: ->(writer) {}, env: ENV, &block) ⇒ Object



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/justrun.rb', line 9

def self.command(command, timeout: 0, block_size: 4096*4, buffer_output: false, chdir: Dir.pwd, init: ->(writer) {}, env: ENV, &block)
  ret_code = -1

  buffers = {
      stdout: [],
      stderr: [],
      all: []
  }

  beginning_time = Time.now
  Open3.popen3 env, command, chdir: chdir do |stdin, stdout, stderr, wait_thr|
    writer = JustRun::Writer.new stdin
    init.call writer
    line_handler = -> line, name {
      if buffer_output
        buffers[name].push line
        buffers[:all].push line
      end
      if block
        block.call line, name, writer
      end
    }

    fileno_lines = {}
    begin
      files = [stdout, stderr]
      fileno2name = {
          stdout.fileno => :stdout,
          stderr.fileno => :stderr
      }

      was_eof = []
      until was_eof[stdout.fileno] && was_eof[stderr.fileno] do
        ready = IO.select files, stdin.closed? ? [] : [stdin], [], 0.1
        if timeout > 0 && (Time.now - beginning_time) > timeout
          `kill -9 #{wait_thr.pid}` # note: Process.kill does not work
          raise CommandTimeout, "Command: '#{command}' timed out with timeout #{timeout}s"
        end

        if ready
          readable = ready[0]
          readable.each do |f|
            fileno = f.fileno
            fileno_lines[fileno] ||= []
            lines = fileno_lines[fileno]
            name = fileno2name[fileno]

            begin
              data = f.read_nonblock block_size
              lines_new = data.lines
              if lines.length > 0 and lines[-1] !~ /\n\r?$/
                lines[-1] = lines[-1] + lines_new.shift
              end
              lines.push(*lines_new)
              while lines[0] =~ /\n\r?/
                line = lines.shift.chomp
                line_handler.call line, name
              end
            rescue EOFError => e
              was_eof[fileno] = true
            end
          end
          writable = ready[1]
          writable.each { |stdin|  writer.process }
        end
      end
      fileno_lines.each do |fileno, lines|
        name = fileno2name[fileno]
        if lines.length > 0
          line_handler.call lines.shift.chomp, name
        end
      end
    rescue IOError => e
      raise e
    end
    ret_code = wait_thr.value
  end

  if buffer_output
    {
      code: ret_code.exitstatus,
      stderr: buffers[:stderr],
      stdout: buffers[:stdout],
      all: buffers[:all]
    }
  else
    ret_code.exitstatus
  end
end