session-3.1.0/0000755000175000017500000000000011567734040011321 5ustar plbplbsession-3.1.0/LICENSE0000644000175000017500000000007011567734040012323 0ustar plbplbsame as Ruby's http://www.ruby-lang.org/en/LICENSE.txt session-3.1.0/README0000644000175000017500000001310111567734040012175 0ustar plbplbURLS: | http://raa.ruby-lang.org/project/session/ http://www.codeforpeople.com/lib/ruby/session/ NAME: | Session ::Sh ::Bash ::Shell ::IDL SYNOPSIS: | Session::* offers a set of classes built upon Open3::popen3 for driving external progams via pipes. It offers a significant abstraction over Open3::popen in that the stdout/stderr of each command sent can be deliniated: open3: i,o,e = Open3::popen3 '/bin/sh' i.puts 'ls' i.puts 'echo 42' now, how to determine the boundry between the output from 'ls' and 'echo'? the only (simple) way is start a process for each command i,o,e = Open3::popen3 '/bin/sh' i.puts 'ls' i.close stdout, stderr = o.read, e.read i,o,e = Open3::popen3 '/bin/sh' i.puts 'echo 42' i.close stdout, stderr = o.read, e.read session: sh = Session::new stdout, stderr = sh.execute 'ls' stdout, stderr = sh.execute 'echo 42' Both stderr and stdout can be redirected, and the exit_status of each command is made available: bash = Session::Bash.new stdout, stderr = StringIO::new, StringIO::new bash.execute 'ls', :stdout => stdout, :stderr => stderr # bash.execute 'ls', 1 => stdout, 2 => stderr # same thing # bash.execute 'ls', :o => stdout, :e => stderr # same thing exit_status = bash.exit_status A block form can be used to specify a callback to be invoked whenever output has become availible: bash = Session::Bash.new bash.execute( 'long_running_command.exe' ) do |out, err| logger << out if out elogger << err if err end Sessions are Thread safe (in the sense that they do not block on io operations) allowing commands spawned from guis to update widgets with output while running in the background. button.configure 'action' => lambda do sh = Session::new sh.execute(cmd) do |o,e| out_widget.update o if o err_widget.update e if e end end SAMPLES: | see samples/* HISTORY: | 3.1.0: - patches from @headius 3.0.0 - move to github 2.4.0: - added ability to specify stdin for Session::Bash and Session::Sh sh = Session::new sh.execute 'cat', :stdin => io sh.execute 'cat', :stdin => string sh.execute 'cat', :stdin => stringio 2.3.0: - fixed warning of @debug being un-initialized 2.2.0: - added a private munged version of Open3::open3. the builtin one causes the child process to become a child of init, this was very inconvenient because it was sometimes hard to crawl proces trees - the parent was lost. now the seesion is a child process that has been detached using Process::detach. this results in less suprising behaviour; for instance sending signal TERM to a process results in any sessions it had open dying as well. you can use Session::use_open3=true or ENV['SESSION_USE_OPEN3']='1' for the old behaviour if you need it. - added Session::Bash::Login class. this class opens a session which has all the normal settings of a bash loging shell (.bashrc is sourced). this if often convenient as paths, aliases, etc. are set as normal. - moved the Spawn module inside the Session module. now the Session module is the namespace for everything so using session pollutes namespace less. 2.1.9: - fixed bug where setting track history after creation caused later failure in execute (@history =[] only in ctor). thanks leon breedt ! - updates to README - included session-x.x.x.rpa file - thanks batsman - to_str/to_s/to_yaml for History/Command is now valid yaml (updated samples to reflect this) - inspect for History/Command is now ruby's default 2.1.8: - greatly simplified read loop using two reader threads, one for stderr and one for stdout alongside a mutex to protect data. this streamlined the code alot vs. the old select method including allowing removal of the linbuffer class. the interface remains exactly as before however. 2.1.7: - improved thread safe non-blocking read method - gemspec 2.1.6: - wrapped send_command in a Thread (send async) so output processing can commend immeadiately. this was o.k. before, but had strange behaviour when using popen3 from threads. thanks to tanaka akira for this suggestion. - iff ENV['SESSION_USE_SPAWN'] is set Session uses Spawn::spawn instead of Open3::popen3. also noted that spawn seems to be a bit faster. - added tests for threads. - run 'sh SESSION_USE_SPAWN=1 ruby test/session.rb' to test using spawn - added test for idl so it's test is not run if system doesn't have it, all that should be required for 'ruby test/session.rb' is should be sh' - removed sample/tcsh and note about Tcsh and Csh in README - stderr redirection/separation is flaky in those shells 2.1.5: - added Session.use_spawn=, AbstractSession.use_spawn=, and an :use_session=> option to AbstractSession#initialize. if any of them are set the code uses Spawn::spawn to create external processes instead of Open3::popen3. Spawn::spawn uses named pipes (fifos) for IPC instead of forking and pipes. the fork used in popen3 can cause strange behaviour with multi-threaded apps (like a tk app). see source for details 2.1.4: - added Thread.exclusive{} wrapper when io is read to works in multi threaded apps AUTHOR: | ara.t.howard@noaa.gov session-3.1.0/gemspec.rb0000755000175000017500000000300411567734040013271 0ustar plbplb#! /usr/bin/env ruby lib, version, *ignored = ARGV unless lib lib = File.basename(Dir.pwd) end unless version mod = lib.capitalize require "./lib/#{ lib }" version = eval(mod).send(:version) end abort('no lib') unless lib abort('no version') unless version puts "### gemspec: #{ lib }-#{ version }" $VERBOSE = nil shiteless = lambda{|list| list.delete_if{|file| file =~ %r/\.(git|svn|tmp|sw.|bak)$/}} files = shiteless[Dir::glob("**/**")] executables = shiteless[Dir::glob("bin/*")].map{|exe| File.basename(exe)} has_rdoc = true #File.exist?('doc') test_files = "test/#{ lib }.rb" if File.file?("test/#{ lib }.rb") extensions = [] %w( Makefile configure extconf.rb rakefile Rakefile mkrf_conf ).each do |ext| extensions << ext if File.exists?(ext) end template = <<-__ Gem::Specification::new do |spec| spec.name = #{ lib.inspect } spec.version = #{ version.inspect } spec.platform = Gem::Platform::RUBY spec.summary = #{ lib.inspect } spec.files = #{ files.inspect } spec.executables = #{ executables.inspect } spec.require_path = "lib" spec.has_rdoc = #{ has_rdoc.inspect } spec.test_files = #{ test_files.inspect } #spec.add_dependency 'lib', '>= version' #spec.add_dependency 'fattr' spec.extensions.push(*#{ extensions.inspect }) spec.rubyforge_project = 'codeforpeople' spec.author = "Ara T. Howard" spec.email = "ara.t.howard@gmail.com" spec.homepage = "http://github.com/ahoward/#{ lib }/tree/master" end __ puts template session-3.1.0/test/0000755000175000017500000000000011567734040012300 5ustar plbplbsession-3.1.0/test/session.rb0000644000175000017500000001406611567734040014317 0ustar plbplb %w(lib ../lib . ..).each{|d| $:.unshift d} require 'session' $VERBOSE=nil STDOUT.sync = true STDERR.sync = true STDOUT.puts "Session::VERSION <#{ Session::VERSION }>" STDOUT.puts "Session.use_spawn <#{ Session.use_spawn ? 'true' : 'false' }>" STDOUT.puts "Session.use_open3 <#{ Session.use_open3 ? 'true' : 'false' }>" Session::debug = true # # a timeout method which does not stop all threads! # for testing only! # class TimeoutError < StandardError; end def timeout n #{{{ # JRuby does not support fork, so we stub out timeout here return yield if defined? JRUBY_VERSION ret = nil cid = fork unless cid trap('SIGQUIT'){ exit! } sleep n begin; Process.kill 'SIGUSR1', Process.ppid; rescue Errno::Exception; end exit! else begin handler = trap('SIGUSR1'){throw :timeout, TimeoutError.new} thrown = catch(:timeout){ ret = yield } if TimeoutError === thrown display STDIN.gets raise thrown end ensure begin; Process.kill 'SIGQUIT', cid; rescue Exception; end begin; Process.wait; rescue Exception => e; end trap 'SIGUSR1', handler if defined? handler end end ret #}}} end def display #{{{ puts "$session_command < #{ $session_command.inspect }> " puts "$session_iodat < #{ $session_iodat.inspect }> " puts "$session_selecting < #{ $session_selecting.inspect }> " # puts "$session_buffer < #{ $session_buffer.inspect }> " # puts "$session_err < #{ $session_err.inspect }> " # puts "$session_out < #{ $session_out.inspect }> " # puts "$session_iodat_name < #{ $session_iodat_name.inspect }> " # puts "$session_reading < #{ $session_reading.inspect }> " # puts "$session_buf < #{ $session_buf.inspect }> " # puts "$session_lines < #{ $session_lines.inspect }> " # puts "$session_line < #{ $session_line.inspect }> " # puts "$session_getting_status < #{ $session_getting_status.inspect }> " self #}}} end system "which idl > /dev/null 2>&1" HAVE_IDL = ($?.exitstatus == 0 ? true : false) require "test/unit" module Session class Test < Test::Unit::TestCase def test_0 #{{{ sh = nil assert_nothing_raised { sh = Shell.new } #}}} end def test_1 #{{{ assert_nothing_raised { timeout(16) { sh = nil assert_nothing_raised { sh = Shell.new } sh.execute 'ls' } } #}}} end def test_3 #{{{ assert_nothing_raised { timeout(64) { sh = nil assert_nothing_raised { sh = Shell.new } 128.times { sh.execute 'echo 42' } } } #}}} end def test_4 #{{{ cmds = ['ls', 'echo 42', 'printf "foobar"', 'printf "foobar\n"'] assert_nothing_raised { timeout(64) { sh = nil assert_nothing_raised { sh = Shell.new } 128.times { cmds.each{|cmd| sh.execute cmd;sh.execute "#{cmd} 1>&2"} } } } #}}} end def test_5 #{{{ assert_nothing_raised { timeout(16) { sh = nil assert_nothing_raised { sh = Shell.new } out, err = sh.execute 'echo 42' assert_equal '42', out.strip out, err = sh.execute 'echo "forty-two" 1>&2' assert_equal 'forty-two', err.strip } } #}}} end def test_6 #{{{ out = '' err = '' assert_nothing_raised { timeout(16) { sh = nil assert_nothing_raised { sh = Shell.new } sh.execute 'echo 42', :stdout => out, :stderr => err assert_equal '42', out.strip sh.execute 'echo "forty-two" 1>&2', :stdout => out, :stderr => err assert_equal 'forty-two', err.strip } } #}}} end def test_7 #{{{ #$DEBUG = true assert_nothing_raised { timeout(16) { sh = nil assert_nothing_raised { sh = Shell.new } sh.execute('echo 42') do |out, err| if out assert_equal '42', out.strip end end sh.execute('echo "forty-two" 1>&2') do |out, err| if err assert_equal 'forty-two', err.strip end end } } #ensure #$DEBUG = true #}}} end if HAVE_IDL def test_8 #{{{ assert_nothing_raised { timeout(16) { idl = nil assert_nothing_raised { idl = IDL.new } out = ''; err = '' idl.execute 'printf, -1, 42', :stdout => out, :stderr => err assert_equal '42', out.strip out = ''; err = '' idl.execute 'printf, -2, \'forty-two\'', :stdout => out, :stderr => err assert_equal 'forty-two', err.strip out = ''; err = '' idl.execute 'foo', :stdout => out, :stderr => err assert_match %r/undefined procedure/io, err } } #}}} end end def test_9 #{{{ assert_nothing_raised { timeout(16) { lines = [] Thread.new { sh = nil assert_nothing_raised { sh = Shell.new } sh.debug = true #cmd = 'date; sleep 1;' * 3 cmd = 'ruby -e "puts 42; sleep 0.1"' * 3 sh.execute(cmd) do |o,e| line = o || e lines << [Time.now.to_f, line] end }.join i = 0 while((a = lines[i]) and (b = lines[i + 1])) ta = a.first tb = b.first # they all come back at once if thread hung sending cmd... # make sure we got output about a second apart... begin assert( (tb - ta) >= 0.1 ) rescue Exception STDERR.puts "lines : <#{ lines.inspect}>" STDERR.puts "i : <#{ i }>" STDERR.puts "b : <#{ b.inspect }>" STDERR.puts "a : <#{ a.inspect }>" STDERR.puts "tb : <#{ tb }>" STDERR.puts "ta : <#{ ta }>" raise end i += 1 end } } #}}} end end end session-3.1.0/lib/0000755000175000017500000000000011567734040012067 5ustar plbplbsession-3.1.0/lib/session.rb0000755000175000017500000004112711567734040014107 0ustar plbplbrequire 'open3' require 'tmpdir' require 'thread' require 'yaml' require 'tempfile' module Session VERSION = '3.1.0' def self.version() VERSION end @track_history = ENV['SESSION_HISTORY'] || ENV['SESSION_TRACK_HISTORY'] @use_spawn = ENV['SESSION_USE_SPAWN'] @use_open3 = ENV['SESSION_USE_OPEN3'] @debug = ENV['SESSION_DEBUG'] class << self attr :track_history, true attr :use_spawn, true attr :use_open3, true attr :debug, true def new(*a, &b) Sh::new(*a, &b) end alias [] new end class PipeError < StandardError; end class ExecutionError < StandardError; end class History def initialize; @a = []; end def method_missing(m,*a,&b); @a.send(m,*a,&b); end def to_yaml(*a,&b); @a.to_yaml(*a,&b); end alias to_s to_yaml alias to_str to_yaml end # class History class Command class << self def cmdno; @cmdno ||= 0; end def cmdno= n; @cmdno = n; end end # attributes attr :cmd attr :cmdno attr :out,true attr :err,true attr :cid attr :begin_out attr :end_out attr :begin_out_pat attr :end_out_pat attr :begin_err attr :end_err attr :begin_err_pat attr :end_err_pat def initialize(command) @cmd = command.to_s @cmdno = self.class.cmdno self.class.cmdno += 1 @err = '' @out = '' @cid = "%d_%d_%d" % [$$, cmdno, rand(Time.now.usec)] @begin_out = "__CMD_OUT_%s_BEGIN__" % cid @end_out = "__CMD_OUT_%s_END__" % cid @begin_out_pat = %r/#{ Regexp.escape(@begin_out) }/ @end_out_pat = %r/#{ Regexp.escape(@end_out) }/ @begin_err = "__CMD_ERR_%s_BEGIN__" % cid @end_err = "__CMD_ERR_%s_END__" % cid @begin_err_pat = %r/#{ Regexp.escape(@begin_err) }/ @end_err_pat = %r/#{ Regexp.escape(@end_err) }/ end def to_hash %w(cmdno cmd out err cid).inject({}){|h,k| h.update k => send(k) } end def to_yaml(*a,&b) to_hash.to_yaml(*a,&b) end alias to_s to_yaml alias to_str to_yaml end # class Command class AbstractSession # class methods class << self 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 def default_prog= prog @default_prog = prog end attr :track_history, true attr :use_spawn, true attr :use_open3, true attr :debug, true def init @track_history = nil @use_spawn = nil @use_open3 = nil @debug = nil end alias [] new end # class init init # attributes attr :opts attr :prog attr :stdin alias i stdin attr :stdout alias o stdout attr :stderr alias e stderr attr :history attr :track_history attr :outproc, true attr :errproc, true attr :use_spawn attr :use_open3 attr :debug, true alias debug? debug attr :threads # instance methods 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 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 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 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 def __fork(*a, &b) verbose = $VERBOSE begin $VERBOSE = nil Kernel::fork(*a, &b) ensure $VERBOSE = verbose end end # abstract methods def clear raise NotImplementedError end alias flush clear def path raise NotImplementedError end def path= raise NotImplementedError end def send_command cmd raise NotImplementedError end # concrete methods def track_history= bool @history ||= History::new @track_history = bool end 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 def close! [stdin, stdout, stderr].each{|pipe| pipe.close} stdin, stdout, stderr = nil, nil, nil true end alias close close! def hashify(*a) a.inject({}){|o,h| o.update(h)} end private :hashify 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 end # class AbstractSession class Sh < AbstractSession DEFAULT_PROG = 'sh' ECHO = 'echo' attr :status alias exit_status status alias exitstatus status def clear stdin.puts "#{ ECHO } __clear__ 1>&2" stdin.puts "#{ ECHO } __clear__" stdin.flush while((line = stderr.gets) and line !~ %r/__clear__/o); end while((line = stdout.gets) and line !~ %r/__clear__/o); end self end def send_command cmd stdin.printf "%s '%s' 1>&2\n", ECHO, cmd.begin_err stdin.printf "%s '%s' \n", ECHO, cmd.begin_out stdin.printf "%s\n", cmd.cmd stdin.printf "export __exit_status__=$?\n" stdin.printf "%s '%s' 1>&2\n", ECHO, cmd.end_err stdin.printf "%s '%s' \n", ECHO, cmd.end_out stdin.flush end def get_status @status = get_var '__exit_status__' unless @status =~ /^\s*\d+\s*$/o raise ExecutionError, "could not determine exit status from <#{ @status.inspect }>" end @status = Integer @status end def set_var name, value stdin.puts "export #{ name }=#{ value }" stdin.flush end def get_var name stdin.puts "#{ ECHO } \"#{ name }=${#{ name }}\"" stdin.flush var = nil while((line = stdout.gets)) m = %r/#{ name }\s*=\s*(.*)/.match line if m var = m[1] raise ExecutionError, "could not determine <#{ name }> from <#{ line.inspect }>" unless var break end end var end def path var = get_var 'PATH' var.strip.split %r/:/o end def path= arg case arg when Array arg = arg.join ':' else arg = arg.to_s.strip end set_var 'PATH', "'#{ arg }'" self.path end def execute(command, redirects = {}, &block) # setup redirect on stdin rin = redirects[:i] || redirects[:in] || redirects[:stdin] || redirects['stdin'] || redirects['i'] || redirects['in'] || redirects[0] || redirects['0'] if rin tmp = begin Tempfile::new rand.to_s rescue Tempfile::new rand.to_s end begin tmp.write( if rin.respond_to? 'read' rin.read elsif rin.respond_to? 'to_s' rin.to_s else rin end ) tmp.flush command = "{ #{ command } ;} < #{ tmp.path }" #puts command super(command, redirects, &block) ensure tmp.close! if tmp end else super end end end # class Sh class Bash < Sh DEFAULT_PROG = 'bash' class Login < Bash DEFAULT_PROG = 'bash --login' end end # class Bash class Shell < Bash; end # IDL => interactive data language - see http://www.rsinc.com/ class IDL < AbstractSession class LicenseManagerError < StandardError; end DEFAULT_PROG = 'idl' MAX_TRIES = 32 def initialize(*args) tries = 0 ret = nil begin ret = super rescue LicenseManagerError => e tries += 1 if tries < MAX_TRIES sleep 1 retry else raise LicenseManagerError, "<#{ MAX_TRIES }> attempts <#{ e.message }>" end end ret end def clear stdin.puts "retall" stdin.puts "printf, -2, '__clear__'" stdin.puts "printf, -1, '__clear__'" stdin.flush while((line = stderr.gets) and line !~ %r/__clear__/o) raise LicenseManagerError, line if line =~ %r/license\s*manager/io end while((line = stdout.gets) and line !~ %r/__clear__/o) raise LicenseManagerError, line if line =~ %r/license\s*manager/io end self end def send_command cmd stdin.printf "printf, -2, '%s'\n", cmd.begin_err stdin.printf "printf, -1, '%s'\n", cmd.begin_out stdin.printf "%s\n", cmd.cmd stdin.printf "retall\n" stdin.printf "printf, -2, '%s'\n", cmd.end_err stdin.printf "printf, -1, '%s'\n", cmd.end_out stdin.flush end def path stdout, stderr = execute "print, !path" stdout.strip.split %r/:/o end def path= arg case arg when Array arg = arg.join ':' else arg = arg.to_s.strip end stdout, stderr = execute "!path='#{ arg }'" self.path end end # class IDL module Spawn class << self def spawn command ipath = tmpfifo opath = tmpfifo epath = tmpfifo cmd = "#{ command } < #{ ipath } 1> #{ opath } 2> #{ epath } &" system cmd i = open ipath, 'w' o = open opath, 'r' e = open epath, 'r' [i,o,e] end def tmpfifo path = nil 42.times do |i| tpath = File::join(Dir::tmpdir, "#{ $$ }.#{ rand }.#{ i }") v = $VERBOSE begin $VERBOSE = nil system "mkfifo #{ tpath }" ensure $VERBOSE = v end next unless $? == 0 path = tpath at_exit{ File::unlink(path) rescue STDERR.puts("rm <#{ path }> failed") } break end raise "could not generate tmpfifo" unless path path end end end # module Spawn end # module Session session-3.1.0/sample/0000755000175000017500000000000011567734040012602 5ustar plbplbsession-3.1.0/sample/sh0.rb0000644000175000017500000000102511567734040013617 0ustar plbplb#!/usr/bin/env ruby require 'tempfile' $:.unshift '.', '..', 'lib', File.join('..','lib') require 'session' shell = Session::Shell.new :history => false shell.execute('ls -ltar') do |out, err| if out puts "OUT:\n#{ out }" elsif err puts "ERR:\n#{ err }" end end puts shell.history shell.outproc = lambda{|out| puts "OUT:\n#{ out }"} shell.errproc = lambda{|err| puts "ERR:\n#{ err }"} #shell.execute('ls -1') #shell.execute('ls no-exit') shell.execute('while test 1; do echo `date` && ls no-exist; sleep 1; done') session-3.1.0/sample/session_sh.rb0000644000175000017500000000113711567734040015306 0ustar plbplb#!/usr/bin/env ruby require 'tempfile' require 'readline' include Readline $:.unshift '.', '..', 'lib', File.join('..','lib') require 'session' shell = Session::Shell.new require 'tempfile' require 'readline' include Readline n = 0 loop do command = readline("#{ n }: SHELL > ", true) if command =~ /^\s*\!*history\!*\s*$/ open('shell.history','w'){|f| f.puts shell.history} next end exit if command =~ /^\s*(?:exit|quit)\s*$/io out, err = shell.execute command out ||= '' err ||= '' printf "STDOUT:\n%s\nSTDERR:\n%s\n", out.gsub(%r/^/,"\t"), err.gsub(%r/^/,"\t") n += 1 end session-3.1.0/sample/threadtest.rb0000644000175000017500000000200411567734040015272 0ustar plbplb# usage: # ruby -I lib -r session test/threadtest.rb # SESSION_USE_SPAWN=1 ruby -I lib -r session test/threadtest.rb %w(lib ../lib . ..).each{|d| $:.unshift d} require 'session' def display obj Thread.critical = true STDOUT.print obj STDOUT.flush ensure Thread.critical = false end $VERBOSE=nil STDOUT.sync = true STDERR.sync = true STDOUT.puts "Session::VERSION <#{ Session::VERSION }>" STDOUT.puts "Session.use_spawn <#{ Session.use_spawn ? 'true' : 'false' }>" STDOUT.puts "the timestamps of each tid should come back about 1 second apart or there are problems..." STDOUT.puts threads = [] 3.times do |i| threads << Thread::new(i) do |i| cmd = 'echo 42; sleep 1;' * 3 sh = Session.new #:use_spawn=>true sh.execute(cmd) do |o,e| which = o ? 'stdout' : 'stderr' line = (o || e).strip indent = '| ' * i display "#{ indent }tid<#{ i }> #{ which }<#{ line }> time<#{ Time.now.to_f }>\n" end end sleep rand end threads.map{|t| t.join} session-3.1.0/sample/stdin.rb0000644000175000017500000000052211567734040014247 0ustar plbplb#!/usr/bin/env ruby $:.unshift '.', '..', 'lib', File.join('..','lib') require 'session' sh = Session::new stdout, stderr = sh.execute('cat', :stdin => open('/etc/passwd')) stdout.each{|line| print line} stdin, stdout, stderr = "42\n", '', '' sh.execute('cat', 0 => stdin, 1 => stdout, 2 => stderr) stdout.each{|line| print line} session-3.1.0/sample/bash.rb.out0000644000175000017500000000123311567734040014651 0ustar plbplb======== ======== ======== ======== #1 ======== ======== ======== ======== STDOUT: README install.rb lib sample STDERR: STATUS: 0 ======== ======== ======== ======== ======== ======== ======== ======== #2 ======== ======== ======== ======== STDOUT: README STDERR: STDOUT: install.rb STDERR: STDOUT: lib STDERR: STDOUT: sample STDERR: STATUS: 0 ======== ======== ======== ======== ======== ======== ======== ======== #3 ======== ======== ======== ======== STDOUT: README install.rb lib sample STDERR: STATUS: 0 ======== ======== ======== ======== ======== ======== ======== ======== #4 ======== ======== ======== ======== 42 42 42 42 42 42 42 42 ... (repeats forever) session-3.1.0/sample/bash.rb0000755000175000017500000000240111567734040014044 0ustar plbplb#!/usr/bin/env ruby $:.unshift '.', '..', 'lib', File.join('..','lib') require 'session' bash = Session::Bash.new puts "======== ======== ======== ========" puts "#1" puts "======== ======== ======== ========" stdout, stderr = bash.execute 'ls' puts "STDOUT:\n#{ stdout }" puts "STDERR:\n#{ stderr }" puts "STATUS: #{ bash.status }" puts "======== ======== ======== ========" puts "======== ======== ======== ========" puts "#2" puts "======== ======== ======== ========" bash.execute 'ls' do |stdout, stderr| puts "STDOUT:\n#{ stdout }" puts "STDERR:\n#{ stderr }" end puts "STATUS: #{ bash.status }" puts "======== ======== ======== ========" puts "======== ======== ======== ========" puts "#3" puts "======== ======== ======== ========" stdout, stderr = '', '' bash.execute 'ls', :stdout => stdout, :stderr => stderr puts "STDOUT:\n#{ stdout }" puts "STDERR:\n#{ stderr }" puts "STATUS: #{ bash.status }" puts "======== ======== ======== ========" puts "======== ======== ======== ========" puts "#4" puts "======== ======== ======== ========" bash.outproc = lambda{|out| puts "#{ out }"} bash.errproc = lambda{|err| raise err} bash.execute('while test 1; do echo 42; sleep 1; done') # => 42 ... 42 ... 42 puts "======== ======== ======== ========" session-3.1.0/sample/session_idl.rb0000644000175000017500000000103411567734040015440 0ustar plbplb#!/usr/bin/env ruby require 'tempfile' require 'readline' include Readline $:.unshift '.', '..', 'lib', File.join('..','lib') require 'session' idl = Session::IDL.new n = 0 loop do command = readline("#{ n }: IDL_WRAP > ", true) if command =~ /^\s*\!*history\!*\s*$/ open('idl_wrap.history','w'){|f| f.puts idl.history} next end exit if command =~ /^\s*exit\s*$/io out, err = idl.execute command out ||= '' err ||= '' printf "STDOUT:\n%s\nSTDERR:\n%s\n", out.gsub(%r/^/,"\t"), err.gsub(%r/^/,"\t") n += 1 end session-3.1.0/sample/driver.rb0000644000175000017500000000153311567734040014424 0ustar plbplb#!/usr/bin/env ruby require 'tempfile' require 'logger' $:.unshift '.', '..', 'lib', File.join('..','lib') require 'session' DIV = ('=' * 79) << "\n" # start session with bash bash = Session::Bash.new # create two tempory external programs to drive prog_a = Tempfile.new('prog_a_') prog_a.write <<-code puts $0 puts 42 code prog_a.close prog_a = prog_a.path prog_b = Tempfile.new('prog_b_') prog_b.write <<-code puts $0 puts 'forty-two' code prog_b.close prog_b = prog_b.path # set up logging logger = Logger.new STDOUT # run both programs redirect the stdout into the log logger.info{ 'running program a' } logger << DIV bash.execute "ruby #{ prog_a}", :stdout => logger, :stderr => STDERR logger << DIV logger.info{ 'running program b' } logger << DIV bash.execute "ruby #{ prog_b}", :stdout => logger, :stderr => STDERR logger << DIV session-3.1.0/Rakefile0000644000175000017500000001345311567734040012774 0ustar plbplb This.rubyforge_project = 'codeforpeople' This.author = "Ara T. Howard" This.email = "ara.t.howard@gmail.com" This.homepage = "http://github.com/ahoward/#{ This.lib }/tree/master" task :default do puts(Rake::Task.tasks.map{|task| task.name} - ['default']) end task :gemspec do ignore_extensions = 'git', 'svn', 'tmp', /sw./, 'bak', 'gem' ignore_directories = 'pkg' ignore_files = 'test/log' shiteless = lambda do |list| list.delete_if do |entry| next unless test(?e, entry) extension = File.basename(entry).split(%r/[.]/).last ignore_extensions.any?{|ext| ext === extension} end list.delete_if do |entry| next unless test(?d, entry) dirname = File.expand_path(entry) ignore_directories.any?{|dir| File.expand_path(dir) == dirname} end list.delete_if do |entry| next unless test(?f, entry) filename = File.expand_path(entry) ignore_files.any?{|file| File.expand_path(file) == filename} end end lib = This.lib object = This.object version = This.version files = shiteless[Dir::glob("**/**")] executables = shiteless[Dir::glob("bin/*")].map{|exe| File.basename(exe)} has_rdoc = true #File.exist?('doc') test_files = "test/#{ lib }.rb" if File.file?("test/#{ lib }.rb") summary = object.respond_to?(:summary) ? object.summary : "summary: #{ lib } kicks the ass" description = object.respond_to?(:description) ? object.description : "description: #{ lib } kicks the ass" extensions = This.extensions if extensions.nil? %w( Makefile configure extconf.rb ).each do |ext| extensions << ext if File.exists?(ext) end end extensions = [extensions].flatten.compact template = if test(?e, 'gemspec.erb') Template{ IO.read('gemspec.erb') } else Template { <<-__ ## #{ lib }.gemspec # Gem::Specification::new do |spec| spec.name = #{ lib.inspect } spec.version = #{ version.inspect } spec.platform = Gem::Platform::RUBY spec.summary = #{ lib.inspect } spec.description = #{ description.inspect } spec.files = #{ files.inspect } spec.executables = #{ executables.inspect } spec.require_path = "lib" spec.has_rdoc = #{ has_rdoc.inspect } spec.test_files = #{ test_files.inspect } #spec.add_dependency 'lib', '>= version' spec.add_dependency 'fattr' spec.extensions.push(*#{ extensions.inspect }) spec.rubyforge_project = #{ This.rubyforge_project.inspect } spec.author = #{ This.author.inspect } spec.email = #{ This.email.inspect } spec.homepage = #{ This.homepage.inspect } end __ } end open("#{ lib }.gemspec", "w"){|fd| fd.puts template} This.gemspec = "#{ lib }.gemspec" end task :gem => [:clean, :gemspec] do Fu.mkdir_p This.pkgdir before = Dir['*.gem'] cmd = "gem build #{ This.gemspec }" `#{ cmd }` after = Dir['*.gem'] gem = ((after - before).first || after.first) or abort('no gem!') Fu.mv gem, This.pkgdir This.gem = File.basename(gem) end task :readme do samples = '' prompt = '~ > ' lib = This.lib version = This.version Dir['sample*/*'].sort.each do |sample| samples << "\n" << " <========< #{ sample } >========>" << "\n\n" cmd = "cat #{ sample }" samples << Util.indent(prompt + cmd, 2) << "\n\n" samples << Util.indent(`#{ cmd }`, 4) << "\n" cmd = "ruby #{ sample }" samples << Util.indent(prompt + cmd, 2) << "\n\n" cmd = "ruby -e'STDOUT.sync=true; exec %(ruby -I ./lib #{ sample })'" samples << Util.indent(`#{ cmd } 2>&1`, 4) << "\n" end template = if test(?e, 'readme.erb') Template{ IO.read('readme.erb') } else Template { <<-__ NAME #{ lib } DESCRIPTION INSTALL gem install #{ lib } SAMPLES #{ samples } __ } end open("README", "w"){|fd| fd.puts template} end task :clean do Dir[File.join(This.pkgdir, '**/**')].each{|entry| Fu.rm_rf(entry)} end task :release => [:clean, :gemspec, :gem] do gems = Dir[File.join(This.pkgdir, '*.gem')].flatten raise "which one? : #{ gems.inspect }" if gems.size > 1 raise "no gems?" if gems.size < 1 cmd = "rubyforge login && rubyforge add_release #{ This.rubyforge_project } #{ This.lib } #{ This.version } #{ This.pkgdir }/#{ This.gem }" puts cmd system cmd end BEGIN { $VERBOSE = nil require 'ostruct' require 'erb' require 'fileutils' Fu = FileUtils This = OpenStruct.new This.file = File.expand_path(__FILE__) This.dir = File.dirname(This.file) This.pkgdir = File.join(This.dir, 'pkg') lib = ENV['LIB'] unless lib lib = File.basename(Dir.pwd) end This.lib = lib version = ENV['VERSION'] unless version require "./lib/#{ This.lib }" This.name = lib.capitalize This.object = eval(This.name) version = This.object.send(:version) end This.version = version abort('no lib') unless This.lib abort('no version') unless This.version module Util def indent(s, n = 2) s = unindent(s) ws = ' ' * n s.gsub(%r/^/, ws) end def unindent(s) indent = nil s.each do |line| next if line =~ %r/^\s*$/ indent = line[%r/^\s*/] and break end indent ? s.gsub(%r/^#{ indent }/, "") : s end extend self end class Template def initialize(&block) @block = block @template = block.call.to_s end def expand(b=nil) ERB.new(Util.unindent(@template)).result(b||@block) end alias_method 'to_s', 'expand' end def Template(*args, &block) Template.new(*args, &block) end Dir.chdir(This.dir) } session-3.1.0/session.gemspec0000644000175000017500000000166311567734040014357 0ustar plbplb## session.gemspec # Gem::Specification::new do |spec| spec.name = "session" spec.version = "3.1.0" spec.platform = Gem::Platform::RUBY spec.summary = "session" spec.description = "description: session kicks the ass" spec.files = ["gemspec.rb", "lib", "lib/session.rb", "LICENSE", "Rakefile", "README", "sample", "sample/bash.rb", "sample/bash.rb.out", "sample/driver.rb", "sample/session_idl.rb", "sample/session_sh.rb", "sample/sh0.rb", "sample/stdin.rb", "sample/threadtest.rb", "session.gemspec", "test", "test/session.rb"] spec.executables = [] spec.require_path = "lib" spec.has_rdoc = true spec.test_files = "test/session.rb" #spec.add_dependency 'lib', '>= version' spec.add_dependency 'fattr' spec.extensions.push(*[]) spec.rubyforge_project = "codeforpeople" spec.author = "Ara T. Howard" spec.email = "ara.t.howard@gmail.com" spec.homepage = "http://github.com/ahoward/session/tree/master" end session-3.1.0/metadata.yml0000644000175000017500000000274211567734040013631 0ustar plbplb--- !ruby/object:Gem::Specification name: session version: !ruby/object:Gem::Version version: 3.1.0 platform: ruby authors: - Ara T. Howard autorequire: bindir: bin cert_chain: [] date: 2010-05-20 00:00:00 -06:00 default_executable: dependencies: - !ruby/object:Gem::Dependency name: fattr type: :runtime version_requirement: version_requirements: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: "0" version: description: "description: session kicks the ass" email: ara.t.howard@gmail.com executables: [] extensions: [] extra_rdoc_files: [] files: - gemspec.rb - lib/session.rb - LICENSE - Rakefile - README - sample/bash.rb - sample/bash.rb.out - sample/driver.rb - sample/session_idl.rb - sample/session_sh.rb - sample/sh0.rb - sample/stdin.rb - sample/threadtest.rb - session.gemspec - test/session.rb has_rdoc: true homepage: http://github.com/ahoward/session/tree/master licenses: [] post_install_message: rdoc_options: [] require_paths: - lib required_ruby_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: "0" version: required_rubygems_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: "0" version: requirements: [] rubyforge_project: codeforpeople rubygems_version: 1.3.5 signing_key: specification_version: 3 summary: session test_files: - test/session.rb