class Session::AbstractSession

Attributes

debug[RW]
track_history[RW]
use_open3[RW]
use_spawn[RW]
debug[RW]
debug?[RW]
e[R]
errproc[RW]
history[R]
i[R]
o[R]
opts[R]

attributes

outproc[RW]
prog[R]
stderr[R]
stdin[R]
stdout[R]
threads[R]
track_history[R]
use_open3[R]
use_spawn[R]

Public Class Methods

default_prog() click to toggle source
# File lib/session.rb, line 87
def default_prog
  return @default_prog if defined? @default_prog and @default_prog
  if defined? self::DEFAULT_PROG
    return @default_prog = self::DEFAULT_PROG 
  else
    @default_prog = ENV["SESSION_#{ self }_PROG"]
  end
  nil
end
default_prog=(prog) click to toggle source
# File lib/session.rb, line 96
def default_prog= prog
  @default_prog = prog 
end
init() click to toggle source
# File lib/session.rb, line 103
def init
  @track_history = nil
  @use_spawn = nil
  @use_open3 = nil
  @debug = nil
end
new(*args) { |self| ... } click to toggle source

instance methods

# File lib/session.rb, line 135
def initialize(*args)
  @opts = hashify(*args)

  @prog = getopt('prog', opts, getopt('program', opts, self.class::default_prog))

  raise(ArgumentError, "no program specified") unless @prog

  @track_history = nil
  @track_history = Session::track_history unless Session::track_history.nil?
  @track_history = self.class::track_history unless self.class::track_history.nil?
  @track_history = getopt('history', opts) if hasopt('history', opts) 
  @track_history = getopt('track_history', opts) if hasopt('track_history', opts) 

  @use_spawn = nil
  @use_spawn = Session::use_spawn unless Session::use_spawn.nil?
  @use_spawn = self.class::use_spawn unless self.class::use_spawn.nil?
  @use_spawn = getopt('use_spawn', opts) if hasopt('use_spawn', opts)

  if defined? JRUBY_VERSION
    @use_open3 = true
  else
    @use_open3 = nil
    @use_open3 = Session::use_open3 unless Session::use_open3.nil?
    @use_open3 = self.class::use_open3 unless self.class::use_open3.nil?
    @use_open3 = getopt('use_open3', opts) if hasopt('use_open3', opts)
  end

  @debug = nil
  @debug = Session::debug unless Session::debug.nil?
  @debug = self.class::debug unless self.class::debug.nil?
  @debug = getopt('debug', opts) if hasopt('debug', opts) 

  @history = nil
  @history = History::new if @track_history 

  @outproc = nil
  @errproc = nil

  @stdin, @stdout, @stderr =
    if @use_spawn
      Spawn::spawn @prog
    elsif @use_open3
      Open3::popen3 @prog
    else
      __popen3 @prog
    end

  @threads = []

  clear

  if block_given?
    ret = nil
    begin
      ret = yield self
    ensure
      self.close!
    end
    return ret
  end

  return self
end

Public Instance Methods

__fork(*a, &b) click to toggle source
# File lib/session.rb, line 255
def __fork(*a, &b)
  verbose = $VERBOSE
  begin
    $VERBOSE = nil 
    Kernel::fork(*a, &b)
  ensure
    $VERBOSE = verbose
  end
end
__popen3(*cmd) { || ... } click to toggle source
# File lib/session.rb, line 216
def __popen3(*cmd)
  pw = IO::pipe   # pipe[0] for read, pipe[1] for write
  pr = IO::pipe
  pe = IO::pipe

  pid =
    __fork{
      # child
      pw[1].close
      STDIN.reopen(pw[0])
      pw[0].close

      pr[0].close
      STDOUT.reopen(pr[1])
      pr[1].close

      pe[0].close
      STDERR.reopen(pe[1])
      pe[1].close

      exec(*cmd)
    }

  Process::detach pid   # avoid zombies

  pw[0].close
  pr[1].close
  pe[1].close
  pi = [pw[1], pr[0], pe[0]]
  pw[1].sync = true
  if defined? yield
    begin
      return yield(*pi)
    ensure
      pi.each{|p| p.close unless p.closed?}
    end
  end
  pi
end
clear() click to toggle source

abstract methods

# File lib/session.rb, line 266
def clear
  raise NotImplementedError
end
Also aliased as: flush
close()
Alias for: close!
close!() click to toggle source
# File lib/session.rb, line 290
def close!
  [stdin, stdout, stderr].each{|pipe| pipe.close}
  stdin, stdout, stderr = nil, nil, nil
  true
end
Also aliased as: close
execute(command, redirects = {}) { |nil, buf| ... } click to toggle source
# File lib/session.rb, line 300
def execute(command, redirects = {})
  $session_command = command if @debug

  raise(PipeError, command) unless ready? 

# clear buffers
  clear

# setup redirects
  rerr = redirects[:e] || redirects[:err] || redirects[:stderr] || 
         redirects['stderr'] || redirects['e'] || redirects['err'] ||
         redirects[2] || redirects['2']

  rout = redirects[:o] || redirects[:out] || redirects[:stdout] || 
         redirects['stdout'] || redirects['o'] || redirects['out'] ||
         redirects[1] || redirects['1']

# create cmd object and add to history
  cmd = Command::new command.to_s

# store cmd if tracking history
  history << cmd if track_history

# mutex for accessing shared data
  mutex = Mutex::new

# io data for stderr and stdout
  err = {
    :io        => stderr,
    :cmd       => cmd.err,
    :name      => 'stderr',
    :begin     => false,
    :end       => false,
    :begin_pat => cmd.begin_err_pat,
    :end_pat   => cmd.end_err_pat,
    :redirect  => rerr,
    :proc      => errproc,
    :yield     => lambda{|buf| yield(nil, buf)},
    :mutex     => mutex,
  }
  out = {
    :io        => stdout,
    :cmd       => cmd.out,
    :name      => 'stdout',
    :begin     => false,
    :end       => false,
    :begin_pat => cmd.begin_out_pat,
    :end_pat   => cmd.end_out_pat,
    :redirect  => rout,
    :proc      => outproc,
    :yield     => lambda{|buf| yield(buf, nil)},
    :mutex     => mutex,
  }

begin
  # send command in the background so we can begin processing output
  # immediately - thanks to tanaka akira for this suggestion
    threads << Thread::new { send_command cmd }

  # init
    main       = Thread::current
    exceptions = []

  # fire off reader threads
    [err, out].each do |iodat|
      threads <<
        Thread::new(iodat, main) do |iodat, main|

          loop do
            main.raise(PipeError, command) unless ready? 
            main.raise ExecutionError, iodat[:name] if iodat[:end] and not iodat[:begin]

            break if iodat[:end] or iodat[:io].eof?

            line = iodat[:io].gets

            buf = nil

            case line
              when iodat[:end_pat]
                iodat[:end] = true
              # handle the special case of non-newline terminated output
                if((m = %r/(.+)__CMD/o.match(line)) and (pre = m[1]))
                  buf = pre
                end
              when iodat[:begin_pat]
                iodat[:begin] = true
              else
                next unless iodat[:begin] and not iodat[:end] # ignore chaff
                buf = line
            end

            if buf
              iodat[:mutex].synchronize do
                iodat[:cmd] << buf
                iodat[:redirect] << buf if iodat[:redirect]
                iodat[:proc].call buf  if iodat[:proc]
                iodat[:yield].call buf  if block_given?
              end
            end
          end

          true
      end
    end
  ensure
  # reap all threads - accumulating and rethrowing any exceptions
    begin
      while((t = threads.shift))
        t.join
        raise ExecutionError, 'iodat thread failure' unless t.value
      end
    rescue => e
      exceptions << e
      retry unless threads.empty?
    ensure
      unless exceptions.empty?
        meta_message = '<' << exceptions.map{|e| "#{ e.message } - (#{ e.class })"}.join('|') << '>'
        meta_backtrace = exceptions.map{|e| e.backtrace}.flatten
        raise ExecutionError, meta_message, meta_backtrace 
      end
    end
  end

# this should only happen if eof was reached before end pat
  [err, out].each do |iodat|
    raise ExecutionError, iodat[:name] unless iodat[:begin] and iodat[:end]
  end


# get the exit status
  get_status if respond_to? :get_status

  out = err = iodat = nil

  return [cmd.out, cmd.err]
end
flush()
Alias for: clear
getopt(opt, hash, default = nil) click to toggle source
# File lib/session.rb, line 198
def getopt opt, hash, default = nil
  key = opt
  return hash[key] if hash.has_key? key
  key = "#{ key }"
  return hash[key] if hash.has_key? key
  key = key.intern
  return hash[key] if hash.has_key? key
  return default
end
hasopt(opt, hash) click to toggle source
# File lib/session.rb, line 207
def hasopt opt, hash
  key = opt
  return key if hash.has_key? key
  key = "#{ key }"
  return key if hash.has_key? key
  key = key.intern
  return key if hash.has_key? key
  return false 
end
path() click to toggle source
# File lib/session.rb, line 270
def path 
  raise NotImplementedError
end
path=() click to toggle source
# File lib/session.rb, line 273
def path= 
  raise NotImplementedError
end
ready?() click to toggle source
# File lib/session.rb, line 285
def ready?
  (stdin and stdout and stderr) and
  (IO === stdin and IO === stdout and IO === stderr) and
  (not (stdin.closed? or stdout.closed? or stderr.closed?))
end
send_command(cmd) click to toggle source
# File lib/session.rb, line 276
def send_command cmd
  raise NotImplementedError
end
track_history=(bool) click to toggle source

concrete methods

# File lib/session.rb, line 281
def track_history= bool
  @history ||= History::new
  @track_history = bool
end

Private Instance Methods

hashify(*a) click to toggle source
# File lib/session.rb, line 296
def hashify(*a)
  a.inject({}){|o,h| o.update(h)}
end