session-3.2.0/0000755000004100000410000000000012325251213013223 5ustar www-datawww-datasession-3.2.0/Rakefile0000644000004100000410000002366412325251213014703 0ustar www-datawww-dataThis.rubyforge_project = 'codeforpeople' This.author = "Ara T. Howard" This.email = "ara.t.howard@gmail.com" This.homepage = "https://github.com/ahoward/#{ This.lib }" task :license do open('LICENSE', 'w'){|fd| fd.puts "same as ruby's"} end task :default do puts((Rake::Task.tasks.map{|task| task.name.gsub(/::/,':')} - ['default']).sort) end task :test do run_tests! end namespace :test do task(:unit){ run_tests!(:unit) } task(:functional){ run_tests!(:functional) } task(:integration){ run_tests!(:integration) } end def run_tests!(which = nil) which ||= '**' test_dir = File.join(This.dir, "test") test_glob ||= File.join(test_dir, "#{ which }/**_test.rb") test_rbs = Dir.glob(test_glob).sort div = ('=' * 119) line = ('-' * 119) test_rbs.each_with_index do |test_rb, index| testno = index + 1 command = "#{ This.ruby } -w -I ./lib -I ./test/lib #{ test_rb }" puts say(div, :color => :cyan, :bold => true) say("@#{ testno } => ", :bold => true, :method => :print) say(command, :color => :cyan, :bold => true) say(line, :color => :cyan, :bold => true) system(command) say(line, :color => :cyan, :bold => true) status = $?.exitstatus if status.zero? say("@#{ testno } <= ", :bold => true, :color => :white, :method => :print) say("SUCCESS", :color => :green, :bold => true) else say("@#{ testno } <= ", :bold => true, :color => :white, :method => :print) say("FAILURE", :color => :red, :bold => true) end say(line, :color => :cyan, :bold => true) exit(status) unless status.zero? end 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" license = object.respond_to?(:license) ? object.license : "same as ruby's" if This.extensions.nil? This.extensions = [] extensions = This.extensions %w( Makefile configure extconf.rb ).each do |ext| extensions << ext if File.exists?(ext) end end extensions = [extensions].flatten.compact if This.dependencies.nil? dependencies = [] else case This.dependencies when Hash dependencies = This.dependencies.values when Array dependencies = This.dependencies end end 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.license = <%= license.inspect %> spec.files =\n<%= files.sort.pretty_inspect %> spec.executables = <%= executables.inspect %> spec.require_path = "lib" spec.test_files = <%= test_files.inspect %> <% dependencies.each do |lib_version| %> spec.add_dependency(*<%= Array(lib_version).flatten.inspect %>) <% end %> 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 Fu.mkdir_p(This.pkgdir) gemspec = "#{ lib }.gemspec" open(gemspec, "w"){|fd| fd.puts(template)} This.gemspec = 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.join(This.pkgdir, 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 = "gem push #{ This.gem }" puts cmd puts system(cmd) abort("cmd(#{ cmd }) failed with (#{ $?.inspect })") unless $?.exitstatus.zero? cmd = "rubyforge login && rubyforge add_release #{ This.rubyforge_project } #{ This.lib } #{ This.version } #{ This.gem }" puts cmd puts system(cmd) abort("cmd(#{ cmd }) failed with (#{ $?.inspect })") unless $?.exitstatus.zero? end BEGIN { # support for this rakefile # $VERBOSE = nil require 'ostruct' require 'erb' require 'fileutils' require 'rbconfig' require 'pp' # fu shortcut # Fu = FileUtils # cache a bunch of stuff about this rakefile/environment # This = OpenStruct.new This.file = File.expand_path(__FILE__) This.dir = File.dirname(This.file) This.pkgdir = File.join(This.dir, 'pkg') # grok lib # lib = ENV['LIB'] unless lib lib = File.basename(Dir.pwd).sub(/[-].*$/, '') end This.lib = lib # grok version # 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 # see if dependencies are export by the module # if This.object.respond_to?(:dependencies) This.dependencies = This.object.dependencies end # we need to know the name of the lib an it's version # abort('no lib') unless This.lib abort('no version') unless This.version # discover full path to this ruby executable # c = Config::CONFIG bindir = c["bindir"] || c['BINDIR'] ruby_install_name = c['ruby_install_name'] || c['RUBY_INSTALL_NAME'] || 'ruby' ruby_ext = c['EXEEXT'] || '' ruby = File.join(bindir, (ruby_install_name + ruby_ext)) This.ruby = ruby # some utils # module Util def indent(s, n = 2) s = unindent(s) ws = ' ' * n s.gsub(%r/^/, ws) end def unindent(s) indent = nil s.each_line do |line| next if line =~ %r/^\s*$/ indent = line[%r/^\s*/] and break end indent ? s.gsub(%r/^#{ indent }/, "") : s end extend self end # template support # 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).binding) end alias_method 'to_s', 'expand' end def Template(*args, &block) Template.new(*args, &block) end # colored console output support # This.ansi = { :clear => "\e[0m", :reset => "\e[0m", :erase_line => "\e[K", :erase_char => "\e[P", :bold => "\e[1m", :dark => "\e[2m", :underline => "\e[4m", :underscore => "\e[4m", :blink => "\e[5m", :reverse => "\e[7m", :concealed => "\e[8m", :black => "\e[30m", :red => "\e[31m", :green => "\e[32m", :yellow => "\e[33m", :blue => "\e[34m", :magenta => "\e[35m", :cyan => "\e[36m", :white => "\e[37m", :on_black => "\e[40m", :on_red => "\e[41m", :on_green => "\e[42m", :on_yellow => "\e[43m", :on_blue => "\e[44m", :on_magenta => "\e[45m", :on_cyan => "\e[46m", :on_white => "\e[47m" } def say(phrase, *args) options = args.last.is_a?(Hash) ? args.pop : {} options[:color] = args.shift.to_s.to_sym unless args.empty? keys = options.keys keys.each{|key| options[key.to_s.to_sym] = options.delete(key)} color = options[:color] bold = options.has_key?(:bold) parts = [phrase] parts.unshift(This.ansi[color]) if color parts.unshift(This.ansi[:bold]) if bold parts.push(This.ansi[:clear]) if parts.size > 1 method = options[:method] || :puts Kernel.send(method, parts.join) end # always run out of the project dir # Dir.chdir(This.dir) } session-3.2.0/README0000644000004100000410000001310112325251213014077 0ustar www-datawww-dataURLS: | 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.2.0/session.gemspec0000644000004100000410000000162512325251213016257 0ustar www-datawww-data## session.gemspec # Gem::Specification::new do |spec| spec.name = "session" spec.version = "3.2.0" spec.platform = Gem::Platform::RUBY spec.summary = "session" spec.description = "persistent connections with external programs like bash" spec.license = "same as ruby's" spec.files = ["LICENSE", "README", "Rakefile", "gemspec.rb", "lib", "lib/session.rb", "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.test_files = "test/session.rb" spec.extensions.push(*[]) spec.rubyforge_project = "codeforpeople" spec.author = "Ara T. Howard" spec.email = "ara.t.howard@gmail.com" spec.homepage = "https://github.com/ahoward/session" end session-3.2.0/lib/0000755000004100000410000000000012325251213013771 5ustar www-datawww-datasession-3.2.0/lib/session.rb0000755000004100000410000004156612325251213016020 0ustar www-datawww-datarequire 'open3' require 'tmpdir' require 'thread' require 'yaml' require 'tempfile' module Session VERSION = '3.2.0' def self.version() VERSION end def Session.description 'persistent connections with external programs like bash' 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 # In case their are weird chars, this will avoid a "invalid byte sequence in US-ASCII" error line.force_encoding("binary") if line.respond_to? :force_encoding 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.2.0/metadata.yml0000644000004100000410000000231512325251213015527 0ustar www-datawww-data--- !ruby/object:Gem::Specification name: session version: !ruby/object:Gem::Version version: 3.2.0 platform: ruby authors: - Ara T. Howard autorequire: bindir: bin cert_chain: [] date: 2014-04-07 00:00:00.000000000 Z dependencies: [] description: persistent connections with external programs like bash email: ara.t.howard@gmail.com executables: [] extensions: [] extra_rdoc_files: [] files: - LICENSE - README - Rakefile - gemspec.rb - lib/session.rb - 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 homepage: https://github.com/ahoward/session licenses: - same as ruby's metadata: {} post_install_message: rdoc_options: [] require_paths: - lib required_ruby_version: !ruby/object:Gem::Requirement requirements: - - ! '>=' - !ruby/object:Gem::Version version: '0' required_rubygems_version: !ruby/object:Gem::Requirement requirements: - - ! '>=' - !ruby/object:Gem::Version version: '0' requirements: [] rubyforge_project: codeforpeople rubygems_version: 2.0.3 signing_key: specification_version: 4 summary: session test_files: - test/session.rb session-3.2.0/test/0000755000004100000410000000000012325251213014202 5ustar www-datawww-datasession-3.2.0/test/session.rb0000644000004100000410000001406612325251213016221 0ustar www-datawww-data %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.2.0/LICENSE0000644000004100000410000000007012325251213014225 0ustar www-datawww-datasame as Ruby's http://www.ruby-lang.org/en/LICENSE.txt session-3.2.0/sample/0000755000004100000410000000000012325251213014504 5ustar www-datawww-datasession-3.2.0/sample/driver.rb0000644000004100000410000000153312325251213016326 0ustar www-datawww-data#!/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.2.0/sample/threadtest.rb0000644000004100000410000000200412325251213017174 0ustar www-datawww-data# 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.2.0/sample/bash.rb.out0000644000004100000410000000123312325251213016553 0ustar www-datawww-data======== ======== ======== ======== #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.2.0/sample/sh0.rb0000644000004100000410000000102512325251213015521 0ustar www-datawww-data#!/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.2.0/sample/stdin.rb0000644000004100000410000000052212325251213016151 0ustar www-datawww-data#!/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.2.0/sample/bash.rb0000755000004100000410000000240112325251213015746 0ustar www-datawww-data#!/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.2.0/sample/session_sh.rb0000644000004100000410000000113712325251213017210 0ustar www-datawww-data#!/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.2.0/sample/session_idl.rb0000644000004100000410000000103412325251213017342 0ustar www-datawww-data#!/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.2.0/gemspec.rb0000755000004100000410000000300412325251213015173 0ustar www-datawww-data#! /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.2.0/checksums.yaml.gz0000444000004100000410000000065212325251213016514 0ustar www-datawww-dataBSA0Slٹ,EaF$H37Ė@ln{( Fқno}I\ڿyz$Iqk[>'Me\KY.7:H3So|=}QJ",?$oe.TCE;c#k*S!r fT|;..E{ɖ(Oʤg 8jx`KOAw3Fr{^ցi=+]g|PAe "-Jãط^\a4 *?\yp ʺʙ, 5t0rֱ0zDvF8ST+)E_P!Ss