Class: DTC::Utils::Exec

Inherits:
Object
  • Object
show all
Defined in:
lib/dtc/utils/exec.rb

Overview

Execute a program with popen3, pipe data to-from a proc in an async loop

i = 10
Exec.run("cat",
  :input => "Initial input\n",
  :select_timeout => 1,
  :autoclose_stdin => false
  ) do |process, sout, serr, writer|
  if writer && (i -= 1) > 0 && i % 2 == 0
    puts "Writing"
    writer.call("Hello async world!\n", lambda { |*args| puts "Write complete" })
  elsif writer && i <= 0
    puts "Closing stdin"
    writer.call(nil, lambda { |*args| puts "Close complete" })
  end
  puts "Got #{sout.inspect}" if sout != ""
end

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(*cmd) ⇒ Exec

Returns a new instance of Exec.



65
66
67
68
69
70
71
72
73
# File 'lib/dtc/utils/exec.rb', line 65

def initialize *cmd
  options = cmd.last.is_a?(Hash) ? cmd.pop() : {}
  @input = options.delete(:input)
  @autoclose_stdin = options.delete(:autoclose_stdin) { true }
  @cmd = cmd + options.delete(:cmd) { [] }
  @select_timeout = options.delete(:select_timeout) { 5 }
  @running = false
  @ran = false
end

Instance Attribute Details

#cmdObject

Returns the value of attribute cmd.



75
76
77
# File 'lib/dtc/utils/exec.rb', line 75

def cmd
  @cmd
end

#exec_timeObject (readonly)

Returns the value of attribute exec_time.



76
77
78
# File 'lib/dtc/utils/exec.rb', line 76

def exec_time
  @exec_time
end

#inputObject

Returns the value of attribute input.



74
75
76
# File 'lib/dtc/utils/exec.rb', line 74

def input
  @input
end

#ranObject (readonly)

Returns the value of attribute ran.



76
77
78
# File 'lib/dtc/utils/exec.rb', line 76

def ran
  @ran
end

#runningObject (readonly)

Returns the value of attribute running.



76
77
78
# File 'lib/dtc/utils/exec.rb', line 76

def running
  @running
end

#stderrObject (readonly)

Returns the value of attribute stderr.



76
77
78
# File 'lib/dtc/utils/exec.rb', line 76

def stderr
  @stderr
end

#stdoutObject (readonly)

Returns the value of attribute stdout.



76
77
78
# File 'lib/dtc/utils/exec.rb', line 76

def stdout
  @stdout
end

Class Method Details

.git(*opts) ⇒ Object



51
52
53
# File 'lib/dtc/utils/exec.rb', line 51

def git *opts
  sys(*(%w[git] + opts))
end

.git_in(cwd, *opts) ⇒ Object



54
55
56
# File 'lib/dtc/utils/exec.rb', line 54

def git_in cwd, *opts
  Dir.chdir(cwd) { git(*opts) }
end

.rgit(*opts) ⇒ Object



57
58
59
# File 'lib/dtc/utils/exec.rb', line 57

def rgit *opts
  rsys(*(%w[git] + opts))
end

.rgit_in(cwd, *opts) ⇒ Object



60
61
62
# File 'lib/dtc/utils/exec.rb', line 60

def rgit_in cwd, *opts
  Dir.chdir(cwd) { return rgit(*opts) }
end

.rsys(*opts) ⇒ Object



44
45
46
47
# File 'lib/dtc/utils/exec.rb', line 44

def rsys *opts
  res = `#{opts.map {|e| Shellwords::shellescape(e.to_s)}.join(" ")}`
  $?.success? ? res : nil
end

.rsys_in(cwd, *opts) ⇒ Object



48
49
50
# File 'lib/dtc/utils/exec.rb', line 48

def rsys_in cwd, *opts
  Dir.chdir cwd { rsys(*opts) }
end

.run(*args, &block) ⇒ Object

self.new(&block).run(*args)



140
141
142
# File 'lib/dtc/utils/exec.rb', line 140

def self.run *args, &block
  self.new(*args).run(&block)
end

.sys(*opts) ⇒ Object



26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/dtc/utils/exec.rb', line 26

def sys *opts
  options = {}
  if opts.last.is_a?(Hash)
    options = opts.pop
  end
  arguments = Shellwords.join(opts.flatten.map(&:to_s).to_a)
  if options[:capture_stdout]
    result = `#{arguments}`
  else
    system(arguments)
    result = $?.exitstatus
  end
  raise "External command error: #{arguments}" unless options[:ignore_exit_code] || $?.success?
  result
end

.sys_in(cwd, *opts) ⇒ Object



41
42
43
# File 'lib/dtc/utils/exec.rb', line 41

def sys_in cwd, *opts
  Dir.chdir cwd { sys(*opts) }
end

Instance Method Details

#runObject

:yields: exec, new_stdout, new_stderr, write_proc



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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/dtc/utils/exec.rb', line 77

def run # :yields: exec, new_stdout, new_stderr, write_proc
  @start_time = Time.new
  @stdout = ""
  @stderr = ""
  @running = true
  stdin, stdout, stderr = Open3::popen3(*@cmd)
  begin
    stdout_read, stderr_read = "", ""
    MiniSelect.run(@select_timeout) do |select|
      writing = 0
      write_proc = lambda do |*args|
        text, callback = *args
        if text.nil?
          write_proc = nil
          select.close(stdin) do |miniselect, event, file, error|
            callback.call(miniselect, event, file, error) if callback
          end
        else
          writing += 1
          select.write(stdin, text) do |miniselect, event, file, error|
            if event == :error || event == :close
              writing = 0
              write_proc = nil
            else
              writing -= 1
            end
            callback.call(miniselect, event, file, error) if callback
          end
        end
      end
      select.add_read(stdout) do |miniselect, event, file, data_string|
        if data_string
          @stdout << data_string
          stdout_read << data_string
        end
      end
      select.add_read(stderr) do |miniselect, event, file, data_string|
        if data_string
          @stderr << data_string
          stderr_read << data_string
        end
      end
      select.every_beat do |miniselect|
        yield(self, stdout_read, stderr_read, write_proc) if block_given?
        if write_proc && writing == 0 && @autoclose_stdin
          select.close(stdin)
          write_proc = nil
        end
        stdout_read, stderr_read = "", ""
      end
      write_proc.call(@input) if @input
    end
    if stdout_read != "" || stderr_read != ""
      yield(self, stdout_read, stderr_read, nil) if block_given?
    end
  ensure
    [stdin, stdout, stderr].each { |f| f.close() if f && !f.closed? }
    @running = false
    @ran = true
  end
  @exec_time = Time.new - @start_time
end