Class: Kommando

Inherits:
Object
  • Object
show all
Defined in:
lib/kommando.rb,
lib/kommando.rb,
lib/kommando/error.rb,
lib/kommando/stdin.rb,
lib/kommando/stdout.rb,
lib/kommando/version.rb

Defined Under Namespace

Modules: Matchers Classes: Error, Stdin, Stdout, When

Constant Summary collapse

VERSION =
"0.1.1"
@@timeout =
nil
@@whens =
nil

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(cmd, opts = {}) ⇒ Kommando

Returns a new instance of Kommando.



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
# File 'lib/kommando.rb', line 56

def initialize(cmd, opts={})
  Thread.abort_on_exception=true

  @cmd = cmd
  @shell = @cmd.start_with? "$"
  @stdout = Kommando::Stdout.new @shell
  @stdin = Kommando::Stdin.new

  @output_stdout = opts[:output] == true
  @output_file = if opts[:output].class == String
    opts[:output]
  end

  @timeout = if opts[:timeout].class == Float
    opts[:timeout]
  elsif opts[:timeout].class.to_s == "Integer" || opts[:timeout].class.to_s == "Fixnum"
    opts[:timeout].to_f
  else
    @timeout = @@timeout
  end

  @timeout_happened = false
  @kill_happened = false
  @rescue_happened = false

  @env = opts[:env] || {}

  @code = nil
  @executed = false
  @process_completed = false

  if opts[:retry]
    if opts[:retry][:times]
      @retry_times_total = opts[:retry][:times]
      @retry_time = @retry_times_total
    end
    if opts[:retry][:sleep]
      @retry_sleep = opts[:retry][:sleep]
    end
  end

  @start_fired = false

  @thread = nil
  @pid = nil

  @whens = {}
  @when = When.new(self)

  if @@whens
    @@whens.instance_variable_get("@whens").each_pair do |event_name, blocks|
      blocks.each do |block|
        @when.register event_name, block
      end
    end
  end
end

Class Method Details

.puts(cmd, opts = {}) ⇒ Object



31
32
33
34
35
36
# File 'lib/kommando.rb', line 31

def puts(cmd, opts={})
  k = Kommando.new cmd, opts
  k.run
  Kernel.puts k.out
  k
end

.run(cmd, opts = {}) ⇒ Object



19
20
21
22
23
# File 'lib/kommando.rb', line 19

def run(cmd, opts={})
  k = Kommando.new cmd, opts
  k.run
  k
end

.run_async(cmd, opts = {}) ⇒ Object



25
26
27
28
29
# File 'lib/kommando.rb', line 25

def run_async(cmd, opts={})
  k = Kommando.new cmd, opts
  k.run_async
  k
end

.timeoutObject



38
39
40
# File 'lib/kommando.rb', line 38

def timeout
  @@timeout
end

.timeout=(value) ⇒ Object



42
43
44
# File 'lib/kommando.rb', line 42

def timeout=(value)
  @@timeout=value
end

.when(event_name, &block) ⇒ Object



46
47
48
49
# File 'lib/kommando.rb', line 46

def when(event_name, &block)
  @@whens ||= Kommando::When.new
  @@whens.register event_name, block
end

.when=(w) ⇒ Object



51
52
53
# File 'lib/kommando.rb', line 51

def when=(w)
  @@whens = w
end

Instance Method Details

#codeObject



308
309
310
# File 'lib/kommando.rb', line 308

def code
  @code
end

#inObject



312
313
314
# File 'lib/kommando.rb', line 312

def in
  @stdin
end

#killObject



120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/kommando.rb', line 120

def kill
  begin
    Process.kill('KILL', @pid)
  rescue Errno::ESRCH => ex
    #raise ex # see if happens
  end

  @kill_happened = true
  begin
    Timeout.timeout(1) do
      sleep 0.001 until @code # let finalize
    end
  rescue Timeout::Error => ex
    raise ex # let's see if happens
  end
end

#outObject



304
305
306
# File 'lib/kommando.rb', line 304

def out
  @stdout.to_s
end

#runObject



137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
# File 'lib/kommando.rb', line 137

def run
  return false if @executed
  @executed = true

  command, *args = if @shell
    trash, line = @cmd.split "$", 2
    line.lstrip!

    if `command -v bash`
      ["bash", "-c", line]
    else
      ["sh", "-c", line]
    end
  else
    @cmd.split " "
  end

  @env.each_pair do |k,v|
    ENV[k.to_s] = v
  end

  interpolated_args = []
  if @shell
    interpolated_args << args.shift
    shell_line = args[0]

    to_be_interpolated = shell_line.scan(/\$[^\s]*/)
    to_be_interpolated.each do |to_interpolate|
      if ENV[to_interpolate]
        shell_line.gsub!("${to_interpolate}", ENV[to_interpolate])
      else
        shell_line.gsub!("${to_interpolate}", "")
      end
    end

    interpolated_args << shell_line
  else
    args.each do |arg|
      interpolated_args << if arg.start_with? "$"
        env_name = arg.split("$")[1]
        ENV[env_name]
      else
        arg
      end
    end
  end

  begin
    debug "pty before spawn"
    make_pty_testable.spawn(command, *interpolated_args) do |stdout, stdin, pid|
      debug "pty in spawn"

      @pid = pid

      if @output_file
        stdout_file = File.open @output_file, 'w'
        stdout_file.sync = true
      end

      thread_stdin = nil
      self.when :start do
        thread_stdin = Thread.new do
          while true do
            break if @process_completed
            # c = nil
            # Timeout.timeout(1) do
            c = @stdin.getc
            #end

            unless c
              sleep 0.01
              next
            end

            stdin.write c
          end
        end
      end


      debug "thread_stdin started"

      unless @start_fired
        debug "when :start firing"
        @when.fire :start
        debug "when :start fired"
      else
        debug "when :start NOT fired, as :start has already been fired"
      end

      if @timeout
        begin
          Timeout.timeout(@timeout) do
            process_stdout(pid, stdout, stdout_file)
          end
        rescue Timeout::Error
          Process.kill('KILL', pid)
          @timeout_happened = true
        end
      else
        process_stdout(pid, stdout, stdout_file)
      end
      @process_completed = true
      debug "thread_stdin joining"
      thread_stdin.join
      debug "thread_stdin joined"
      stdout_file.close if @output_file
    end

  rescue RuntimeError => ex
    if ex.message == "can't get Master/Slave device"
      #suppress, weird stuff.
      @rescue_happened = true
    else
      raise ex
    end
  rescue ThreadError => ex
    if ex.message == "can't create Thread: Resource temporarily unavailable"
      if @retry_time && @retry_time > 0
        @executed = false
        @retry_time -= 1
        sleep @retry_sleep if @retry_sleep
        @when.fire :retry
        return run
      end

      raise_after_callbacks(ex)
    else
      raise_after_callbacks(ex)
    end
  rescue Errno::ENOENT => ex
    @when.fire :error
    raise Kommando::Error, "Command '#{command}' not found"
  ensure
    @code = if @timeout_happened
      1
    elsif @kill_happened
      137
    else
      begin
        Process.wait @pid if @pid
      rescue Errno::ECHILD => ex
        # safe to supress, I guess
      end

      if $?
        $?.exitstatus
      else
        137   # sometimes with ruby2.1 ? (maybefix now?)
      end
    end

    @when.fire :error if @rescue_happened
  end

  @when.fire :timeout if @timeout_happened
  @when.fire :exit
  if @code == 0
    @when.fire :success
  else
    @when.fire :failed
  end

  debug "run returning true"
  true
end

#run_asyncObject



114
115
116
117
118
# File 'lib/kommando.rb', line 114

def run_async
  @thread = Thread.new do
    run
  end
end

#waitObject



316
317
318
319
320
321
322
323
324
# File 'lib/kommando.rb', line 316

def wait
  debug "k.wait starting"
  exited = false
  self.when :exit do
    exited = true
  end
  sleep 0.0001 until exited
  debug "k.wait done"
end

#when(event, &block) ⇒ Object



326
327
328
329
# File 'lib/kommando.rb', line 326

def when(event, &block)
  @when.register event, block
  self
end