Class: Amberletters::Process

Inherits:
Object
  • Object
show all
Extended by:
Forwardable
Defined in:
lib/amberletters.rb

Constant Summary collapse

END_MARKER =
'__Amberletters_PROCESS_ENDED__'
DEFAULT_LOG_LEVEL =
::Logger::WARN
DEFAULT_TIMEOUT =
1.0
RUBY_EXT =

Shamelessly stolen from Rake

((RbConfig::CONFIG['ruby_install_name'] =~ /\.(com|cmd|exe|bat|rb|sh)$/) ?
      "" :
RbConfig::CONFIG['EXEEXT'])
RUBY =
File.join(
RbConfig::CONFIG['bindir'],
RbConfig::CONFIG['ruby_install_name'] + RUBY_EXT).
sub(/.*\s.*/m, '"\&"')

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(*args) ⇒ Process

Returns a new instance of Process.



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

def initialize(*args)
  options         = if args.last.is_a?(Hash) then args.pop else {} end
  @command        = args
  @triggers       = []
  @blocker        = nil
  @input_buffer   = StringIO.new
  @output_buffer  = StringScanner.new("")
  @env            = options.fetch(:env) {{}}
  @cwd            = options.fetch(:cwd) {Dir.pwd}
  @logger   = options.fetch(:logger) {
    l = ::Logger.new($stdout)
    l.level = DEFAULT_LOG_LEVEL
    l
  }
  @state         = :not_started
  @shell         = options.fetch(:shell) { '/bin/sh' }
  @transcript    = options.fetch(:transcript) {
    t = Object.new
    def t.<<(*)
      # NOOP
    end
    t
  }
  @history = TranscriptHistoryBuffer.new(@transcript)
  @timeout = options.fetch(:timeout) { DEFAULT_TIMEOUT }

  ObjectSpace.define_finalizer(self) do
    kill!
  end
end

Instance Attribute Details

#blockerObject

The Trigger currently being waited for, if any



232
233
234
# File 'lib/amberletters.rb', line 232

def blocker
  @blocker
end

#commandObject (readonly)

Command to run in a subshell



231
232
233
# File 'lib/amberletters.rb', line 231

def command
  @command
end

#cwdObject (readonly)

Working directory for the command



236
237
238
# File 'lib/amberletters.rb', line 236

def cwd
  @cwd
end

#inputObject (readonly)

Returns the value of attribute input.



237
238
239
# File 'lib/amberletters.rb', line 237

def input
  @input
end

#input_bufferObject (readonly)

Input waiting to be written to process



233
234
235
# File 'lib/amberletters.rb', line 233

def input_buffer
  @input_buffer
end

#outputObject (readonly)

Returns the value of attribute output.



238
239
240
# File 'lib/amberletters.rb', line 238

def output
  @output
end

#output_bufferObject (readonly)

Output ready to be read from process



234
235
236
# File 'lib/amberletters.rb', line 234

def output_buffer
  @output_buffer
end

#pidObject (readonly)

Returns the value of attribute pid.



239
240
241
# File 'lib/amberletters.rb', line 239

def pid
  @pid
end

#statusObject (readonly)

:not_started -> :running -> :ended -> :exited



235
236
237
# File 'lib/amberletters.rb', line 235

def status
  @status
end

#timeoutObject

Returns the value of attribute timeout.



240
241
242
# File 'lib/amberletters.rb', line 240

def timeout
  @timeout
end

Instance Method Details

#add_blocking_trigger(event, *args, &block) ⇒ Object



319
320
321
322
323
324
325
326
# File 'lib/amberletters.rb', line 319

def add_blocking_trigger(event, *args, &block)
  t = add_trigger(event, *args, &block)
  t.time_to_live = 1
  @logger.debug "waiting for #{t}"
  self.blocker = t
  catchup_trigger!(t)
  t
end

#add_nonblocking_trigger(event, *args, &block) ⇒ Object



292
293
294
295
296
# File 'lib/amberletters.rb', line 292

def add_nonblocking_trigger(event, *args, &block)
  t = add_trigger(event, *args, &block)
  catchup_trigger!(t)
  t
end

#add_trigger(event, *args, &block) ⇒ Object



298
299
300
301
302
303
# File 'lib/amberletters.rb', line 298

def add_trigger(event, *args, &block)
  t = build_trigger(event, *args, &block)
  triggers << t
  @logger.debug "added trigger on #{t}"
  t
end

#alive?Boolean

Returns:

  • (Boolean)


357
358
359
360
361
362
# File 'lib/amberletters.rb', line 357

def alive?
  ::Process.kill(0, @pid)
  true
rescue Errno::ESRCH, Errno::ENOENT
  false
end

#blocked?Boolean

Returns:

  • (Boolean)


364
365
366
# File 'lib/amberletters.rb', line 364

def blocked?
  @blocker
end

#ended?Boolean

Have we seen the end marker yet?

Returns:

  • (Boolean)


381
382
383
# File 'lib/amberletters.rb', line 381

def ended?
  @state == :ended
end

#exited?Boolean

Returns:

  • (Boolean)


376
377
378
# File 'lib/amberletters.rb', line 376

def exited?
  @state == :exited
end

#flush_output_buffer!Object



345
346
347
348
# File 'lib/amberletters.rb', line 345

def flush_output_buffer!
  @logger.debug "flushing output buffer"
  @output_buffer.terminate
end

#kill!(signal = "TERM") ⇒ Object



350
351
352
353
354
355
# File 'lib/amberletters.rb', line 350

def kill!(signal="TERM")
  handle_child_exit do
    @logger.info "Killing process #{@pid}"
    ::Process.kill(signal, @pid)
  end
end

#not_started?Boolean

Returns:

  • (Boolean)


372
373
374
# File 'lib/amberletters.rb', line 372

def not_started?
  @state == :not_started
end

#on(event, *args, &block) ⇒ Object



277
278
279
# File 'lib/amberletters.rb', line 277

def on(event, *args, &block)
  add_nonblocking_trigger(event, *args, &block)
end

#prepend_trigger(event, *args, &block) ⇒ Object



311
312
313
314
315
316
# File 'lib/amberletters.rb', line 311

def prepend_trigger(event, *args, &block)
  t = build_trigger(event, *args, &block)
  triggers.unshift(t)
  @logger.debug "prepended trigger on #{t}"
  t
end

#remove_trigger(t) ⇒ Object



305
306
307
308
309
# File 'lib/amberletters.rb', line 305

def remove_trigger(t)
  triggers.delete(t)
  @logger.debug "removed trigger on #{t}"
  t
end

#running?Boolean

Returns:

  • (Boolean)


368
369
370
# File 'lib/amberletters.rb', line 368

def running?
  @state == :running
end

#start!Object

Raises:



328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
# File 'lib/amberletters.rb', line 328

def start!
  raise StateError, "Already started!" unless not_started?
  @logger.debug "installing end marker handler for #{END_MARKER}"
  prepend_trigger(:output, /#{END_MARKER}/, :exclusive => false, :time_to_live => 1) do |process, data|
    handle_end_marker
  end
  handle_child_exit do
    cmd = wrapped_command
    @logger.debug "executing #{cmd.join(' ')}"
    merge_environment(@env) do
      @output, @input, @pid = PTY.spawn(*cmd)
    end
    @state = :running
    @logger.debug "spawned pid #{@pid}; in: #{@input.inspect}; out: #{@output.inspect}"
  end
end

#timeObject



385
386
387
# File 'lib/amberletters.rb', line 385

def time
  Time.now
end

#to_sObject



389
390
391
# File 'lib/amberletters.rb', line 389

def to_s
  "Process<pid: #{pid}; in: #{input.inspect}; out: #{output.inspect}>"
end

#wait_for(event, *args, &block) ⇒ Object



281
282
283
284
285
286
287
288
289
290
# File 'lib/amberletters.rb', line 281

def wait_for(event, *args, &block)
  raise "Already waiting for #{blocker}" if blocker
  t = add_blocking_trigger(event, *args, &block)
  @logger.debug "Entering wait cycle for #{event}"
  process_events
rescue
  unblock!
  triggers.delete(t)
  raise
end