commit 7ba874036965efa8eb3701001e4b099223c60c25 Author: Denis Knauf Date: Sat Jan 30 01:27:51 2010 +0100 version 0.2.0 imported diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..01c6483 --- /dev/null +++ b/Rakefile @@ -0,0 +1,57 @@ +require 'rubygems' +require 'rake' + +begin + require 'jeweler' + Jeweler::Tasks.new do |gem| + gem.name = "select" + gem.summary = %Q{IO-event-handler based on select} + gem.description = %Q{Select based event-handler for servers and sockets} + gem.email = "Denis.Knauf@gmail.com" + gem.homepage = "http://github.com/DenisKnauf/select" + gem.authors = ["Denis Knauf"] + gem.files = ["README", "VERSION", "lib/**/*.rb", "test/**/*.rb"] + gem.require_paths = ["lib"] + end + Jeweler::GemcutterTasks.new +rescue LoadError + puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler" +end + +require 'rake/testtask' +Rake::TestTask.new(:test) do |test| + test.libs << 'lib' << 'test' << 'ext' + test.pattern = 'test/**/*_test.rb' + test.verbose = true +end + +begin + require 'rcov/rcovtask' + Rcov::RcovTask.new do |test| + test.libs << 'test' + test.pattern = 'test/**/*_test.rb' + test.verbose = true + end +rescue LoadError + task :rcov do + abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov" + end +end + +task :test => :check_dependencies + +task :default => :test + +require 'rake/rdoctask' +Rake::RDocTask.new do |rdoc| + if File.exist?('VERSION') + version = File.read('VERSION') + else + version = "" + end + + rdoc.rdoc_dir = 'rdoc' + rdoc.title = "sbdb #{version}" + rdoc.rdoc_files.include('README*') + rdoc.rdoc_files.include('lib/**/*.rb') +end diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..0ea3a94 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +0.2.0 diff --git a/lib/select.rb b/lib/select.rb new file mode 100644 index 0000000..1af7859 --- /dev/null +++ b/lib/select.rb @@ -0,0 +1,256 @@ +class Select + READ, WRITE, ERROR = 1, 2, 3 + + attr_reader :read, :write, :error + attr_accessor :exit, :exit_on_empty + + def empty? + @read.empty? && @write.empty? && @error.empty? + end + + def self.new *p, &e + r = super *p + if e + e.call r + r.close + else r + end + end + + def initialize timeout = 30 + @read, @write, @error = {}, {}, {} + @read.default = @write.default = @error.default = lambda{} + @timeout, @tevent, @exit = timeout, lambda{}, false + end + + def timeout timeout = nil, &event + return @timeout if timeout.nil? + raise ArgumentError, "Numeric value expected, not: '#{timeout}'" unless timeout.kind_of? Numeric + @timeout = timeout + @tevent = event if event + timeout + end + + def set hook, type = :read, &event + raise ArgumentError, "This hook isn't supported: '#{hook.inspect}'" unless hook.kind_of? IO + raise ArgumentError, "Unexpected Event: '#{event.inspect}'" unless event.kind_of? Proc + case type + when READ, :read then @read[ hook] = event + when WRITE, :write then @write[ hook] = event + when ERROR, :error then @error[ hook] = event + when nil + @read[ hook] = event + @write[ hook] = event + @error[ hook] = event + else raise ArgumentError, "Unknown event-type: '#{type}'" + end + end + + def del hook, type = nil + case type + when READ, :read then @read.delete hook + when WRITE, :write then @write.delete hook + when ERROR, :error then @error.delete hook + when nil + @read.delete hook + @write.delete hook + @error.delete hook + else raise ArgumentError, "Unknown event-type: '#{type}'" + end + end + + def read_set( hook, &event) self.set hook, :read, &event end + def write_set( hook, &event) self.set hook, :write, &event end + def error_set( hook, &event) self.set hook, :error, &event end + def read_del( hook) @read.delete hook end + def write_del( hook) @write.delete hook end + def error_del( hook) @error.delete hook end + + def run_once timeout = @timeout + r, w, e = Kernel.select( @read.keys, @write.keys, @error.keys, timeout) + return @tevent.call unless r or w or e + r.each {|h| @read[ h].call h, :read } + w.each {|h| @write[ h].call h, :write } + e.each {|h| @error[ h].call h, :error } + end + + def run &e + if e + until @exit || (@exit_on_empty && self.empty?) + self.run_once + e.call + end + else + self.run_once until @exit || (@exit_on_empty && self.empty?) + end + end +end + +class Select::Buffer " + end + + def remove x + self.slice! 0...x + end + alias remove! remove + + def each! x + return Enumerator.new( self, :each!, x) unless block_given? + s = nil + yield s while s = self.slice!( x) + self + end + + def + s + self.class.new super(s) + end +end + +class Select::Socket + attr_reader :select, :sock, :bufsize, :parent + + def initialize opts + self.init opts + @select.read_set @sock, &method( :event_read) + @select.error_set @sock, &method( :event_error) + end + + def init opts + @select = opts[ :select] || Select.new + @sock = opts[ :sock] || raise( ArgumentError, "need sock") + @bufsize = opts[ :bufsize] || 4096 + @parent = opts[ :parent] || nil + self.delimiter = opts[ :delimiter] || $/ + @linebuf, @writebuf = Select::Buffer.new(""), Select::Buffer.new("") + end + + def delimiter= delimiter + @delimiter = case delimiter + when Regexp then Regexp.new "^.*?"+delimiter.source + when Fixnum, Bignum then /^.{#{delimiter}}/ + else Regexp.new "^.*?"+Regexp.quote( delimiter.to_s) + end + end + + def write str + e = @writebuf.empty? + @writebuf += str + @select.write_set @sock, &method( :event_write) if e + end + alias :print :write + alias :<< :write + + def puts str + self.write "#{str}\n" + end + + def close + @select.del @sock + @sock.close + @parent.event_client_closed self if @parent.respond_to? :event_client_closed + rescue IOError + end + + def event_line line + end + + def event_read sock = @sock, event = :read + @linebuf += sock.readpartial @bufsize + @linebuf.each! @delimiter, &self.method( :event_line) + rescue EOFError + self.event_eof sock + rescue Errno::EPIPE => e + self.event_errno e, sock, event + rescue IOError + self.event_ioerror sock, event + rescue Errno::ECONNRESET => e + self.event_errno e, sock, event + end + + def event_write sock = @sock, event = :write + @writebuf.remove! sock.syswrite( @writebuf) + @select.write_del sock if @writebuf.empty? + @writebuf + rescue IOError + @select.del @sock + @writebuf + end + + def event_eof sock = @sock + self.close + end + + def event_errno errno, sock = @sock, event = :error + self.close + end + + def event_error sock = @sock, event = :error + self.close + end + + def event_ioerror sock = @sock, event = :error + end + + def closed? + @sock.closed? + end +end + +class Select::Server + attr_reader :select, :clientclass + + def initialize opts + raise "You can't use this class directly. Create a subclass" if self.class.superclass == Object + init opts + select.read_set @sock, &self.method( :event_conn) + end + + def init opts + @sock = opts[ :sock] || raise( ArgumentError, "need sock") + @select = opts[ :select] || Select.new + @clientclass = opts[ :clientclass] || Select::Socket + @clients = [] + end + + def run + @select.run + end + + def delete sock + @select.del sock + end + + def event_conn sock = @sock, event = :read + a = sock.accept + c = event_new_client a + if c.kind_of? Hash + cc = c[ :clientclass] || @clientclass + h = { :sock => a, :select => @select, :parent => self }.update c + c = cc.new h + end + @clients.push c + end + + def event_error sock = @sock, event = :error + end + + def event_new_client sock + Hash.new + end + + def event_client_closed client + @clients.delete client + end + + def close + @select.del @sock + @sock.close + rescue IOError + end + + def clients_close + @clients.each {|c| c.close } + end +end diff --git a/select.gemspec b/select.gemspec new file mode 100644 index 0000000..b9309c7 --- /dev/null +++ b/select.gemspec @@ -0,0 +1,35 @@ +# Generated by jeweler +# DO NOT EDIT THIS FILE DIRECTLY +# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command +# -*- encoding: utf-8 -*- + +Gem::Specification.new do |s| + s.name = %q{select} + s.version = "0.2.0" + + s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= + s.authors = ["Denis Knauf"] + s.date = %q{2010-01-30} + s.description = %q{Select based event-handler for servers and sockets} + s.email = %q{Denis.Knauf@gmail.com} + s.files = [ + "VERSION", + "lib/select.rb" + ] + s.homepage = %q{http://github.com/DenisKnauf/select} + s.rdoc_options = ["--charset=UTF-8"] + s.require_paths = ["lib"] + s.rubygems_version = %q{1.3.5} + s.summary = %q{IO-event-handler based on select} + + if s.respond_to? :specification_version then + current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION + s.specification_version = 3 + + if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then + else + end + else + end +end +