Module: Exeggutor

Defined in:
lib/exeggutor.rb

Defined Under Namespace

Classes: ProcessError, ProcessResult

Class Method Summary collapse

Class Method Details

.run!(args, can_fail: false, show_stdout: false, show_stderr: false, env: nil, cwd: nil, stdin_data: nil) ⇒ Object



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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/exeggutor.rb', line 51

def self.run!(args, can_fail: false, show_stdout: false, show_stderr: false, env: nil, cwd: nil, stdin_data: nil)
  # TODO: expand "~"? popen3 doesn't expand it by default
  if cwd
    stdin_stream, stdout_stream, stderr_stream, wait_thread = Dir.chdir(cwd) { Exeggutor::run_popen3(args, env) }
  else
    stdin_stream, stdout_stream, stderr_stream, wait_thread = Exeggutor::run_popen3(args, env)
  end

  stdin_stream.write(stdin_data) if stdin_data
  stdin_stream.close

  # Make the streams as synchronous as possible, to minimize the possibility of a surprising lack
  # of output
  stdout_stream.sync = true
  stderr_stream.sync = true

  stdout_str = +''  # Using unfrozen string
  stderr_str = +''

  # Start readers for both stdout and stderr
  stdout_thread = Thread.new do
    while (line = stdout_stream.gets)

      stdout_str << line
      print line if show_stdout
    end
  end

  stderr_thread = Thread.new do
    while (line = stderr_stream.gets)
      stderr_str << line
      warn line if show_stderr
    end
  end

  # Wait for process completion
  exit_status = wait_thread.value

  # Ensure all IO is complete
  stdout_thread.join
  stderr_thread.join

  # Close open pipes
  stdout_stream.close
  stderr_stream.close

  result = ProcessResult.new(
    stdout: stdout_str.force_encoding('UTF-8'),
    stderr: stderr_str.force_encoding('UTF-8'),
    exit_code: exit_status.exitstatus
  )

  if !can_fail && !result.success?
    error_str = <<~ERROR_STR
      Command failed: #{args.shelljoin}
      Exit code: #{result.exit_code}
      stdout: #{result.stdout}
      stderr: #{result.stderr}
    ERROR_STR
    raise ProcessError.new(result), error_str
  end

  result
end

.run_popen3(args, env) ⇒ Object



42
43
44
45
46
47
48
49
# File 'lib/exeggutor.rb', line 42

def self.run_popen3(args, env)
  # Use this weird [args[0], args[0]] thing for the case where a command with just one arg is being run
  if env
    Open3.popen3(env, [args[0], args[0]], *args.drop(1))
  else
    Open3.popen3([args[0], args[0]], *args.drop(1))
  end
end