class Chump
Attributes
slow[RW]
Public Class Methods
connect(url, opts={})
click to toggle source
# File lib/chump.rb, line 40 def self.connect(url, opts={}) scheme, _, target, *info = url.split('/') # scheme://target/info target =~ /^(?:(\w+)(?::([^@]+))?@)?(?:([^:]+)?(?::(\d+))?)$/ user, pass, host, port = $1,$2,$3,$4 # user:pass@host:port opts[:url ] = "#{scheme}//" << [user, host].compact.join('@') opts[:url ] << ":#{port}" if host && port opts[:url ] << ['', *info].compact.join('/') opts[:info] = info unless info.empty? case scheme when 'spawn:' then spawn(target, opts) when 'ssh:' then ssh(host, port, user, pass, opts) when 'tcp:' then tcp(host, port, user, pass, opts) else abort "can't parse #{url.inspect}" end end
new(io, opts={})
click to toggle source
# File lib/chump.rb, line 145 def initialize(io, opts={}) opts.empty? or opts.each {|k,v| opts[k.to_sym] ||= v if k.is_a?(String)} @live = opts.has_key?(:live) ? opts[:live] : true # live reads @nocr = opts.has_key?(:nocr) ? opts[:nocr] : true # strip "\r" @ansi = opts.has_key?(:ansi) ? opts[:ansi] : false # allow ANSI escapes => for GT.M, but checkout "U $P:(NOECHO)" @show = opts.has_key?(:show) ? opts[:show] : false # show matches @echo = opts.has_key?(:echo) ? opts[:echo] : false # echo sends @wait = opts.has_key?(:wait) ? opts[:wait] : nil # sleep times @bomb = opts.has_key?(:bomb) ? opts[:bomb] : true # bomb on slow timeout @slow = opts.has_key?(:slow) ? opts[:slow] : 10 # slow timeout @fast = opts.has_key?(:fast) ? opts[:fast] : 0.25 # fast timeout @size = opts.has_key?(:size) ? opts[:size] : 1 << 16 # buffer size @line = opts.has_key?(:line) ? opts[:line] : "\r" # line terminator @buff = '' @start = Time.now @sleep = 0.0 @final = @start @io = io.is_a?(String) ? self.class.connect(io,opts.update(:io=>true)) : io @io.sync = true chat(opts.delete(:auth)) if opts[:auth] # authenticate if requested chat(opts.delete(:init)) if opts[:init] # initialize if requested end
spawn(cmd=nil, opts={})
click to toggle source
# File lib/chump.rb, line 58 def self.spawn(cmd=nil, opts={}) io, child_io = IO.socketpair if UNIX require 'pty' cmd = ENV['SHELL'].dup if !cmd || cmd.empty? cmd << '; cat -' # hack to keep program running, anything better? reader, writer, pid = PTY.spawn(cmd); reader.sync = writer.sync = true Thread.new { child_io.syswrite(reader.sysread(1 << 16)) while true } Thread.new { writer.syswrite(child_io.sysread(1 << 16)) while true } at_exit do Process.kill(9, pid) end else require 'win32/process' child = Process.create( 'app_name' => "cmd /k #{cmd}", 'process_inherit' => true, #!# is this needed? 'thread_inherit' => true, #!# is this needed? 'startup_info' => { 'stdin' => child_io, 'stdout' => child_io, 'stderr' => File.open('nul', 'wb') # ignore STDERR (what about sync? close?) } ) at_exit do Process.TerminateProcess(child.process_handle, child.process_id) Process.CloseHandle(child.process_handle) end child_io.close end opts[:io] ? io : new(io, opts) end
ssh(host, port=nil, user=nil, pass=nil, cmd=nil, opts={})
click to toggle source
# File lib/chump.rb, line 93 def self.ssh(host, port=nil, user=nil, pass=nil, cmd=nil, opts={}) io, child_io = IO.socketpair require 'net/ssh' ENV['HOME'] ||= ENV['USERPROFILE'] unless UNIX options = {}; options[:pass] = pass if pass; options[:port] = port if port ssh = Net::SSH.start(host||'localhost', user||ENV['USER']||ENV['USERNAME'], options) ssh.open_channel do |channel| channel.request_pty do |ch, success| raise "can't get pty" unless success end channel.send_channel_request "shell" do |ch, success| raise "can't start shell" unless success ch.send_data "#{cmd}\r" if cmd && !cmd.empty? Thread.new do loop do if select([child_io], nil, nil, 0.25) data = child_io.sysread(1 << 16) or raise "can't read from child_io" ch.send_data(data) ssh.process end end end end channel.on_data do |ch, data| child_io.syswrite(data) end end Thread.new { ssh.loop } opts[:io] ? io : new(io, opts) end
tcp(host, port, user=nil, pass=nil, opts={})
click to toggle source
# File lib/chump.rb, line 126 def self.tcp(host, port, user=nil, pass=nil, opts={}) user,opts = nil,user if user.is_a?(Hash) && pass==nil && opts.empty? # allow short calls host ||= "127.0.0.1" port ||= 23 pass ||= "" if user io = TCPSocket.new(host, port.to_i) opts[:auth] ||= { # /\xFF\xFD(.)/ => proc { [:pure, "\xFF\xFC#{$1}" ] }, # reject these (do -> wont) # /\xFF\xFB(.)/ => proc { [:pure, "\xFF\xFE#{$1}" ] }, # accept these (will -> dont) /Log ?in|User ?name/i => [user], /Pass ?word/i => pass, :else => nil, } if user && pass opts[:io] ? io : new(io, opts) end
Public Instance Methods
chat(*list) { |key, back| ... }
click to toggle source
# File lib/chump.rb, line 171 def chat(*list) return self if list.empty? item = nil back = nil talk = false fast = false list.each do |item| loop do case item when false, Symbol # notifier back = item case back when :redo then break else return back end break when true, nil # continuer back = item talk = !talk if item.nil? break when String, Fixnum, Float # [literal] item = item.to_s if talk # talker send(item) back = item talk = false break elsif index = @buff.index(item) # comparer @last = item # save for future reference back = @buff.slice!(0..(index + item.size - 1)) print back.tr("\r",'') if @show talk = true break end when Regexp # matcher if match = @buff.match(item) @last = match[1] || match[0] # save for future reference @buff = match.post_match back = [match.pre_match + match.to_s, *match.to_a[1..-1]] print back.first.tr("\r",'') if @show talk = true break else talk = false end when Hash # multiplexer item.each do |key, val| key, val = '', item[:else] if fast case key when :else # insurer next when Symbol # yielder case val when String, Fixnum, Float # comparer val = val.to_s if index = @buff.index(val) back = @buff.slice!(0..(index + val.size - 1)) print back.tr("\r",'') if @show back = yield(key, back) if block_given? break end when Regexp # matcher if match = @buff.match(val) @buff = match.post_match back = [match.pre_match + match.to_s, *match.to_a[1..-1]] print back.first.tr("\r",'') if @show back = yield(key, back) if block_given? break end when Array, Proc, Hash # indexer # processed elsewhere else raise "Hash symbols don't support #{val.class} matchers" end when String, Fixnum, Float, Regexp # comparer/matcher (ugly, but shares actions) key = key.to_s unless regx = key.is_a?(Regexp) if fast back = :else elsif !regx && index = @buff.index(key) back = @buff.slice!(0..(index + key.size - 1)) print back.tr("\r",'') if @show elsif regx && match = @buff.match(key) @buff = match.post_match back = [match.pre_match + match.to_s, *match.to_a[1..-1]] print back.first.tr("\r",'') if @show else regx = nil end unless regx.nil? case val when String, Fixnum, Float send(val.to_s) when Array back = chat(nil, *val) unless val.empty? back = :redo if val.size <= 1 when Proc eval("proc {|m| $~ = m}", val.binding).call($~) if $~ # infuse proc with our match variables back = back.is_a?(String) ? val.call(back) : val.call(*back) # don't convert embedded newlines to array case val = back when Array if pure = (val.first == :pure) line, @line = @line, "" back = chat(nil, *val[1..-1]) unless val.size == 1 @line = line back = :redo if val.size <= 2 else back = chat(nil, *val) unless val.empty? back = :redo if val.size <= 1 end end when false, Symbol if val == :this back = back.first if back.is_a?(Array) # regexps store leading + matched text in back.first else back = val end when true, nil back = val when Hash back = chat(val) else raise "Hash literals can't multiplex to #{val.class} types" end break end else raise "Hash items can't process #{key}.class keys" end end and begin # read when nothing matches fast = read(item.has_key?(:else)) == :fast next end fast &&= false talk = false case back when :else then break when :redo then redo when :skip then return :skip when false, Symbol then return back end break when Array # walker if item.first == :pure ansi, @ansi = @ansi, :false line, @line = @line, "" back = talk ? chat(nil, *item[1..-1]) : chat(*item[1..-1]) @ansi = ansi @line = line else back = talk ? chat(nil, *item) : chat(*item) end talk = false break when Proc, Method # macro item = item.to_proc if item.class == Method eval("proc {|m| $~ = m}", item.binding).call($~) if $~ # infuse proc with our match variables back = back.is_a?(String) ? item.call(back) : item.call(*back) # don't convert embedded newlines to array item = back unless back == :redo redo else # aborter raise "Chump doesn't handle #{item.class} objects like: #{item.inspect}" end read unless talk end case back when :redo then break when :skip then break when :false then return false # same as false in parent when :true then return true # same as true in parent when :nil then return nil # same as nil in parent end end back rescue Object => e exit if defined?(PTY::ChildExited) and e.class == PTY::ChildExited warn ['', '', "==[ #{e} ]==" ] * "\n" warn ['', e.backtrace, ''].flatten * "\n" warn ['', "Buffer: ", @buff.inspect] * "\n" warn ['', "Failed: ", item.inspect ] * "\n" if item disconnect exit end
disconnect()
click to toggle source
# File lib/chump.rb, line 400 def disconnect @stop = Time.now print @buff.tr("\r",'') if @show @io.close puts end
peek(*list)
click to toggle source
# File lib/chump.rb, line 396 def peek(*list) list.compact.inject(:else=>false) {|h,v| h[v]=:this; h} end
read(fast=false)
click to toggle source
# File lib/chump.rb, line 357 def read(fast=false) unless select([@io], nil, nil, fast ? @fast : @slow) return :fast if fast raise "Timeout" if @bomb return :slow end buff = @io.sysread(@size) buff.tr!("\r",'') if @nocr unless @ansi # http://www.esrl.noaa.gov/gmd/dv/hats/cats/stations/qnxman/Devansi.html # http://support.dell.com/support/edocs/systems/SC1425/en/ug/f3593ab0.htm buff.gsub!(/\x08/,'') buff.gsub!(/\e[=>]/,'') buff.gsub!(/\e\[(?>[^a-z]*)[a-z]/i,'') end print @nocr ? buff : buff.tr("\r",'') if @live @buff << buff end
send(item='', *list)
click to toggle source
# File lib/chump.rb, line 380 def send(item='', *list) if back = item select(nil, [@io], nil, @slow) or return :slow if @wait prior = Time.now.to_f sleep(@wait[0] + rand * (@wait[1] - @wait[0])) @sleep += Time.now.to_f - prior end back = back.to_s @io.syswrite(back + @line) # line ending, usually "\r" print back.tr("\r",'') if @echo end back = chat(*list) unless list.empty? back end
unshift(str)
click to toggle source
# File lib/chump.rb, line 376 def unshift(str) Thread.exclusive { @buff = str + @buff } end