event-loop-0.3/0000755000175000017500000000000011574172561012473 5ustar gwolfgwolfevent-loop-0.3/lib/0000755000175000017500000000000011574172561013241 5ustar gwolfgwolfevent-loop-0.3/lib/event-loop.rb0000644000175000017500000000004011574172561015650 0ustar gwolfgwolfrequire "event-loop/event-loop" event-loop-0.3/lib/event-loop/0000755000175000017500000000000011574172561015331 5ustar gwolfgwolfevent-loop-0.3/lib/event-loop/io.rb0000644000175000017500000000455711574172561016300 0ustar gwolfgwolf## io.rb --- convenience features for IO objects # Copyright (C) 2005, 2006 Daniel Brockman # This program is free software; you can redistribute it # and/or modify it under the terms of the GNU General Public # License as published by the Free Software Foundation; # either version 2 of the License, or (at your option) any # later version. # This file is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty # of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # See the GNU General Public License for more details. # You should have received a copy of the GNU General Public # License along with this program; if not, write to the Free # Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. require "event-loop" require "fcntl" class Symbol def io_state? EventLoop::IO_STATES.include? self end end module EventLoop::Watchable include SignalEmitter define_signals :readable, :writable, :exceptional def monitor_events (*events) EventLoop.monitor_io(self, *events) end def ignore_events (*events) EventLoop.ignore_io(self, *events) end define_soft_aliases \ :monitor_event => :monitor_events, :ignore_event => :ignore_events def close ; super ignore_events end def close_read ; super ignore_event :readable end def close_write ; super ignore_event :writable end module Automatic include EventLoop::Watchable def add_signal_handler (name, &handler) super monitor_event(name) if name.io_state? end def remove_signal_handler (name, handler) super if @signal_handlers[name].empty? ignore_event(name) if name.io_state? end end end end class IO def on_readable &block extend EventLoop::Watchable::Automatic on_readable(&block) end def on_writable &block extend EventLoop::Watchable::Automatic on_writable(&block) end def on_exceptional &block extend EventLoop::Watchable::Automatic on_exceptional(&block) end def will_block? require "fcntl" fcntl(Fcntl::F_GETFL, 0) & Fcntl::O_NONBLOCK == 0 end def will_block= (wants_blocking) require "fcntl" flags = fcntl(Fcntl::F_GETFL, 0) if wants_blocking flags &= ~Fcntl::O_NONBLOCK else flags |= Fcntl::O_NONBLOCK end fcntl(Fcntl::F_SETFL, flags) end end ## io.rb ends here. event-loop-0.3/lib/event-loop/better-definers.rb0000644000175000017500000002507411574172561020750 0ustar gwolfgwolf## better-definers.rb --- better attribute and method definers # Copyright (C) 2005 Daniel Brockman # This program is free software; you can redistribute it # and/or modify it under the terms of the GNU General Public # License as published by the Free Software Foundation; # either version 2 of the License, or (at your option) any # later version. # This file is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty # of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # See the GNU General Public License for more details. # You should have received a copy of the GNU General Public # License along with this program; if not, write to the Free # Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. class Symbol def predicate? to_s.include? "?" end def imperative? to_s.include? "!" end def writer? to_s.include? "=" end def punctuated? predicate? or imperative? or writer? end def without_punctuation to_s.delete("?!=").to_sym end def predicate without_punctuation.to_s + "?" end def imperative without_punctuation.to_s + "!" end def writer without_punctuation.to_s + "=" end end class Hash def collect! (&block) replace Hash[*collect(&block).flatten] end def flatten to_a.flatten end end module Kernel def returning (value) yield value ; value end end class Module def define_hard_aliases (name_pairs) for new_aliases, existing_name in name_pairs do new_aliases.kind_of? Array or new_aliases = [new_aliases] for new_alias in new_aliases do alias_method(new_alias, existing_name) end end end def define_soft_aliases (name_pairs) for new_aliases, existing_name in name_pairs do new_aliases.kind_of? Array or new_aliases = [new_aliases] for new_alias in new_aliases do class_eval %{def #{new_alias}(*args, &block) #{existing_name}(*args, &block) end} end end end define_soft_aliases \ :define_hard_alias => :define_hard_aliases, :define_soft_alias => :define_soft_aliases # This method lets you define predicates like :foo?, # which will be defined to return the value of @foo. def define_readers (*names) for name in names.map { |x| x.to_sym } do if name.punctuated? # There's no way to define an efficient reader whose # name is different from the instance variable. class_eval %{def #{name} ; @#{name.without_punctuation} end} else # Use `attr_reader' to define an efficient method. attr_reader(name) end end end def writer_defined? (name) method_defined? name.to_sym.writer end # If you pass a predicate symbol :foo? to this method, it'll first # define a regular writer method :foo, without a question mark. # Then it'll define an imperative writer method :foo! as a shorthand # for setting the property to true. def define_writers (*names, &body) for name in names.map { |x| x.to_sym } do if block_given? define_method(name.writer, &body) else attr_writer(name.without_punctuation) end if name.predicate? class_eval %{def #{name.imperative} self.#{name.writer} true end} end end end define_soft_aliases \ :define_reader => :define_readers, :define_writer => :define_writers # We don't need a singular alias for `define_accessors', # because it always defines at least two methods. def define_accessors (*names) define_readers(*names) define_writers(*names) end def define_opposite_readers (name_pairs) name_pairs.collect! { |k, v| [k.to_sym, v.to_sym] } for opposite_name, name in name_pairs do define_reader(name) unless method_defined? name class_eval %{def #{opposite_name} ; not #{name} end} end end def define_opposite_writers (name_pairs) name_pairs.collect! { |k, v| [k.to_sym, v.to_sym] } for opposite_name, name in name_pairs do define_writer(name) unless writer_defined? name class_eval %{def #{opposite_name.writer} x self.#{name.writer} !x end} class_eval %{def #{opposite_name.imperative} self.#{name.writer} false end} end end define_soft_aliases \ :define_opposite_reader => :define_opposite_readers, :define_opposite_writer => :define_opposite_writers def define_opposite_accessors (name_pairs) define_opposite_readers name_pairs define_opposite_writers name_pairs end def define_reader_with_opposite (name_pair, &body) name, opposite_name = name_pair.flatten.collect { |x| x.to_sym } define_method(name, &body) define_opposite_reader(opposite_name => name) end def define_writer_with_opposite (name_pair, &body) name, opposite_name = name_pair.flatten.collect { |x| x.to_sym } define_writer(name, &body) define_opposite_writer(opposite_name => name) end public :define_method def define_methods (*names, &body) names.each { |name| define_method(name, &body) } end def define_private_methods (*names, &body) define_methods(*names, &body) names.each { |name| private name } end def define_protected_methods (*names, &body) define_methods(*names, &body) names.each { |name| protected name } end def define_private_method (name, &body) define_method(name, &body) private name end def define_protected_method (name, &body) define_method(name, &body) protected name end end class ImmutableAttributeError < StandardError def initialize (attribute=nil, message=nil) super message @attribute = attribute end define_accessors :attribute def to_s if @attribute and @message "cannot change the value of `#@attribute': #@message" elsif @attribute "cannot change the value of `#@attribute'" elsif @message "cannot change the value of attribute: #@message" else "cannot change the value of attribute" end end end class Module # Guard each of the specified attributes by replacing the writer # method with a proxy that asks the supplied block before proceeding # with the change. # # If it's okay to change the attribute, the block should return # either nil or the symbol :mutable. If it isn't okay, the block # should return a string saying why the attribute can't be changed. # If you don't want to provide a reason, you can have the block # return just the symbol :immutable. def guard_writers(*names, &predicate) for name in names.map { |x| x.to_sym } do define_hard_alias("__unguarded_#{name.writer}" => name.writer) define_method(name.writer) do |new_value| case result = predicate.call when :mutable, nil __send__("__unguarded_#{name.writer}", new_value) when :immutable raise ImmutableAttributeError.new(name) else raise ImmutableAttributeError.new(name, result) end end end end def define_guarded_writers (*names, &block) define_writers(*names) guard_writers(*names, &block) end define_soft_alias :guard_writer => :guard_writers define_soft_alias :define_guarded_writer => :define_guarded_writers end if __FILE__ == $0 require "test/unit" class DefineAccessorsTest < Test::Unit::TestCase def setup @X = Class.new @Y = Class.new @X @x = @X.new @y = @Y.new end def test_define_hard_aliases @X.define_method(:foo) { 123 } @X.define_method(:baz) { 321 } @X.define_hard_aliases :bar => :foo, :quux => :baz assert_equal @x.foo, 123 assert_equal @x.bar, 123 assert_equal @y.foo, 123 assert_equal @y.bar, 123 assert_equal @x.baz, 321 assert_equal @x.quux, 321 assert_equal @y.baz, 321 assert_equal @y.quux, 321 @Y.define_method(:foo) { 456 } assert_equal @y.foo, 456 assert_equal @y.bar, 123 @Y.define_method(:quux) { 654 } assert_equal @y.baz, 321 assert_equal @y.quux, 654 end def test_define_soft_aliases @X.define_method(:foo) { 123 } @X.define_method(:baz) { 321 } @X.define_soft_aliases :bar => :foo, :quux => :baz assert_equal @x.foo, 123 assert_equal @x.bar, 123 assert_equal @y.foo, 123 assert_equal @y.bar, 123 assert_equal @x.baz, 321 assert_equal @x.quux, 321 assert_equal @y.baz, 321 assert_equal @y.quux, 321 @Y.define_method(:foo) { 456 } assert_equal @y.foo, @y.bar, 456 @Y.define_method(:quux) { 654 } assert_equal @y.baz, 321 assert_equal @y.quux, 654 end def test_define_readers @X.define_readers :foo, :bar assert !@x.respond_to?(:foo=) assert !@x.respond_to?(:bar=) @x.instance_eval { @foo = 123 ; @bar = 456 } assert_equal @x.foo, 123 assert_equal @x.bar, 456 @X.define_readers :baz?, :quux? assert !@x.respond_to?(:baz=) assert !@x.respond_to?(:quux=) @x.instance_eval { @baz = false ; @quux = true } assert !@x.baz? assert @x.quux? end def test_define_writers assert !@X.writer_defined?(:foo) assert !@X.writer_defined?(:bar) @X.define_writers :foo, :bar assert @X.writer_defined?(:foo) assert @X.writer_defined?(:bar) assert @X.writer_defined?(:foo=) assert @X.writer_defined?(:bar=) assert @X.writer_defined?(:foo?) assert @X.writer_defined?(:bar?) assert !@x.respond_to?(:foo) assert !@x.respond_to?(:bar) @x.foo = 123 @x.bar = 456 assert_equal @x.instance_eval { @foo }, 123 assert_equal @x.instance_eval { @bar }, 456 @X.define_writers :baz?, :quux? assert !@x.respond_to?(:baz?) assert !@x.respond_to?(:quux?) @x.baz = true @x.quux = false assert_equal @x.instance_eval { @baz }, true assert_equal @x.instance_eval { @quux }, false end def test_define_accessors @X.define_accessors :foo, :bar @x.foo = 123 ; @x.bar = 456 assert_equal @x.foo, 123 assert_equal @x.bar, 456 end def test_define_opposite_readers @X.define_opposite_readers :foo? => :bar?, :baz? => :quux? assert !@x.respond_to?(:foo=) assert !@x.respond_to?(:bar=) assert !@x.respond_to?(:baz=) assert !@x.respond_to?(:quux=) @x.instance_eval { @bar = true ; @quux = false } assert !@x.foo? assert @x.bar? assert @x.baz? assert !@x.quux? end def test_define_opposite_writers @X.define_opposite_writers :foo? => :bar?, :baz => :quux end end end ## better-definers.rb ends here. event-loop-0.3/lib/event-loop/event-loop.rb0000644000175000017500000001166111574172561017753 0ustar gwolfgwolf## event-loop.rb --- high-level IO multiplexer # Copyright (C) 2005, 2006 Daniel Brockman # This program is free software; you can redistribute it # and/or modify it under the terms of the GNU General Public # License as published by the Free Software Foundation; # either version 2 of the License, or (at your option) any # later version. # This file is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty # of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # See the GNU General Public License for more details. # You should have received a copy of the GNU General Public # License along with this program; if not, write to the Free # Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. require "event-loop/better-definers" require "event-loop/signal-system" class EventLoop ; end require "event-loop/io" class EventLoop module Utilities def self.validate_keyword_arguments (actual, allowed) (unknown_keys = actual - allowed).empty? or fail "unrecognized keyword argument" + "#{"s" if unknown_keys.size > 1}: " + unknown_keys.map { |x| "`#{x}'" }.join(", ") end end end class EventLoop include SignalEmitter IO_STATES = [:readable, :writable, :exceptional] class << self def default ; @default ||= new end def default= x ; @default = x end def current Thread.current["event-loop::current"] || default end def current= x Thread.current["event-loop::current"] = x end def with_current (new) # Be sure to return the value of the block. if current == new yield else begin old = self.current self.current = new yield ensure current == new or warn "uncontained change " + "to `EventLoop.current' within dynamic " + "extent of `EventLoop.with_current'" self.current = old end end end def method_missing (name, *args, &block) if current.respond_to? name current.__send__(name, *args, &block) else super end end end define_signals :before_sleep, :after_sleep def initialize @running = false @awake = false @wakeup_time = nil @timers = [] @io_arrays = [[], [], []] @ios = Hash.new do |h, k| raise ArgumentError, "invalid IO event: #{k}", caller(2) end IO_STATES.each_with_index { |x, i| @ios[x] = @io_arrays[i] } @notify_src, @notify_snk = IO.pipe @notify_src.will_block = false @notify_snk.will_block = false # For bootstrapping reasons, we can't let the stub # implementation of IO#on_readable set this up. monitor_io(@notify_src, :readable) @notify_src.extend(Watchable) # Each time a byte is sent through the notification pipe # we need to read it, or IO.select will keep returning. @notify_src.on_readable do begin @notify_src.sysread(256) rescue Errno::EAGAIN # The pipe wasn't readable after all. end end end define_opposite_accessors \ :stopped? => :running?, :asleep? => :awake? # This is an old name for the property. define_hard_alias :sleeping? => :asleep? def run if block_given? thread = Thread.new { run } yield ; quit ; thread.join else running! iterate while running? end ensure quit end def iterate (user_timeout=nil) t1, t2 = user_timeout, max_timeout timeout = t1 && t2 ? [t1, t2].min : t1 || t2 select(timeout).zip(IO_STATES) do |ios, state| ios.each { |x| x.signal(state) } if ios end end private def select (timeout) @wakeup_time = timeout ? Time.now + timeout : nil # puts "waiting: #{timeout} seconds" signal :before_sleep ; asleep! IO.select(*@io_arrays + [timeout]) || [] ensure awake! ; signal :after_sleep @timers.each { |x| x.sound_alarm if x.ready? } end public def quit ; stopped! ; wake_up ; self end def monitoring_io? (io, event) @ios[event].include? io end def monitoring_timer? (timer) @timers.include? timer end def monitor_io (io, *events) for event in events do unless monitoring_io?(io, event) @ios[event] << io ; wake_up end end end def monitor_timer (timer) @timers << timer unless monitoring_timer? timer check_timer(timer) end def check_timer (timer) wake_up if running? and asleep? and timer.end_time < @wakeup_time end def ignore_io (io, *events) events = IO_STATES if events.empty? for event in events do wake_up if @ios[event].delete(io) end end def ignore_timer (timer) # Don't need to wake up for this. @timers.delete(timer) end def max_timeout return nil if @timers.empty? [@timers.collect { |x| x.time_left }.min, 0].max end def wake_up returning self do @notify_snk.write('.') if asleep? end end end ## event-loop.rb ends here. event-loop-0.3/lib/event-loop/timer.rb0000644000175000017500000001647511574172561017013 0ustar gwolfgwolf## timer.rb --- timer implementations for the event loop # Copyright (C) 2005, 2006 Daniel Brockman # This program is free software; you can redistribute it # and/or modify it under the terms of the GNU General Public # License as published by the Free Software Foundation; # either version 2 of the License, or (at your option) any # later version. # This file is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty # of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # See the GNU General Public License for more details. # You should have received a copy of the GNU General Public # License along with this program; if not, write to the Free # Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. require "event-loop" class EventLoop def every (interval, options={}, &body) options[:event_loop] ||= self PeriodicTimer.new(interval, options, &body).start end def after (interval, options={}, &body) options[:event_loop] ||= self SporadicTimer.new(interval, options, &body).start end def repeat (&body) every(0, &body) end def later (&body) after(0, &body) end end class EventLoop::Timer include SignalEmitter DEFAULT_TOLERANCE = 0.001 define_opposite_readers :stopped? => :running? define_readers :interval, :tolerance, :event_loop define_signal :alarm def initialize (interval, options={}, &alarm_handler) EventLoop::Utilities.validate_keyword_arguments options.keys, [:tolerance, :event_loop] @running = false @start_time = nil @interval = interval @event_loop = options[:event_loop] || EventLoop.current @alarm_handler = alarm_handler and replace_alarm_handler(&@alarm_handler) if options[:tolerance] @tolerance = options[:tolerance].to_f elsif DEFAULT_TOLERANCE < @interval @tolerance = DEFAULT_TOLERANCE else @tolerance = 0.0 end end def start_time ; @start_time or fail "the timer has not been started" end def end_time ; start_time + @interval end def time_left ; end_time - Time.now end def ready? ; time_left <= @tolerance end def interval= (new_interval) old_interval = @interval @interval = new_interval if running? and new_interval < old_interval @event_loop.check_timer(self) end end def end_time= (new_end_time) self.interval = new_end_time - start_time end def time_left= (new_time_left) self.end_time = Time.now + new_time_left end def replace_alarm_handler (&block) remove_signal_handler(:alarm, @alarm_handler) if @alarm_handler add_signal_handler(:alarm, &block) @alarm_handler = block end def restart (&block) running? or raise "the timer is not running" replace_alarm_handler(&block) if block_given? returning self do @start_time = Time.now end end def start (&block) replace_alarm_handler(&block) if block_given? returning self do @start_time = Time.now @event_loop.monitor_timer(self) @running = true end end def stop returning self do @event_loop.ignore_timer(self) @running = false end end class << self define_hard_alias :regular_new => :new def new (*a, &b) warn "event-loop: As of event-loop 0.3, `EventLoop::Timer.new' " + "is deprecated in favor of `EventLoop#every' and " + "`EventLoop#after'; see the documentation for more information." new!(*a, &b) end def new! (options={}, &body) if options.kind_of? Numeric interval = options options = {} elsif options.include? :interval interval = options[:interval].to_f options.delete(:interval) else interval = 0.0 end EventLoop::Utilities.validate_keyword_arguments options.keys, [:interval, :tolerance, :start?, :event_loop] if options.include? :start? start = options.delete(:start?) else start = block_given? end timer = EventLoop::PeriodicTimer.new(interval, options) timer.on_alarm(&body) if block_given? timer.start if start return timer end end end class EventLoop::PeriodicTimer < EventLoop::Timer class << self define_soft_alias :new => :regular_new end def sound_alarm signal :alarm ; restart if running? end end class EventLoop::SporadicTimer < EventLoop::Timer class << self define_soft_alias :new => :regular_new end def sound_alarm stop ; signal :alarm end end class Numeric def nanoseconds ; self / 1_000_000_000.0 end def microseconds ; self / 1_000_000.0 end def milliseconds ; self / 1_000.0 end def seconds ; self end def minutes ; self * 60.seconds end def hours ; self * 60.minutes end def days ; self * 24.hours end def weeks ; self * 7.days end def years ; self * 365.24.days end define_hard_aliases \ :nanosecond => :nanoseconds, :microsecond => :microseconds, :millisecond => :milliseconds, :second => :seconds, :minute => :minutes, :hour => :hours, :day => :days, :week => :weeks, :year => :years define_hard_aliases \ :ns => :nanoseconds, :ms => :milliseconds def half ; self / 2.0 end def quarter ; self / 4.0 end def from_now (&block) EventLoop.after(self, &block) end def from_now_and_repeat (&block) EventLoop.every(self, &block) end end class Integer # It turns out whole numbers of years are # always whole numbers of seconds. def years ; super.to_i end define_hard_alias :year => :years end def Time.measure t0 = now ; yield ; now - t0 end if __FILE__ == $0 require "test/unit" class TimerTest < Test::Unit::TestCase def setup EventLoop.current = EventLoop.new @timer = EventLoop::Timer.new!(:interval => 1.ms) end def test_monitor_unstarted_timer assert_raise RuntimeError do EventLoop.monitor_timer(@timer) EventLoop.run end end def test_start_monitoring_timer_while_running EventLoop.later { 1.ms.from_now { EventLoop.quit } } 1.second.from_now { EventLoop.quit } assert Time.measure { EventLoop.run } < 1.half.second end def test_start_monitoring_timer_while_running_deprecated @timer.start { EventLoop::Timer.new!(1.ms) { EventLoop.quit } } EventLoop::Timer.new!(1.second) { EventLoop.quit } assert Time.measure { EventLoop.run } < 1.half.second end def test_timer_tolerance timer = EventLoop::SporadicTimer.new(10.milliseconds) do puts "[#{timer.time_left * 1000} milliseconds left on alarm]" EventLoop.quit end 8.times do dt = Time.measure { timer.start ; EventLoop.run } assert(dt > timer.interval - timer.tolerance) end end end class SporadicTimerTest < Test::Unit::TestCase def setup EventLoop.current = EventLoop.new end def test_sporadicity counter = 0 1.nanosecond.from_now { counter += 1 } 5.times { EventLoop.iterate(10.milliseconds) } assert counter == 1 end end class PeriodicTimerTest < Test::Unit::TestCase def setup EventLoop.current = EventLoop.new end def test_periodicity counter = 0 EventLoop.every(1.nanosecond) { counter += 1 } 5.times { EventLoop.iterate(10.milliseconds) } assert counter == 5 end end end ## timer.rb ends here. event-loop-0.3/lib/event-loop/signal-system.rb0000644000175000017500000001464111574172561020463 0ustar gwolfgwolf## signal-system.rb --- simple intra-process signal system # Copyright (C) 2005, 2006 Daniel Brockman # This program is free software; you can redistribute it # and/or modify it under the terms of the GNU General Public # License as published by the Free Software Foundation; # either version 2 of the License, or (at your option) any # later version. # This file is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty # of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # See the GNU General Public License for more details. # You should have received a copy of the GNU General Public # License along with this program; if not, write to the Free # Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. require "event-loop/better-definers" module SignalEmitterModule def self.extended (object) if object.kind_of? Module and not object < SignalEmitter if object.respond_to? :fcall # This is the way to call private methods # in Ruby 1.9 as of November 16. object.fcall :include, SignalEmitter else object.__send__ :include, SignalEmitter end end end def define_signal (name, slot=:before, &body) # Can't use `define_method' and take a block pre-1.9. class_eval %{ def on_#{name} &block add_signal_handler(:#{name}, &block) end } define_signal_handler(name, :before, &lambda {|*a|}) define_signal_handler(name, :after, &lambda {|*a|}) define_signal_handler(name, slot, &body) if block_given? end def define_signals (*names, &body) names.each { |x| define_signal(x, &body) } end def define_signal_handler (name, slot=:before, &body) case slot when :before define_protected_method "prehandle_#{name}", &body when :after define_protected_method "posthandle_#{name}", &body else raise ArgumentError, "invalid slot `#{slot.inspect}'; " + "should be `:before' or `:after'", caller(1) end end end # This is an old name for the same thing. SignalEmitterClass = SignalEmitterModule module SignalEmitter def self.included (includer) if not includer.kind_of? SignalEmitterClass includer.extend SignalEmitterClass end end def __maybe_initialize_signal_emitter @signal_handlers ||= Hash.new { |h, k| h[k] = Array.new } @allow_dynamic_signals ||= false end define_accessors :allow_dynamic_signals? def add_signal_handler (name, &handler) __maybe_initialize_signal_emitter @signal_handlers[name] << handler return handler end define_soft_aliases [:on, :on_signal] => :add_signal_handler def remove_signal_handler (name, handler) __maybe_initialize_signal_emitter @signal_handlers[name].delete(handler) end def __signal__ (name, *args, &block) __maybe_initialize_signal_emitter respond_to? "on_#{name}" or allow_dynamic_signals? or fail "undefined signal `#{name}' for #{self}:#{self.class}" __send__("prehandle_#{name}", *args, &block) if respond_to? "prehandle_#{name}" # This is an old name for the same thing: __send__("handle_#{name}", *args, &block) if respond_to? "handle_#{name}" @signal_handlers[name].each { |x| x.call(*args, &block) } __send__("posthandle_#{name}", *args, &block) if respond_to? "posthandle_#{name}" # This is an old name for the same thing: __send__("after_handle_#{name}", *args, &block) if respond_to? "after_handle_#{name}" end define_soft_alias :signal => :__signal__ end # This module is indended to be a convenience mixin to be used by # classes whose objects need to observe foreign signals. That is, # if you want to observe some signals coming from an object, *you* # should mix in this module. # # You cannot use this module at two different places of the same # inheritance chain to observe signals coming from the same object. # # XXX: This has not seen much use, and I'd like to provide a # better solution for the problem in the future. module SignalObserver def __maybe_initialize_signal_observer @observed_signals ||= Hash.new do |signals, object| signals[object] = Hash.new do |handlers, name| handlers[name] = Array.new end end end def observe_signal (subject, name, &handler) __maybe_initialize_signal_observer @observed_signals[subject][name] << handler subject.add_signal_handler(name, &handler) end def map_signals (source, pairs={}) pairs.each do |src_name, dst_name| observe_signal(source, src_name) do |*args| __signal__(dst_name, *args) end end end def absorb_signals (subject, *names) names.each do |name| observe_signal(subject, name) do |*args| __signal__(name, *args) end end end define_soft_aliases \ :map_signal => :map_signals, :absorb_signal => :absorb_signals def ignore_signal (subject, name) __maybe_initialize_signal_observer __ignore_signal_1(subject, name) @observed_signals.delete(subject) if @observed_signals[subject].empty? end def ignore_signals (subject, *names) __maybe_initialize_signal_observer names = @observed_signals[subject] if names.empty? names.each { |x| __ignore_signal_1(subject, x) } end private def __ignore_signal_1(subject, name) @observed_signals[subject][name].each do |handler| subject.remove_signal_handler(name, handler) end @observed_signals[subject].delete(name) end end if __FILE__ == $0 require "test/unit" class SignalEmitterTest < Test::Unit::TestCase class X include SignalEmitter define_signal :foo end def setup @x = X.new end def test_on_signal moomin = 0 @x.on_signal(:foo) { moomin = 1 } @x.signal :foo assert moomin == 1 end def test_on_foo moomin = 0 @x.on_foo { moomin = 1 } @x.signal :foo assert moomin == 1 end def test_multiple_on_signal moomin = 0 @x.on_signal(:foo) { moomin += 1 } @x.on_signal(:foo) { moomin += 2 } @x.on_signal(:foo) { moomin += 4 } @x.on_signal(:foo) { moomin += 8 } @x.signal :foo assert moomin == 15 end def test_multiple_on_foo moomin = 0 @x.on_foo { moomin += 1 } @x.on_foo { moomin += 2 } @x.on_foo { moomin += 4 } @x.on_foo { moomin += 8 } @x.signal :foo assert moomin == 15 end end end ## signal-system.rb ends here. event-loop-0.3/GFDL0000644000175000017500000004766311574172561013152 0ustar gwolfgwolf GNU Free Documentation License Version 1.2, November 2002 Copyright (C) 2000,2001,2002 Free Software Foundation, Inc. 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. 0. PREAMBLE The purpose of this License is to make a manual, textbook, or other functional and useful document "free" in the sense of freedom: to assure everyone the effective freedom to copy and redistribute it, with or without modifying it, either commercially or noncommercially. Secondarily, this License preserves for the author and publisher a way to get credit for their work, while not being considered responsible for modifications made by others. This License is a kind of "copyleft", which means that derivative works of the document must themselves be free in the same sense. It complements the GNU General Public License, which is a copyleft license designed for free software. We have designed this License in order to use it for manuals for free software, because free software needs free documentation: a free program should come with manuals providing the same freedoms that the software does. But this License is not limited to software manuals; it can be used for any textual work, regardless of subject matter or whether it is published as a printed book. We recommend this License principally for works whose purpose is instruction or reference. 1. APPLICABILITY AND DEFINITIONS This License applies to any manual or other work, in any medium, that contains a notice placed by the copyright holder saying it can be distributed under the terms of this License. Such a notice grants a world-wide, royalty-free license, unlimited in duration, to use that work under the conditions stated herein. The "Document", below, refers to any such manual or work. Any member of the public is a licensee, and is addressed as "you". You accept the license if you copy, modify or distribute the work in a way requiring permission under copyright law. A "Modified Version" of the Document means any work containing the Document or a portion of it, either copied verbatim, or with modifications and/or translated into another language. A "Secondary Section" is a named appendix or a front-matter section of the Document that deals exclusively with the relationship of the publishers or authors of the Document to the Document's overall subject (or to related matters) and contains nothing that could fall directly within that overall subject. (Thus, if the Document is in part a textbook of mathematics, a Secondary Section may not explain any mathematics.) The relationship could be a matter of historical connection with the subject or with related matters, or of legal, commercial, philosophical, ethical or political position regarding them. The "Invariant Sections" are certain Secondary Sections whose titles are designated, as being those of Invariant Sections, in the notice that says that the Document is released under this License. If a section does not fit the above definition of Secondary then it is not allowed to be designated as Invariant. The Document may contain zero Invariant Sections. If the Document does not identify any Invariant Sections then there are none. The "Cover Texts" are certain short passages of text that are listed, as Front-Cover Texts or Back-Cover Texts, in the notice that says that the Document is released under this License. A Front-Cover Text may be at most 5 words, and a Back-Cover Text may be at most 25 words. A "Transparent" copy of the Document means a machine-readable copy, represented in a format whose specification is available to the general public, that is suitable for revising the document straightforwardly with generic text editors or (for images composed of pixels) generic paint programs or (for drawings) some widely available drawing editor, and that is suitable for input to text formatters or for automatic translation to a variety of formats suitable for input to text formatters. A copy made in an otherwise Transparent file format whose markup, or absence of markup, has been arranged to thwart or discourage subsequent modification by readers is not Transparent. An image format is not Transparent if used for any substantial amount of text. A copy that is not "Transparent" is called "Opaque". Examples of suitable formats for Transparent copies include plain ASCII without markup, Texinfo input format, LaTeX input format, SGML or XML using a publicly available DTD, and standard-conforming simple HTML, PostScript or PDF designed for human modification. Examples of transparent image formats include PNG, XCF and JPG. Opaque formats include proprietary formats that can be read and edited only by proprietary word processors, SGML or XML for which the DTD and/or processing tools are not generally available, and the machine-generated HTML, PostScript or PDF produced by some word processors for output purposes only. The "Title Page" means, for a printed book, the title page itself, plus such following pages as are needed to hold, legibly, the material this License requires to appear in the title page. For works in formats which do not have any title page as such, "Title Page" means the text near the most prominent appearance of the work's title, preceding the beginning of the body of the text. A section "Entitled XYZ" means a named subunit of the Document whose title either is precisely XYZ or contains XYZ in parentheses following text that translates XYZ in another language. (Here XYZ stands for a specific section name mentioned below, such as "Acknowledgements", "Dedications", "Endorsements", or "History".) To "Preserve the Title" of such a section when you modify the Document means that it remains a section "Entitled XYZ" according to this definition. The Document may include Warranty Disclaimers next to the notice which states that this License applies to the Document. These Warranty Disclaimers are considered to be included by reference in this License, but only as regards disclaiming warranties: any other implication that these Warranty Disclaimers may have is void and has no effect on the meaning of this License. 2. VERBATIM COPYING You may copy and distribute the Document in any medium, either commercially or noncommercially, provided that this License, the copyright notices, and the license notice saying this License applies to the Document are reproduced in all copies, and that you add no other conditions whatsoever to those of this License. You may not use technical measures to obstruct or control the reading or further copying of the copies you make or distribute. However, you may accept compensation in exchange for copies. If you distribute a large enough number of copies you must also follow the conditions in section 3. You may also lend copies, under the same conditions stated above, and you may publicly display copies. 3. COPYING IN QUANTITY If you publish printed copies (or copies in media that commonly have printed covers) of the Document, numbering more than 100, and the Document's license notice requires Cover Texts, you must enclose the copies in covers that carry, clearly and legibly, all these Cover Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on the back cover. Both covers must also clearly and legibly identify you as the publisher of these copies. The front cover must present the full title with all words of the title equally prominent and visible. You may add other material on the covers in addition. Copying with changes limited to the covers, as long as they preserve the title of the Document and satisfy these conditions, can be treated as verbatim copying in other respects. If the required texts for either cover are too voluminous to fit legibly, you should put the first ones listed (as many as fit reasonably) on the actual cover, and continue the rest onto adjacent pages. If you publish or distribute Opaque copies of the Document numbering more than 100, you must either include a machine-readable Transparent copy along with each Opaque copy, or state in or with each Opaque copy a computer-network location from which the general network-using public has access to download using public-standard network protocols a complete Transparent copy of the Document, free of added material. If you use the latter option, you must take reasonably prudent steps, when you begin distribution of Opaque copies in quantity, to ensure that this Transparent copy will remain thus accessible at the stated location until at least one year after the last time you distribute an Opaque copy (directly or through your agents or retailers) of that edition to the public. It is requested, but not required, that you contact the authors of the Document well before redistributing any large number of copies, to give them a chance to provide you with an updated version of the Document. 4. MODIFICATIONS You may copy and distribute a Modified Version of the Document under the conditions of sections 2 and 3 above, provided that you release the Modified Version under precisely this License, with the Modified Version filling the role of the Document, thus licensing distribution and modification of the Modified Version to whoever possesses a copy of it. In addition, you must do these things in the Modified Version: A. Use in the Title Page (and on the covers, if any) a title distinct from that of the Document, and from those of previous versions (which should, if there were any, be listed in the History section of the Document). You may use the same title as a previous version if the original publisher of that version gives permission. B. List on the Title Page, as authors, one or more persons or entities responsible for authorship of the modifications in the Modified Version, together with at least five of the principal authors of the Document (all of its principal authors, if it has fewer than five), unless they release you from this requirement. C. State on the Title page the name of the publisher of the Modified Version, as the publisher. D. Preserve all the copyright notices of the Document. E. Add an appropriate copyright notice for your modifications adjacent to the other copyright notices. F. Include, immediately after the copyright notices, a license notice giving the public permission to use the Modified Version under the terms of this License, in the form shown in the Addendum below. G. Preserve in that license notice the full lists of Invariant Sections and required Cover Texts given in the Document's license notice. H. Include an unaltered copy of this License. I. Preserve the section Entitled "History", Preserve its Title, and add to it an item stating at least the title, year, new authors, and publisher of the Modified Version as given on the Title Page. If there is no section Entitled "History" in the Document, create one stating the title, year, authors, and publisher of the Document as given on its Title Page, then add an item describing the Modified Version as stated in the previous sentence. J. Preserve the network location, if any, given in the Document for public access to a Transparent copy of the Document, and likewise the network locations given in the Document for previous versions it was based on. These may be placed in the "History" section. You may omit a network location for a work that was published at least four years before the Document itself, or if the original publisher of the version it refers to gives permission. K. For any section Entitled "Acknowledgements" or "Dedications", Preserve the Title of the section, and preserve in the section all the substance and tone of each of the contributor acknowledgements and/or dedications given therein. L. Preserve all the Invariant Sections of the Document, unaltered in their text and in their titles. Section numbers or the equivalent are not considered part of the section titles. M. Delete any section Entitled "Endorsements". Such a section may not be included in the Modified Version. N. Do not retitle any existing section to be Entitled "Endorsements" or to conflict in title with any Invariant Section. O. Preserve any Warranty Disclaimers. If the Modified Version includes new front-matter sections or appendices that qualify as Secondary Sections and contain no material copied from the Document, you may at your option designate some or all of these sections as invariant. To do this, add their titles to the list of Invariant Sections in the Modified Version's license notice. These titles must be distinct from any other section titles. You may add a section Entitled "Endorsements", provided it contains nothing but endorsements of your Modified Version by various parties--for example, statements of peer review or that the text has been approved by an organization as the authoritative definition of a standard. You may add a passage of up to five words as a Front-Cover Text, and a passage of up to 25 words as a Back-Cover Text, to the end of the list of Cover Texts in the Modified Version. Only one passage of Front-Cover Text and one of Back-Cover Text may be added by (or through arrangements made by) any one entity. If the Document already includes a cover text for the same cover, previously added by you or by arrangement made by the same entity you are acting on behalf of, you may not add another; but you may replace the old one, on explicit permission from the previous publisher that added the old one. The author(s) and publisher(s) of the Document do not by this License give permission to use their names for publicity for or to assert or imply endorsement of any Modified Version. 5. COMBINING DOCUMENTS You may combine the Document with other documents released under this License, under the terms defined in section 4 above for modified versions, provided that you include in the combination all of the Invariant Sections of all of the original documents, unmodified, and list them all as Invariant Sections of your combined work in its license notice, and that you preserve all their Warranty Disclaimers. The combined work need only contain one copy of this License, and multiple identical Invariant Sections may be replaced with a single copy. If there are multiple Invariant Sections with the same name but different contents, make the title of each such section unique by adding at the end of it, in parentheses, the name of the original author or publisher of that section if known, or else a unique number. Make the same adjustment to the section titles in the list of Invariant Sections in the license notice of the combined work. In the combination, you must combine any sections Entitled "History" in the various original documents, forming one section Entitled "History"; likewise combine any sections Entitled "Acknowledgements", and any sections Entitled "Dedications". You must delete all sections Entitled "Endorsements". 6. COLLECTIONS OF DOCUMENTS You may make a collection consisting of the Document and other documents released under this License, and replace the individual copies of this License in the various documents with a single copy that is included in the collection, provided that you follow the rules of this License for verbatim copying of each of the documents in all other respects. You may extract a single document from such a collection, and distribute it individually under this License, provided you insert a copy of this License into the extracted document, and follow this License in all other respects regarding verbatim copying of that document. 7. AGGREGATION WITH INDEPENDENT WORKS A compilation of the Document or its derivatives with other separate and independent documents or works, in or on a volume of a storage or distribution medium, is called an "aggregate" if the copyright resulting from the compilation is not used to limit the legal rights of the compilation's users beyond what the individual works permit. When the Document is included in an aggregate, this License does not apply to the other works in the aggregate which are not themselves derivative works of the Document. If the Cover Text requirement of section 3 is applicable to these copies of the Document, then if the Document is less than one half of the entire aggregate, the Document's Cover Texts may be placed on covers that bracket the Document within the aggregate, or the electronic equivalent of covers if the Document is in electronic form. Otherwise they must appear on printed covers that bracket the whole aggregate. 8. TRANSLATION Translation is considered a kind of modification, so you may distribute translations of the Document under the terms of section 4. Replacing Invariant Sections with translations requires special permission from their copyright holders, but you may include translations of some or all Invariant Sections in addition to the original versions of these Invariant Sections. You may include a translation of this License, and all the license notices in the Document, and any Warranty Disclaimers, provided that you also include the original English version of this License and the original versions of those notices and disclaimers. In case of a disagreement between the translation and the original version of this License or a notice or disclaimer, the original version will prevail. If a section in the Document is Entitled "Acknowledgements", "Dedications", or "History", the requirement (section 4) to Preserve its Title (section 1) will typically require changing the actual title. 9. TERMINATION You may not copy, modify, sublicense, or distribute the Document except as expressly provided for under this License. Any other attempt to copy, modify, sublicense or distribute the Document is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 10. FUTURE REVISIONS OF THIS LICENSE The Free Software Foundation may publish new, revised versions of the GNU Free Documentation License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. See http://www.gnu.org/copyleft/. Each version of the License is given a distinguishing version number. If the Document specifies that a particular numbered version of this License "or any later version" applies to it, you have the option of following the terms and conditions either of that specified version or of any later version that has been published (not as a draft) by the Free Software Foundation. If the Document does not specify a version number of this License, you may choose any version ever published (not as a draft) by the Free Software Foundation. ADDENDUM: How to use this License for your documents To use this License in a document you have written, include a copy of the License in the document and put the following copyright and license notices just after the title page: Copyright (c) YEAR YOUR NAME. Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.2 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license is included in the section entitled "GNU Free Documentation License". If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts, replace the "with...Texts." line with this: with the Invariant Sections being LIST THEIR TITLES, with the Front-Cover Texts being LIST, and with the Back-Cover Texts being LIST. If you have Invariant Sections without Cover Texts, or some other combination of the three, merge those two alternatives to suit the situation. If your document contains nontrivial examples of program code, we recommend releasing these examples in parallel under your choice of free software license, such as the GNU General Public License, to permit their use in free software. event-loop-0.3/Rakefile0000644000175000017500000000452511574172561014146 0ustar gwolfgwolfrequire "rake/packagetask" begin require "rubygems" require "rake/gempackagetask" $have_rubygems = true rescue $have_rubygems = false end PKG_VERSION = File.open("VERSION").read.chomp PKG_FILES = FileList[*%w{[A-Z]* README setup.rb lib/**/*.rb}] MAINTAINER_MAILNAME = "wigwam.brockman.se" SCP_TARGET = "teepee:/var/www/www.brockman.se/software/ruby-event-loop/" task :default => ["README", :check] file "README" => ["README.utf-8", "Rakefile"] do |t| # I'm paranoid and I don't trust Ruby with Unicode. if "a—b".gsub(/—/, "---") != "a---b" warn "Can't build `README': broken Unicode support." else File.open("README", "w") do |out| out << "\ ------------------------------------------------------------ This file was automatically generated from `README.utf-8'. ------------------------------------------------------------\n\n" IO.foreach("README.utf-8") do |line| out << line. gsub(/©/, "(C)"). gsub(/—/, "---"). gsub(/‘/, "`"). gsub(/’/, "'"). gsub(/“/, "\""). gsub(/”/, "\"") end end end end task :check do Dir.foreach("lib/event-loop/") do |basename| next if basename[0] == ?. system %{ruby -w -Ilib "lib/event-loop/#{basename}"} end end if $have_rubygems GEMSPEC = Gem::Specification.new do |gem| gem.name = "event-loop" gem.version = PKG_VERSION gem.summary = "Simple and usable event loop and signal system" gem.description = "This package contains a simple select-based event loop " + "featuring IO (file descriptor) and timer event sources. " + "It comes with a signal system inspired by that of GLib." gem.files = PKG_FILES.to_a gem.require_path = "lib" gem.author = "Daniel Brockman" gem.email = "daniel@brockman.se" gem.homepage = "http://www.brockman.se/software/ruby-event-loop/" end Rake::GemPackageTask.new(GEMSPEC) do |task| task.need_tar_gz = true end else Rake::PackageTask.new("event-loop", PKG_VERSION) do |task| task.need_tar_gz = true task.package_files = PKG_FILES end end task :upload => :package do mailname = File.read("/etc/mailname").chomp rescue "(none)" if mailname == MAINTAINER_MAILNAME sh %{scp -r pkg/*#{PKG_VERSION}* #{SCP_TARGET}} else warn "The `upload' target is only meant for the maintainer." end end event-loop-0.3/README0000644000175000017500000006117711574172561013367 0ustar gwolfgwolf------------------------------------------------------------ This file was automatically generated from `README.utf-8'. ------------------------------------------------------------ README for event-loop 0.3 Copyright (C) 2005, 2006 Daniel Brockman Author: Daniel Brockman Written: August 19, 2005 Updated: October 19, 2006 This document describes a simple signal system and an event loop that uses said signal system. Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.2 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. You should have received a copy of the license along with this document; if not, write to the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. The next screenful of paragraphs is mostly side-chatter. If you scroll down a bit, you'll find a table of contents. If you have any questions, comments, requests, bug reports, or patches, please send them to me at . I have a Darcs repository at this URL, so you can easily grab a copy of the latest version: $ darcs get http://www.brockman.se/software/ruby-event-loop/ If you make any changes, you can send them back to me: $ darcs record $ darcs send You can also reach me on IRC as `dbrock' on Freenode. But I'll say that a couple more times below. This software originated in a Direct Connect client codenamed Refusde, but now stands on its own. Thanks to Tilman Sauerbeck for prompting me to wrap these files up in a self-contained package, and then demanding documentation. Without him, this file wouldn't exist. :-) Tilman also helped by creating a RubyGems specification, and by translating the Makefile into a Rakefile. To counteract confusion, I'll say a few words about version numbers before I begin. Prior to version 0.1, this package used to have version numbers like 0.0.20051116 --- a kind of self-inflicted FUD. Now that it's been used by at least a couple of people other than myself, I've decided to switch to a more conventional line of version numbers: 0.1, 0.2, ..., 0.10, and so on. Should a smart person read the code today and come across some deeply embedded idiocy, I can point to these other people as proof that I'm not the only idiot around here. Then I can proceed to fix the idiocy by completely changing the API, totally breaking compatibility, and if anyone complains I can always tell them they're idiots. Table of Contents ================= This document is getting long enough that I'm starting to have trouble navigating it myself. I probably should convert it into an Info manual. But until that happens, maybe this table of contents will help a little. You will note that there's no section that says anything about the file `better-definers.rb'. I don't have any good excuse for this; maybe I'll write that section some day. In the meantime, the code itself is really easy to follow. * Installation * The Signal System: `signal-system.rb' - Emitting Signals - Receiving Signals * The Event Loop: `event-loop.rb' - Creating Event Loops - Event Sources - IO Events: `io.rb' - Timer Events: `timer.rb' - Running the Event Loop Installation ============ I suppose the recommended way of using this software is to install the gem package. But if you don't want to do that, you could simply copy or symlink the files into your project directory and require whichever ones you need. The files in `lib/event-loop/' must reside in a directory called `event-loop/' for the dependencies to work, so if you are taking anything, take the whole `event-loop/' directory. While `signal-system.rb' depends on `better-definers.rb', and `event-loop.rb' depends on both the aforementioned two, it is quite possible to use `signal-system.rb' without `event-loop.rb', or `better-definers.rb' without either. Indeed, `better-definers.rb' and `signal-system.rb' are two very general-purpose libraries, and you are likely to be able to put them to use for most applications, regardless of whether or not they are based on an event loop. For completeness, `timer.rb' depends on `event-loop.rb', and `event-loop.rb' and `io.rb' depend on each other. The Signal System ================= This package comes with an intra-process signal system. I call these signals "intra-process signals" to distinguish them from Unix signals, which are "inter-process signals". But now that I've made that clear, I'm going to go ahead and refer to intra-process signals as just "signals", and to the intra-process signal system simply as "the signal system". Anyway, the signal system is a generic callback mechanism, similar in spirit to the `observable' library that comes with the standard Ruby distribution. It allows anonymous observers to track the events of signal-emitting objects (thereby helping to decrease coupling). The idea is similar to that of the GObject signal system, and a myriad of others like it. (If you know INTERCAL, signals are to methods as COME FROM is to GO TO.) Emitting Signals ---------------- If you want your objects to be capable of emitting signals, their classes should include the `SignalEmitter' module. That will automatically rig the classes with everything you need to define signals (the `SignalEmitterModule' module). For example, this code class Dog include SignalEmitter define_signal :bark def bark puts "Bark!" signal :bark end end spot = Dog.new spot.on_signal :bark do puts "Hush, Spot!" end 3.times { spot.bark } results in the following output: Bark! Hush, Spot! Bark! Hush, Spot! Bark! Hush, Spot! Receiving Signals ----------------- The true name for the method that connects a block or proc to a signal is `add_signal_handler'. However, that gets awfully long when connecting to lots of signals, so there are a couple of shortcuts. First, there are the `on' and `on_signal' aliases. These synonyms just delegate to `add_signal_handler'. Second, every predefined signal FOO gets a shorthand connector method called `on_FOO'. So in the above example, we could have written this instead: spot = Dog.new spot.on_bark { puts "Hush, Spot!" } The `on_FOO' variant is nice for connecting to signals whose names are known statically. Otherwise, `on_signal' is usually preferable; these other cases are typically not common enough to justify the short form `on'. Connecting signal handlers is a breeze, but *disconnecting* them is actually a minor pain in the ass. Without having an explicit reference to the handler, you cannot identify it, which means that you cannot tell `remove_signal_handler' what handler you want to remove. An example of the obvious but painful solution follows: spot = Dog.new @bark_handler = lambda { puts "Hush, Spot!" } spot.on_bark &@bark_handler # ... spot.remove_signal_handler @bark_handler The `SignalObserver' module provides a slightly better solution: class DogOwner include SignalObserver def initialize(dog=nil) @dog = dog || Dog.new("Spot") observe_signal @dog, :bark do puts "Shut up, #{@dog.name}" end end def stop_yelling ignore_signal @dog, :bark end end But `SignalObserver' has a few caveats that make it insufficient as a general solution (see the source). In the future, I'd like to provide a more powerful mechanism for connecting to signals. Perhaps involving handler tags. If you want this feature, nagging me about it on IRC (I'm `dbrock' on Freenode) or via e-mail usually works best. The Event Loop ============== This section explains how IO multiplexing works in general (albeit briefly and not very in-depth), and specifically the issues relevant for Ruby applications. You may safely skip it if you (a) already know this subject, or (b) don't care. Plain ol' blocking IO works well when you're reading from just a single file descriptor. But when you're interested in a whole bunch of FDs, you can't wait for any single one of them to become readable or writable, because then you'll inevitably miss that happening to the other ones. Instead, you need a multiplexer that can wait for them *all at once*. There are a handful of low-level multiplexing primitives: `select', `poll', `epoll', `/dev/poll', and `kqueue'. In addition, there are portable low-level wrapper libraries such as libevent, which can use any of those primitives. The event loop in this package uses the standard `select' wrapper shipped with Ruby, `IO::select'. But in the future, I'd like to use libevent instead, because that'd be cooler. Most applications use a higher-level abstraction built on top of the low-level multiplexer, usually called a `main loop', an `event loop', or an `event source'. There are also libraries such as liboop, which generalizes the event source and event sink concepts, so that components (event sinks) written against liboop become event-source-agnostic. Actually, the combination of blocking IO and Ruby's green threads works well in most cases where you would normally use an event loop. When you call `IO#read' on an empty file descriptor, for instance, Ruby suspends that thread until its internal event loop, known as the scheduler (currently based on `select'), determines that the file descriptor has become readable. In particular, Ruby never calls the low-level `read' function unless it knows that it will not block (because `select' said it wouldn't, but see below). There are several reasons why you would use an event loop such as the one implemented by this library instead of not-so-plain ol' blocking IO with Ruby's green threads. First of all, you may consider the event loop API more pleasant than Ruby's threads and not-quite-blocking IO. Otherwise, don't listen to me; go on using the latter. :-) Blocking IO can occasionally cause unexpected problems. For example, in some cases a blocking read *can* block even though select said that the file descriptor was readable. This problem may be rare (it can happen, for instance, when the checksum of a piece of data fails to match the payload), but the bottom line is that non-blocking IO is safer. Perhaps most importantly, while Ruby's threads are green, they are still effectively preemptively scheduled, with all the implications thereof --- in a word, synchronization hell. By contrast, event handlers are executed in a strictly sequential manner; an event loop will never run two event handlers simultaneously. (Though, of course, all bets are off if you run multiple event loops in separate threads.) Creating Event Loops -------------------- You create a new event loop by calling `EventLoop.new'. However, if you only need one --- which is likely --- you can get it for free by reading from `EventLoop.default'. If you need multiple event loops in separate threads, put them in `EventLoop.current', which will make each of them end up in a thread-local variable. Actually, if you read from `EventLoop.current' before writing to it, it defaults to `EventLoop.default', so you might as well use `EventLoop.current' in single-threaded applications as well. In fact, `EventLoop.current' is so common that it can be shortened to just `EventLoop', if there is no ambiguity. So `EventLoop.run' is short for `EventLoop.current.run'. To dynamically change the "current event loop" for a block of code, it is convenient to use `EventLoop.with_current'. Once you've got the event loop sitting in front of you just waiting to be used, you'll want to add some event sources, and then finally run the loop. So, first things first: What are event sources, and how do you add them to the loop? Event Sources ------------- As for the first question, this package currently supports two kinds of event sources: watchable IO objects and timers. The former kind is used to detect file descriptor activity; the latter is used for wall-clock scheduling of execution. Typically, you don't add event sources to the loop manually. Both watchable IO objects and timers provide convenient ways of making them add themselves to the "current" event loop. For example, to add `@io' to the current event loop, you might write something like the following: @io.monitor_events :readable, :writable That's short for the following more explicit code: EventLoop.current.monitor_io(@io, :readable, :writable) The call to `EventLoop#monitor_io' causes the event loop to wake up --- if it was sleeping in a call to `IO.select' --- and adds `@io' to the event loop's set of monitored IO objects. For the next event loop iteration, `@io' will be included in one or more of the sets passed to `IO.select'. To add the IO object to another event loop, other_loop.monitor_io(@io, :readable, :writable) you can do it like this, EventLoop.with_current(other_loop) do @io.monitor_events :readable, :writable end which is especially convenient when adding multiple objects. For timers, it's even easier: @timer = EventLoop::PeriodicTimer.new(1.second) @timer.start That call to `@timer.start' causes the following to happen: EventLoop.current.monitor_timer(@timer) The call to `EventLoop#monitor_timer' may force the event loop to wake up, depending on the timer readings and the current timeout of the event loop. In any case, the timer is added to the event loop's set of monitored timers. But there's a caveat. This will not work as expected: EventLoop.with_current(other_loop) { @timer.start } That's because timer objects decide in advance which event loop they are going to use. Once initialized, timer objects no longer care about the value of the current event loop. Hence, this code starts a timer in a different event loop: @timer = EventLoop.with_current(other_loop) do EventLoop::PeriodicTimer.new(1.second) end @timer.start Here is another way of writing it: @timer = EventLoop::PeriodicTimer.new \ 1.second, :event_loop => other_loop @timer.start In addition, there are quite a few convenient short forms. For example, you can write things like this: 3.seconds.from_now { puts "Boo!" } Read on, because the next two sections describe with better examples and in more detail how IO and timer events work. [Actually, there are no examples of using timers at all. But it would be nice to have some under "Timer Events".] IO Events --------- In Ruby, file descriptors are instances of the class IO. Before you can use one of these with the event loop, you need to extend it with the module `EventLoop::Watchable'. That module defines two signals, `readable' and `writable', and a pair of methods for activating and deactivating them. When you want to start receiving `readable' signals, for instance, you call `io.monitor_event :readable'. This makes the current event loop monitor `io' for readability, and emit the `readable' signal on it when the condition occurs. require "socket" def initialize (host, port) @socket = TCPSocket.new(host, port) @socket.extend EventLoop::Watchable @socket.will_block = false @socket.on_readable { perform_read } end def start_listening @socket.monitor_event :readable end The `will_block?' property is provided by this package as a convenient way of setting up non-blocking IO streams. (See `lib/event-loop/io.rb', circa line 81.) The method that actually performs the reading will probably look more or less like so: def perform_read process_data @socket.sysread(BUFFER_SIZE, @buffer) rescue EOFError ... rescue Errno::ECONNRESET ... rescue ... end If you don't want to receive any more `readable' signals, you just call `io.ignore_event :readable'. def stop_listening @socket.ignore_event :readable end The "current event loop" is just `EventLoop.current'. To make another event loop (say `other_loop') monitor or ignore an IO event, either call `other_loop.monitor_io' or `other_loop.ignore_io' directly, other_loop.monitor_io(io, :readable) other_loop.ignore_io(io, :writable) or use the `EventLoop.with_current' form, EventLoop.with_current(other_loop) do io.monitor_event :readable io.ignore_event :writable end which implements "dynamic scoping" of `EventLoop.current'. If you simply want readable signals to be emitted whenever there are handlers connected to the `readable' signal (and likewise for `writable'), without having to mess around with `monitor_event' and `ignore_event', you can extend the IO object with the `EventLoop::Watchable::Automatic' module instead of `EventLoop::Watchable'. The `EventLoop::Watchable::Automatic' module sets it up so that when you connect a handler to either the `readable' or the `writable' signal, the current event loop begins monitoring the IO object for the corresponding condition, and, inversely, when you remove the last handler, it tells the event loop to stop monitoring the condition. Because this is so often useful, you don't even have to extend the IO object yourself. Stub implementations of the `on_readable' and `on_writable' methods are provided, which automatically bootstrap the IO by extending it with the `EventLoop::Watchable::Automatic' module when invoked. @socket = TCPSocket.new(host, port) @socket.will_block = false # By invoking the stub `on_readable' method, # we implicitly extend the IO object with the # module `EventLoop::Watchable::Automatic'. # # That module hooks into the signal system and # reacts when we start watching the `readable' # signal by starting to monitor that event. @socket.on_readable { perform_read } Note that once an IO object has been extended with the `EventLoop::Watchable::Automatic' module, there is currently no way to make it non-automatic (Ruby does not yet allow you to un-extend an object with a module). So if you don't want the automatic behavior, you *have* to manually extend the object with the `EventLoop::Watchable' module before calling either of the `or_readable' and `on_writable' methods. There is actually a third signal: `exceptional', which is emitted when `select' reports that the file descriptor is in an "exceptional state". You probably don't need to worry about this (and if you do, you'll probably know it already). But in case you're wondering, I think you can use it to watch for out-of-band data coming through a socket, provided you've set the right socket options. I also believe you can use it to determine that a non-blocking connection attempt has failed. (When such an attempt succeeds, a writability event is fired for the socket.) But that doesn't matter, because you can't do non-blocking connects in Ruby. :-) Timer Events ------------ If you need to do something after a given amount of wall-clock time has passed, just do the following: 1. Create an `EventLoop::SporadicTimer', passing the timeout (in seconds) to the constructor. 2. Connect to its `alarm' signal (using `on_alarm'). 3. Start the timer (using `start'). Sporadic timers only sound their alarm once, and then stop. If you want to do something periodically, like every second, use `EventLoop::PeriodicTimer' instead. You can start a sporadic timer as many times as you want, but it will still stop itself every time it goes off. Periodic timers must be stopped explicitly (using `stop'), or they will keep going off as long as the event loop runs. You can get the effect of an "idle function" by creating an periodic timer with a zero-second interval, meaning its alarm will sound as often as possible. If you pass a block to a timer constructor, then that block will become the timer's canonical "alarm handler", which is just a signal handler for the `alarm' signal, except that you can easily replace it, using `replace_alarm_handler'. Another way to replace the alarm handler is to pass a block to the `start' method or to the `restart' method, which will just cause `replace_alarm_handler' to be called first. There are a number of short forms for creating a timer and setting its alarm handler. The following statements are pairwise equivalent: sporadic_timer = EventLoop.after(3.seconds) do ... end sporadic_timer = 3.seconds.from_now { ... } periodic_timer = EventLoop.every(3.seconds) do ... end periodic_timer = 3.seconds.from_now_and_repeat { ... } sporadic_timer = other_loop.after(3.seconds) do ... end EventLoop.with_current(other_loop) do sporadic_timer = 3.seconds.from_now { ... } end periodic_timer = other_loop.every(3.seconds) do ... end EventLoop.with_current(other_loop) do periodic_timer = 3.seconds.from_now_and_repeat { ... } end idle_function_timer = EventLoop.every(0) { ... } idle_function_timer = EventLoop.repeat { ... } one_shot_idle_function_timer = EventLoop.after(0) { ... } one_shot_idle_function_timer = EventLoop.later { ... } All of the above forms automatically start the timer. When a timer is started, the event loop associated with the timer is notified and its timeout value updated accordingly. (Unlike `Watchable#monitor_event', the `Timer#start' method does not depend on the value of `EventLoop.current'.) By passing `:event_loop => foo' to the timer constructor, you can specify which event loop the timer should use; otherwise, the "current event loop" (`EventLoop.current') will be used as a default. You can ask a timer for the amount of time left by invoking its `time_left' method. When called from an alarm handler, it will typically return a negative value, representing the amount of time passed since the alarm was supposed to sound. However, if you specify `:tolerance => 0.1' when creating the timer, you are saying it's okay for the alarm to sound one-tenth of a second too early. In that case, `time_left' can return a positive value even when called from within an alarm handler, indicating the alarm sounded too early. The next section explains why you should set the tolerance higher than zero (it is currently 0.001 by default). Timer Tolerances ................ It is useful for timers to have some amount of tolerance because the timeout specified to `select' is an upper bound. This means that the process will usually wake up slightly earlier than expected. For example, if you start a timer set for two seconds and then enter an event loop iteration, the call to `select' is likely to return in 1.99 seconds. If your timer's tolerance is set to zero, that means that the alarm must not be sounded yet, and the event loop is forced to perform an extra iteration with a 0.01 timeout. If, on the other hand, the tolerance of the timer is set to at least 0.01 seconds, then that means it's okay for the alarm to sound slightly too early; in this case, the need for an extra iteration can be avoided. Currently, the error made by a tolerant timer during one iteration is not compensated for during the next iteration. Combined with the fact that a tolerant timer will usually sound too early rather than too late (because the kernel tries hard not to wake the process too late), this means that the more tolerant your timer is, the more frequently it will sound. In other words, the error accumulates. This should not be a problem in most cases, simply because of the fact that people do not in most cases use Ruby for applications that need this kind of precision (the default timer tolerance is one millisecond). However, if you would like to see this problem addressed, please contact me. Running the Event Loop ---------------------- To run the current event loop, just call `EventLoop.run'. That'll block until an event handler says `EventLoop.quit'. One typical event loop application looks like this: ... @socket.on_writable { ... } @socket.on_readable do ... if something_or_other EventLoop.quit end end ... @timer.start ... EventLoop.run While the event loop is running, everything that the application does takes place in event handlers. If you want more control, you can run a single iteration of the event loop by calling `EventLoop.iterate', which takes an optional argument specifying (in seconds) the upper bound on the amount of time to block. The default value (`nil') means infinity; it causes the upper bound to be the amount of time left before the next timer is due. If you're not using timers, `EventLoop.iterate' without an argument blocks until the first interesting IO event occurs. That should be all you need to know to use this event loop. If it turns out not to be, please bug me on IRC (as I said, I'm `dbrock' on Freenode) or send me an e-mail. The source shouldn't be particularly hard to understand either. Thanks for your interest, and happy hacking! --- Daniel Brockman ## Local Variables: ## coding: utf-8 ## time-stamp-format: "%:b %:d, %:y" ## time-stamp-start: "Updated: " ## time-stamp-end: "$" ## End: event-loop-0.3/VERSION0000644000175000017500000000000411574172561013535 0ustar gwolfgwolf0.3 event-loop-0.3/README.utf-80000644000175000017500000006220711574172561014324 0ustar gwolfgwolfREADME for event-loop 0.3 Copyright © 2005, 2006 Daniel Brockman Author: Daniel Brockman Written: August 19, 2005 Updated: October 19, 2006 This document describes a simple signal system and an event loop that uses said signal system. Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.2 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. You should have received a copy of the license along with this document; if not, write to the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. The next screenful of paragraphs is mostly side-chatter. If you scroll down a bit, you’ll find a table of contents. If you have any questions, comments, requests, bug reports, or patches, please send them to me at . I have a Darcs repository at this URL, so you can easily grab a copy of the latest version: $ darcs get http://www.brockman.se/software/ruby-event-loop/ If you make any changes, you can send them back to me: $ darcs record $ darcs send You can also reach me on IRC as ‘dbrock’ on Freenode. But I’ll say that a couple more times below. This software originated in a Direct Connect client codenamed Refusde, but now stands on its own. Thanks to Tilman Sauerbeck for prompting me to wrap these files up in a self-contained package, and then demanding documentation. Without him, this file wouldn’t exist. :-) Tilman also helped by creating a RubyGems specification, and by translating the Makefile into a Rakefile. To counteract confusion, I’ll say a few words about version numbers before I begin. Prior to version 0.1, this package used to have version numbers like 0.0.20051116 — a kind of self-inflicted FUD. Now that it’s been used by at least a couple of people other than myself, I’ve decided to switch to a more conventional line of version numbers: 0.1, 0.2, ..., 0.10, and so on. Should a smart person read the code today and come across some deeply embedded idiocy, I can point to these other people as proof that I’m not the only idiot around here. Then I can proceed to fix the idiocy by completely changing the API, totally breaking compatibility, and if anyone complains I can always tell them they’re idiots. Table of Contents ================= This document is getting long enough that I’m starting to have trouble navigating it myself. I probably should convert it into an Info manual. But until that happens, maybe this table of contents will help a little. You will note that there’s no section that says anything about the file ‘better-definers.rb’. I don’t have any good excuse for this; maybe I’ll write that section some day. In the meantime, the code itself is really easy to follow. * Installation * The Signal System: ‘signal-system.rb’ - Emitting Signals - Receiving Signals * The Event Loop: ‘event-loop.rb’ - Creating Event Loops - Event Sources - IO Events: ‘io.rb’ - Timer Events: ‘timer.rb’ - Running the Event Loop Installation ============ I suppose the recommended way of using this software is to install the gem package. But if you don’t want to do that, you could simply copy or symlink the files into your project directory and require whichever ones you need. The files in ‘lib/event-loop/’ must reside in a directory called ‘event-loop/’ for the dependencies to work, so if you are taking anything, take the whole ‘event-loop/’ directory. While ‘signal-system.rb’ depends on ‘better-definers.rb’, and ‘event-loop.rb’ depends on both the aforementioned two, it is quite possible to use ‘signal-system.rb’ without ‘event-loop.rb’, or ‘better-definers.rb’ without either. Indeed, ‘better-definers.rb’ and ‘signal-system.rb’ are two very general-purpose libraries, and you are likely to be able to put them to use for most applications, regardless of whether or not they are based on an event loop. For completeness, ‘timer.rb’ depends on ‘event-loop.rb’, and ‘event-loop.rb’ and ‘io.rb’ depend on each other. The Signal System ================= This package comes with an intra-process signal system. I call these signals “intra-process signals” to distinguish them from Unix signals, which are “inter-process signals”. But now that I’ve made that clear, I’m going to go ahead and refer to intra-process signals as just “signals”, and to the intra-process signal system simply as “the signal system”. Anyway, the signal system is a generic callback mechanism, similar in spirit to the ‘observable’ library that comes with the standard Ruby distribution. It allows anonymous observers to track the events of signal-emitting objects (thereby helping to decrease coupling). The idea is similar to that of the GObject signal system, and a myriad of others like it. (If you know INTERCAL, signals are to methods as COME FROM is to GO TO.) Emitting Signals ---------------- If you want your objects to be capable of emitting signals, their classes should include the ‘SignalEmitter’ module. That will automatically rig the classes with everything you need to define signals (the ‘SignalEmitterModule’ module). For example, this code class Dog include SignalEmitter define_signal :bark def bark puts "Bark!" signal :bark end end spot = Dog.new spot.on_signal :bark do puts "Hush, Spot!" end 3.times { spot.bark } results in the following output: Bark! Hush, Spot! Bark! Hush, Spot! Bark! Hush, Spot! Receiving Signals ----------------- The true name for the method that connects a block or proc to a signal is ‘add_signal_handler’. However, that gets awfully long when connecting to lots of signals, so there are a couple of shortcuts. First, there are the ‘on’ and ‘on_signal’ aliases. These synonyms just delegate to ‘add_signal_handler’. Second, every predefined signal FOO gets a shorthand connector method called ‘on_FOO’. So in the above example, we could have written this instead: spot = Dog.new spot.on_bark { puts "Hush, Spot!" } The ‘on_FOO’ variant is nice for connecting to signals whose names are known statically. Otherwise, ‘on_signal’ is usually preferable; these other cases are typically not common enough to justify the short form ‘on’. Connecting signal handlers is a breeze, but *disconnecting* them is actually a minor pain in the ass. Without having an explicit reference to the handler, you cannot identify it, which means that you cannot tell ‘remove_signal_handler’ what handler you want to remove. An example of the obvious but painful solution follows: spot = Dog.new @bark_handler = lambda { puts "Hush, Spot!" } spot.on_bark &@bark_handler # ... spot.remove_signal_handler @bark_handler The ‘SignalObserver’ module provides a slightly better solution: class DogOwner include SignalObserver def initialize(dog=nil) @dog = dog || Dog.new("Spot") observe_signal @dog, :bark do puts "Shut up, #{@dog.name}" end end def stop_yelling ignore_signal @dog, :bark end end But ‘SignalObserver’ has a few caveats that make it insufficient as a general solution (see the source). In the future, I’d like to provide a more powerful mechanism for connecting to signals. Perhaps involving handler tags. If you want this feature, nagging me about it on IRC (I’m ‘dbrock’ on Freenode) or via e-mail usually works best. The Event Loop ============== This section explains how IO multiplexing works in general (albeit briefly and not very in-depth), and specifically the issues relevant for Ruby applications. You may safely skip it if you (a) already know this subject, or (b) don’t care. Plain ol’ blocking IO works well when you’re reading from just a single file descriptor. But when you’re interested in a whole bunch of FDs, you can’t wait for any single one of them to become readable or writable, because then you’ll inevitably miss that happening to the other ones. Instead, you need a multiplexer that can wait for them *all at once*. There are a handful of low-level multiplexing primitives: ‘select’, ‘poll’, ‘epoll’, ‘/dev/poll’, and ‘kqueue’. In addition, there are portable low-level wrapper libraries such as libevent, which can use any of those primitives. The event loop in this package uses the standard ‘select’ wrapper shipped with Ruby, ‘IO::select’. But in the future, I’d like to use libevent instead, because that’d be cooler. Most applications use a higher-level abstraction built on top of the low-level multiplexer, usually called a ‘main loop’, an ‘event loop’, or an ‘event source’. There are also libraries such as liboop, which generalizes the event source and event sink concepts, so that components (event sinks) written against liboop become event-source-agnostic. Actually, the combination of blocking IO and Ruby’s green threads works well in most cases where you would normally use an event loop. When you call ‘IO#read’ on an empty file descriptor, for instance, Ruby suspends that thread until its internal event loop, known as the scheduler (currently based on ‘select’), determines that the file descriptor has become readable. In particular, Ruby never calls the low-level ‘read’ function unless it knows that it will not block (because ‘select’ said it wouldn’t, but see below). There are several reasons why you would use an event loop such as the one implemented by this library instead of not-so-plain ol’ blocking IO with Ruby’s green threads. First of all, you may consider the event loop API more pleasant than Ruby’s threads and not-quite-blocking IO. Otherwise, don’t listen to me; go on using the latter. :-) Blocking IO can occasionally cause unexpected problems. For example, in some cases a blocking read *can* block even though select said that the file descriptor was readable. This problem may be rare (it can happen, for instance, when the checksum of a piece of data fails to match the payload), but the bottom line is that non-blocking IO is safer. Perhaps most importantly, while Ruby’s threads are green, they are still effectively preemptively scheduled, with all the implications thereof — in a word, synchronization hell. By contrast, event handlers are executed in a strictly sequential manner; an event loop will never run two event handlers simultaneously. (Though, of course, all bets are off if you run multiple event loops in separate threads.) Creating Event Loops -------------------- You create a new event loop by calling ‘EventLoop.new’. However, if you only need one — which is likely — you can get it for free by reading from ‘EventLoop.default’. If you need multiple event loops in separate threads, put them in ‘EventLoop.current’, which will make each of them end up in a thread-local variable. Actually, if you read from ‘EventLoop.current’ before writing to it, it defaults to ‘EventLoop.default’, so you might as well use ‘EventLoop.current’ in single-threaded applications as well. In fact, ‘EventLoop.current’ is so common that it can be shortened to just ‘EventLoop’, if there is no ambiguity. So ‘EventLoop.run’ is short for ‘EventLoop.current.run’. To dynamically change the “current event loop” for a block of code, it is convenient to use ‘EventLoop.with_current’. Once you’ve got the event loop sitting in front of you just waiting to be used, you’ll want to add some event sources, and then finally run the loop. So, first things first: What are event sources, and how do you add them to the loop? Event Sources ------------- As for the first question, this package currently supports two kinds of event sources: watchable IO objects and timers. The former kind is used to detect file descriptor activity; the latter is used for wall-clock scheduling of execution. Typically, you don’t add event sources to the loop manually. Both watchable IO objects and timers provide convenient ways of making them add themselves to the “current” event loop. For example, to add ‘@io’ to the current event loop, you might write something like the following: @io.monitor_events :readable, :writable That’s short for the following more explicit code: EventLoop.current.monitor_io(@io, :readable, :writable) The call to ‘EventLoop#monitor_io’ causes the event loop to wake up — if it was sleeping in a call to ‘IO.select’ — and adds ‘@io’ to the event loop’s set of monitored IO objects. For the next event loop iteration, ‘@io’ will be included in one or more of the sets passed to ‘IO.select’. To add the IO object to another event loop, other_loop.monitor_io(@io, :readable, :writable) you can do it like this, EventLoop.with_current(other_loop) do @io.monitor_events :readable, :writable end which is especially convenient when adding multiple objects. For timers, it’s even easier: @timer = EventLoop::PeriodicTimer.new(1.second) @timer.start That call to ‘@timer.start’ causes the following to happen: EventLoop.current.monitor_timer(@timer) The call to ‘EventLoop#monitor_timer’ may force the event loop to wake up, depending on the timer readings and the current timeout of the event loop. In any case, the timer is added to the event loop’s set of monitored timers. But there’s a caveat. This will not work as expected: EventLoop.with_current(other_loop) { @timer.start } That’s because timer objects decide in advance which event loop they are going to use. Once initialized, timer objects no longer care about the value of the current event loop. Hence, this code starts a timer in a different event loop: @timer = EventLoop.with_current(other_loop) do EventLoop::PeriodicTimer.new(1.second) end @timer.start Here is another way of writing it: @timer = EventLoop::PeriodicTimer.new \ 1.second, :event_loop => other_loop @timer.start In addition, there are quite a few convenient short forms. For example, you can write things like this: 3.seconds.from_now { puts "Boo!" } Read on, because the next two sections describe with better examples and in more detail how IO and timer events work. [Actually, there are no examples of using timers at all. But it would be nice to have some under “Timer Events”.] IO Events --------- In Ruby, file descriptors are instances of the class IO. Before you can use one of these with the event loop, you need to extend it with the module ‘EventLoop::Watchable’. That module defines two signals, ‘readable’ and ‘writable’, and a pair of methods for activating and deactivating them. When you want to start receiving ‘readable’ signals, for instance, you call ‘io.monitor_event :readable’. This makes the current event loop monitor ‘io’ for readability, and emit the ‘readable’ signal on it when the condition occurs. require "socket" def initialize (host, port) @socket = TCPSocket.new(host, port) @socket.extend EventLoop::Watchable @socket.will_block = false @socket.on_readable { perform_read } end def start_listening @socket.monitor_event :readable end The ‘will_block?’ property is provided by this package as a convenient way of setting up non-blocking IO streams. (See ‘lib/event-loop/io.rb’, circa line 81.) The method that actually performs the reading will probably look more or less like so: def perform_read process_data @socket.sysread(BUFFER_SIZE, @buffer) rescue EOFError ... rescue Errno::ECONNRESET ... rescue ... end If you don’t want to receive any more ‘readable’ signals, you just call ‘io.ignore_event :readable’. def stop_listening @socket.ignore_event :readable end The “current event loop” is just ‘EventLoop.current’. To make another event loop (say ‘other_loop’) monitor or ignore an IO event, either call ‘other_loop.monitor_io’ or ‘other_loop.ignore_io’ directly, other_loop.monitor_io(io, :readable) other_loop.ignore_io(io, :writable) or use the ‘EventLoop.with_current’ form, EventLoop.with_current(other_loop) do io.monitor_event :readable io.ignore_event :writable end which implements “dynamic scoping” of ‘EventLoop.current’. If you simply want readable signals to be emitted whenever there are handlers connected to the ‘readable’ signal (and likewise for ‘writable’), without having to mess around with ‘monitor_event’ and ‘ignore_event’, you can extend the IO object with the ‘EventLoop::Watchable::Automatic’ module instead of ‘EventLoop::Watchable’. The ‘EventLoop::Watchable::Automatic’ module sets it up so that when you connect a handler to either the ‘readable’ or the ‘writable’ signal, the current event loop begins monitoring the IO object for the corresponding condition, and, inversely, when you remove the last handler, it tells the event loop to stop monitoring the condition. Because this is so often useful, you don’t even have to extend the IO object yourself. Stub implementations of the ‘on_readable’ and ‘on_writable’ methods are provided, which automatically bootstrap the IO by extending it with the ‘EventLoop::Watchable::Automatic’ module when invoked. @socket = TCPSocket.new(host, port) @socket.will_block = false # By invoking the stub ‘on_readable’ method, # we implicitly extend the IO object with the # module ‘EventLoop::Watchable::Automatic’. # # That module hooks into the signal system and # reacts when we start watching the ‘readable’ # signal by starting to monitor that event. @socket.on_readable { perform_read } Note that once an IO object has been extended with the ‘EventLoop::Watchable::Automatic’ module, there is currently no way to make it non-automatic (Ruby does not yet allow you to un-extend an object with a module). So if you don’t want the automatic behavior, you *have* to manually extend the object with the ‘EventLoop::Watchable’ module before calling either of the ‘or_readable’ and ‘on_writable’ methods. There is actually a third signal: ‘exceptional’, which is emitted when ‘select’ reports that the file descriptor is in an “exceptional state”. You probably don’t need to worry about this (and if you do, you’ll probably know it already). But in case you’re wondering, I think you can use it to watch for out-of-band data coming through a socket, provided you’ve set the right socket options. I also believe you can use it to determine that a non-blocking connection attempt has failed. (When such an attempt succeeds, a writability event is fired for the socket.) But that doesn’t matter, because you can’t do non-blocking connects in Ruby. :-) Timer Events ------------ If you need to do something after a given amount of wall-clock time has passed, just do the following: 1. Create an ‘EventLoop::SporadicTimer’, passing the timeout (in seconds) to the constructor. 2. Connect to its ‘alarm’ signal (using ‘on_alarm’). 3. Start the timer (using ‘start’). Sporadic timers only sound their alarm once, and then stop. If you want to do something periodically, like every second, use ‘EventLoop::PeriodicTimer’ instead. You can start a sporadic timer as many times as you want, but it will still stop itself every time it goes off. Periodic timers must be stopped explicitly (using ‘stop’), or they will keep going off as long as the event loop runs. You can get the effect of an “idle function” by creating an periodic timer with a zero-second interval, meaning its alarm will sound as often as possible. If you pass a block to a timer constructor, then that block will become the timer’s canonical “alarm handler”, which is just a signal handler for the ‘alarm’ signal, except that you can easily replace it, using ‘replace_alarm_handler’. Another way to replace the alarm handler is to pass a block to the ‘start’ method or to the ‘restart’ method, which will just cause ‘replace_alarm_handler’ to be called first. There are a number of short forms for creating a timer and setting its alarm handler. The following statements are pairwise equivalent: sporadic_timer = EventLoop.after(3.seconds) do ... end sporadic_timer = 3.seconds.from_now { ... } periodic_timer = EventLoop.every(3.seconds) do ... end periodic_timer = 3.seconds.from_now_and_repeat { ... } sporadic_timer = other_loop.after(3.seconds) do ... end EventLoop.with_current(other_loop) do sporadic_timer = 3.seconds.from_now { ... } end periodic_timer = other_loop.every(3.seconds) do ... end EventLoop.with_current(other_loop) do periodic_timer = 3.seconds.from_now_and_repeat { ... } end idle_function_timer = EventLoop.every(0) { ... } idle_function_timer = EventLoop.repeat { ... } one_shot_idle_function_timer = EventLoop.after(0) { ... } one_shot_idle_function_timer = EventLoop.later { ... } All of the above forms automatically start the timer. When a timer is started, the event loop associated with the timer is notified and its timeout value updated accordingly. (Unlike ‘Watchable#monitor_event’, the ‘Timer#start’ method does not depend on the value of ‘EventLoop.current’.) By passing ‘:event_loop => foo’ to the timer constructor, you can specify which event loop the timer should use; otherwise, the “current event loop” (‘EventLoop.current’) will be used as a default. You can ask a timer for the amount of time left by invoking its ‘time_left’ method. When called from an alarm handler, it will typically return a negative value, representing the amount of time passed since the alarm was supposed to sound. However, if you specify ‘:tolerance => 0.1’ when creating the timer, you are saying it’s okay for the alarm to sound one-tenth of a second too early. In that case, ‘time_left’ can return a positive value even when called from within an alarm handler, indicating the alarm sounded too early. The next section explains why you should set the tolerance higher than zero (it is currently 0.001 by default). Timer Tolerances ................ It is useful for timers to have some amount of tolerance because the timeout specified to ‘select’ is an upper bound. This means that the process will usually wake up slightly earlier than expected. For example, if you start a timer set for two seconds and then enter an event loop iteration, the call to ‘select’ is likely to return in 1.99 seconds. If your timer’s tolerance is set to zero, that means that the alarm must not be sounded yet, and the event loop is forced to perform an extra iteration with a 0.01 timeout. If, on the other hand, the tolerance of the timer is set to at least 0.01 seconds, then that means it’s okay for the alarm to sound slightly too early; in this case, the need for an extra iteration can be avoided. Currently, the error made by a tolerant timer during one iteration is not compensated for during the next iteration. Combined with the fact that a tolerant timer will usually sound too early rather than too late (because the kernel tries hard not to wake the process too late), this means that the more tolerant your timer is, the more frequently it will sound. In other words, the error accumulates. This should not be a problem in most cases, simply because of the fact that people do not in most cases use Ruby for applications that need this kind of precision (the default timer tolerance is one millisecond). However, if you would like to see this problem addressed, please contact me. Running the Event Loop ---------------------- To run the current event loop, just call ‘EventLoop.run’. That’ll block until an event handler says ‘EventLoop.quit’. One typical event loop application looks like this: ... @socket.on_writable { ... } @socket.on_readable do ... if something_or_other EventLoop.quit end end ... @timer.start ... EventLoop.run While the event loop is running, everything that the application does takes place in event handlers. If you want more control, you can run a single iteration of the event loop by calling ‘EventLoop.iterate’, which takes an optional argument specifying (in seconds) the upper bound on the amount of time to block. The default value (‘nil’) means infinity; it causes the upper bound to be the amount of time left before the next timer is due. If you’re not using timers, ‘EventLoop.iterate’ without an argument blocks until the first interesting IO event occurs. That should be all you need to know to use this event loop. If it turns out not to be, please bug me on IRC (as I said, I’m ‘dbrock’ on Freenode) or send me an e-mail. The source shouldn’t be particularly hard to understand either. Thanks for your interest, and happy hacking! — Daniel Brockman ## Local Variables: ## coding: utf-8 ## time-stamp-format: "%:b %:d, %:y" ## time-stamp-start: "Updated: " ## time-stamp-end: "$" ## End: event-loop-0.3/AUTHORS0000644000175000017500000000011611574172561013541 0ustar gwolfgwolfDaniel Brockman Tilman Sauerbeck event-loop-0.3/setup.rb0000644000175000017500000010655711574172561014176 0ustar gwolfgwolf# # setup.rb # # Copyright (c) 2000-2006 Minero Aoki # # This program is free software. # You can distribute/modify this program under the terms of # the GNU LGPL, Lesser General Public License version 2.1. # unless Enumerable.method_defined?(:map) # Ruby 1.4.6 module Enumerable alias map collect end end unless File.respond_to?(:read) # Ruby 1.6 def File.read(fname) open(fname) {|f| return f.read } end end unless Errno.const_defined?(:ENOTEMPTY) # Windows? module Errno class ENOTEMPTY # We do not raise this exception, implementation is not needed. end end end def File.binread(fname) open(fname, 'rb') {|f| return f.read } end # for corrupted Windows' stat(2) def File.dir?(path) File.directory?((path[-1,1] == '/') ? path : path + '/') end class ConfigTable include Enumerable def initialize(rbconfig) @rbconfig = rbconfig @items = [] @table = {} # options @install_prefix = nil @config_opt = nil @verbose = true @no_harm = false end attr_accessor :install_prefix attr_accessor :config_opt attr_writer :verbose def verbose? @verbose end attr_writer :no_harm def no_harm? @no_harm end def [](key) lookup(key).resolve(self) end def []=(key, val) lookup(key).set val end def names @items.map {|i| i.name } end def each(&block) @items.each(&block) end def key?(name) @table.key?(name) end def lookup(name) @table[name] or setup_rb_error "no such config item: #{name}" end def add(item) @items.push item @table[item.name] = item end def remove(name) item = lookup(name) @items.delete_if {|i| i.name == name } @table.delete_if {|name, i| i.name == name } item end def load_script(path, inst = nil) if File.file?(path) MetaConfigEnvironment.new(self, inst).instance_eval File.read(path), path end end def savefile '.config' end def load_savefile begin File.foreach(savefile()) do |line| k, v = *line.split(/=/, 2) self[k] = v.strip end rescue Errno::ENOENT setup_rb_error $!.message + "\n#{File.basename($0)} config first" end end def save @items.each {|i| i.value } File.open(savefile(), 'w') {|f| @items.each do |i| f.printf "%s=%s\n", i.name, i.value if i.value? and i.value end } end def load_standard_entries standard_entries(@rbconfig).each do |ent| add ent end end def standard_entries(rbconfig) c = rbconfig rubypath = File.join(c['bindir'], c['ruby_install_name'] + c['EXEEXT']) major = c['MAJOR'].to_i minor = c['MINOR'].to_i teeny = c['TEENY'].to_i version = "#{major}.#{minor}" # ruby ver. >= 1.4.4? newpath_p = ((major >= 2) or ((major == 1) and ((minor >= 5) or ((minor == 4) and (teeny >= 4))))) if c['rubylibdir'] # V > 1.6.3 libruby = "#{c['prefix']}/lib/ruby" librubyver = c['rubylibdir'] librubyverarch = c['archdir'] siteruby = c['sitedir'] siterubyver = c['sitelibdir'] siterubyverarch = c['sitearchdir'] elsif newpath_p # 1.4.4 <= V <= 1.6.3 libruby = "#{c['prefix']}/lib/ruby" librubyver = "#{c['prefix']}/lib/ruby/#{version}" librubyverarch = "#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}" siteruby = c['sitedir'] siterubyver = "$siteruby/#{version}" siterubyverarch = "$siterubyver/#{c['arch']}" else # V < 1.4.4 libruby = "#{c['prefix']}/lib/ruby" librubyver = "#{c['prefix']}/lib/ruby/#{version}" librubyverarch = "#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}" siteruby = "#{c['prefix']}/lib/ruby/#{version}/site_ruby" siterubyver = siteruby siterubyverarch = "$siterubyver/#{c['arch']}" end parameterize = lambda {|path| path.sub(/\A#{Regexp.quote(c['prefix'])}/, '$prefix') } if arg = c['configure_args'].split.detect {|arg| /--with-make-prog=/ =~ arg } makeprog = arg.sub(/'/, '').split(/=/, 2)[1] else makeprog = 'make' end [ ExecItem.new('installdirs', 'std/site/home', 'std: install under libruby; site: install under site_ruby; home: install under $HOME')\ {|val, table| case val when 'std' table['rbdir'] = '$librubyver' table['sodir'] = '$librubyverarch' when 'site' table['rbdir'] = '$siterubyver' table['sodir'] = '$siterubyverarch' when 'home' setup_rb_error '$HOME was not set' unless ENV['HOME'] table['prefix'] = ENV['HOME'] table['rbdir'] = '$libdir/ruby' table['sodir'] = '$libdir/ruby' end }, PathItem.new('prefix', 'path', c['prefix'], 'path prefix of target environment'), PathItem.new('bindir', 'path', parameterize.call(c['bindir']), 'the directory for commands'), PathItem.new('libdir', 'path', parameterize.call(c['libdir']), 'the directory for libraries'), PathItem.new('datadir', 'path', parameterize.call(c['datadir']), 'the directory for shared data'), PathItem.new('mandir', 'path', parameterize.call(c['mandir']), 'the directory for man pages'), PathItem.new('sysconfdir', 'path', parameterize.call(c['sysconfdir']), 'the directory for system configuration files'), PathItem.new('localstatedir', 'path', parameterize.call(c['localstatedir']), 'the directory for local state data'), PathItem.new('libruby', 'path', libruby, 'the directory for ruby libraries'), PathItem.new('librubyver', 'path', librubyver, 'the directory for standard ruby libraries'), PathItem.new('librubyverarch', 'path', librubyverarch, 'the directory for standard ruby extensions'), PathItem.new('siteruby', 'path', siteruby, 'the directory for version-independent aux ruby libraries'), PathItem.new('siterubyver', 'path', siterubyver, 'the directory for aux ruby libraries'), PathItem.new('siterubyverarch', 'path', siterubyverarch, 'the directory for aux ruby binaries'), PathItem.new('rbdir', 'path', '$siterubyver', 'the directory for ruby scripts'), PathItem.new('sodir', 'path', '$siterubyverarch', 'the directory for ruby extentions'), PathItem.new('rubypath', 'path', rubypath, 'the path to set to #! line'), ProgramItem.new('rubyprog', 'name', rubypath, 'the ruby program using for installation'), ProgramItem.new('makeprog', 'name', makeprog, 'the make program to compile ruby extentions'), SelectItem.new('shebang', 'all/ruby/never', 'ruby', 'shebang line (#!) editing mode'), BoolItem.new('without-ext', 'yes/no', 'no', 'does not compile/install ruby extentions') ] end private :standard_entries def load_multipackage_entries multipackage_entries().each do |ent| add ent end end def multipackage_entries [ PackageSelectionItem.new('with', 'name,name...', '', 'ALL', 'package names that you want to install'), PackageSelectionItem.new('without', 'name,name...', '', 'NONE', 'package names that you do not want to install') ] end private :multipackage_entries ALIASES = { 'std-ruby' => 'librubyver', 'stdruby' => 'librubyver', 'rubylibdir' => 'librubyver', 'archdir' => 'librubyverarch', 'site-ruby-common' => 'siteruby', # For backward compatibility 'site-ruby' => 'siterubyver', # For backward compatibility 'bin-dir' => 'bindir', 'bin-dir' => 'bindir', 'rb-dir' => 'rbdir', 'so-dir' => 'sodir', 'data-dir' => 'datadir', 'ruby-path' => 'rubypath', 'ruby-prog' => 'rubyprog', 'ruby' => 'rubyprog', 'make-prog' => 'makeprog', 'make' => 'makeprog' } def fixup ALIASES.each do |ali, name| @table[ali] = @table[name] end end def options_re /\A--(#{@table.keys.join('|')})(?:=(.*))?\z/ end def parse_opt(opt) m = options_re().match(opt) or setup_rb_error "config: unknown option #{opt}" m.to_a[1,2] end def dllext @rbconfig['DLEXT'] end def value_config?(name) lookup(name).value? end class Item def initialize(name, template, default, desc) @name = name.freeze @template = template @value = default @default = default @description = desc end attr_reader :name attr_reader :description attr_accessor :default alias help_default default def help_opt "--#{@name}=#{@template}" end def value? true end def value @value end def resolve(table) @value.gsub(%r<\$([^/]+)>) { table[$1] } end def set(val) @value = check(val) end private def check(val) setup_rb_error "config: --#{name} requires argument" unless val val end end class BoolItem < Item def config_type 'bool' end def help_opt "--#{@name}" end private def check(val) return 'yes' unless val case val when /\Ay(es)?\z/i, /\At(rue)?\z/i then 'yes' when /\An(o)?\z/i, /\Af(alse)\z/i then 'no' else setup_rb_error "config: --#{@name} accepts only yes/no for argument" end end end class PathItem < Item def config_type 'path' end private def check(path) setup_rb_error "config: --#{@name} requires argument" unless path path[0,1] == '$' ? path : File.expand_path(path) end end class ProgramItem < Item def config_type 'program' end end class SelectItem < Item def initialize(name, selection, default, desc) super @ok = selection.split('/') end def config_type 'select' end private def check(val) unless @ok.include?(val.strip) setup_rb_error "config: use --#{@name}=#{@template} (#{val})" end val.strip end end class ExecItem < Item def initialize(name, selection, desc, &block) super name, selection, nil, desc @ok = selection.split('/') @action = block end def config_type 'exec' end def value? false end def resolve(table) setup_rb_error "$#{name()} wrongly used as option value" end undef set def evaluate(val, table) v = val.strip.downcase unless @ok.include?(v) setup_rb_error "invalid option --#{@name}=#{val} (use #{@template})" end @action.call v, table end end class PackageSelectionItem < Item def initialize(name, template, default, help_default, desc) super name, template, default, desc @help_default = help_default end attr_reader :help_default def config_type 'package' end private def check(val) unless File.dir?("packages/#{val}") setup_rb_error "config: no such package: #{val}" end val end end class MetaConfigEnvironment def initialize(config, installer) @config = config @installer = installer end def config_names @config.names end def config?(name) @config.key?(name) end def bool_config?(name) @config.lookup(name).config_type == 'bool' end def path_config?(name) @config.lookup(name).config_type == 'path' end def value_config?(name) @config.lookup(name).config_type != 'exec' end def add_config(item) @config.add item end def add_bool_config(name, default, desc) @config.add BoolItem.new(name, 'yes/no', default ? 'yes' : 'no', desc) end def add_path_config(name, default, desc) @config.add PathItem.new(name, 'path', default, desc) end def set_config_default(name, default) @config.lookup(name).default = default end def remove_config(name) @config.remove(name) end # For only multipackage def packages raise '[setup.rb fatal] multi-package metaconfig API packages() called for single-package; contact application package vendor' unless @installer @installer.packages end # For only multipackage def declare_packages(list) raise '[setup.rb fatal] multi-package metaconfig API declare_packages() called for single-package; contact application package vendor' unless @installer @installer.packages = list end end end # class ConfigTable # This module requires: #verbose?, #no_harm? module FileOperations def mkdir_p(dirname, prefix = nil) dirname = prefix + File.expand_path(dirname) if prefix $stderr.puts "mkdir -p #{dirname}" if verbose? return if no_harm? # Does not check '/', it's too abnormal. dirs = File.expand_path(dirname).split(%r<(?=/)>) if /\A[a-z]:\z/i =~ dirs[0] disk = dirs.shift dirs[0] = disk + dirs[0] end dirs.each_index do |idx| path = dirs[0..idx].join('') Dir.mkdir path unless File.dir?(path) end end def rm_f(path) $stderr.puts "rm -f #{path}" if verbose? return if no_harm? force_remove_file path end def rm_rf(path) $stderr.puts "rm -rf #{path}" if verbose? return if no_harm? remove_tree path end def remove_tree(path) if File.symlink?(path) remove_file path elsif File.dir?(path) remove_tree0 path else force_remove_file path end end def remove_tree0(path) Dir.foreach(path) do |ent| next if ent == '.' next if ent == '..' entpath = "#{path}/#{ent}" if File.symlink?(entpath) remove_file entpath elsif File.dir?(entpath) remove_tree0 entpath else force_remove_file entpath end end begin Dir.rmdir path rescue Errno::ENOTEMPTY # directory may not be empty end end def move_file(src, dest) force_remove_file dest begin File.rename src, dest rescue File.open(dest, 'wb') {|f| f.write File.binread(src) } File.chmod File.stat(src).mode, dest File.unlink src end end def force_remove_file(path) begin remove_file path rescue end end def remove_file(path) File.chmod 0777, path File.unlink path end def install(from, dest, mode, prefix = nil) $stderr.puts "install #{from} #{dest}" if verbose? return if no_harm? realdest = prefix ? prefix + File.expand_path(dest) : dest realdest = File.join(realdest, File.basename(from)) if File.dir?(realdest) str = File.binread(from) if diff?(str, realdest) verbose_off { rm_f realdest if File.exist?(realdest) } File.open(realdest, 'wb') {|f| f.write str } File.chmod mode, realdest File.open("#{objdir_root()}/InstalledFiles", 'a') {|f| if prefix f.puts realdest.sub(prefix, '') else f.puts realdest end } end end def diff?(new_content, path) return true unless File.exist?(path) new_content != File.binread(path) end def command(*args) $stderr.puts args.join(' ') if verbose? system(*args) or raise RuntimeError, "system(#{args.map{|a| a.inspect }.join(' ')}) failed" end def ruby(*args) command config('rubyprog'), *args end def make(task = nil) command(*[config('makeprog'), task].compact) end def extdir?(dir) File.exist?("#{dir}/MANIFEST") or File.exist?("#{dir}/extconf.rb") end def files_of(dir) Dir.open(dir) {|d| return d.select {|ent| File.file?("#{dir}/#{ent}") } } end DIR_REJECT = %w( . .. CVS SCCS RCS CVS.adm .svn ) def directories_of(dir) Dir.open(dir) {|d| return d.select {|ent| File.dir?("#{dir}/#{ent}") } - DIR_REJECT } end end # This module requires: #srcdir_root, #objdir_root, #relpath module HookScriptAPI def get_config(key) @config[key] end alias config get_config # obsolete: use metaconfig to change configuration def set_config(key, val) @config[key] = val end # # srcdir/objdir (works only in the package directory) # def curr_srcdir "#{srcdir_root()}/#{relpath()}" end def curr_objdir "#{objdir_root()}/#{relpath()}" end def srcfile(path) "#{curr_srcdir()}/#{path}" end def srcexist?(path) File.exist?(srcfile(path)) end def srcdirectory?(path) File.dir?(srcfile(path)) end def srcfile?(path) File.file?(srcfile(path)) end def srcentries(path = '.') Dir.open("#{curr_srcdir()}/#{path}") {|d| return d.to_a - %w(. ..) } end def srcfiles(path = '.') srcentries(path).select {|fname| File.file?(File.join(curr_srcdir(), path, fname)) } end def srcdirectories(path = '.') srcentries(path).select {|fname| File.dir?(File.join(curr_srcdir(), path, fname)) } end end class ToplevelInstaller Version = '3.4.1' Copyright = 'Copyright (c) 2000-2006 Minero Aoki' TASKS = [ [ 'all', 'do config, setup, then install' ], [ 'config', 'saves your configurations' ], [ 'show', 'shows current configuration' ], [ 'setup', 'compiles ruby extentions and others' ], [ 'install', 'installs files' ], [ 'test', 'run all tests in test/' ], [ 'clean', "does `make clean' for each extention" ], [ 'distclean',"does `make distclean' for each extention" ] ] def ToplevelInstaller.invoke config = ConfigTable.new(load_rbconfig()) config.load_standard_entries config.load_multipackage_entries if multipackage? config.fixup klass = (multipackage?() ? ToplevelInstallerMulti : ToplevelInstaller) klass.new(File.dirname($0), config).invoke end def ToplevelInstaller.multipackage? File.dir?(File.dirname($0) + '/packages') end def ToplevelInstaller.load_rbconfig if arg = ARGV.detect {|arg| /\A--rbconfig=/ =~ arg } ARGV.delete(arg) load File.expand_path(arg.split(/=/, 2)[1]) $".push 'rbconfig.rb' else require 'rbconfig' end ::Config::CONFIG end def initialize(ardir_root, config) @ardir = File.expand_path(ardir_root) @config = config # cache @valid_task_re = nil end def config(key) @config[key] end def inspect "#<#{self.class} #{__id__()}>" end def invoke run_metaconfigs case task = parsearg_global() when nil, 'all' parsearg_config init_installers exec_config exec_setup exec_install else case task when 'config', 'test' ; when 'clean', 'distclean' @config.load_savefile if File.exist?(@config.savefile) else @config.load_savefile end __send__ "parsearg_#{task}" init_installers __send__ "exec_#{task}" end end def run_metaconfigs @config.load_script "#{@ardir}/metaconfig" end def init_installers @installer = Installer.new(@config, @ardir, File.expand_path('.')) end # # Hook Script API bases # def srcdir_root @ardir end def objdir_root '.' end def relpath '.' end # # Option Parsing # def parsearg_global while arg = ARGV.shift case arg when /\A\w+\z/ setup_rb_error "invalid task: #{arg}" unless valid_task?(arg) return arg when '-q', '--quiet' @config.verbose = false when '--verbose' @config.verbose = true when '--help' print_usage $stdout exit 0 when '--version' puts "#{File.basename($0)} version #{Version}" exit 0 when '--copyright' puts Copyright exit 0 else setup_rb_error "unknown global option '#{arg}'" end end nil end def valid_task?(t) valid_task_re() =~ t end def valid_task_re @valid_task_re ||= /\A(?:#{TASKS.map {|task,desc| task }.join('|')})\z/ end def parsearg_no_options unless ARGV.empty? task = caller(0).first.slice(%r<`parsearg_(\w+)'>, 1) setup_rb_error "#{task}: unknown options: #{ARGV.join(' ')}" end end alias parsearg_show parsearg_no_options alias parsearg_setup parsearg_no_options alias parsearg_test parsearg_no_options alias parsearg_clean parsearg_no_options alias parsearg_distclean parsearg_no_options def parsearg_config evalopt = [] set = [] @config.config_opt = [] while i = ARGV.shift if /\A--?\z/ =~ i @config.config_opt = ARGV.dup break end name, value = *@config.parse_opt(i) if @config.value_config?(name) @config[name] = value else evalopt.push [name, value] end set.push name end evalopt.each do |name, value| @config.lookup(name).evaluate value, @config end # Check if configuration is valid set.each do |n| @config[n] if @config.value_config?(n) end end def parsearg_install @config.no_harm = false @config.install_prefix = '' while a = ARGV.shift case a when '--no-harm' @config.no_harm = true when /\A--prefix=/ path = a.split(/=/, 2)[1] path = File.expand_path(path) unless path[0,1] == '/' @config.install_prefix = path else setup_rb_error "install: unknown option #{a}" end end end def print_usage(out) out.puts 'Typical Installation Procedure:' out.puts " $ ruby #{File.basename $0} config" out.puts " $ ruby #{File.basename $0} setup" out.puts " # ruby #{File.basename $0} install (may require root privilege)" out.puts out.puts 'Detailed Usage:' out.puts " ruby #{File.basename $0} " out.puts " ruby #{File.basename $0} [] []" fmt = " %-24s %s\n" out.puts out.puts 'Global options:' out.printf fmt, '-q,--quiet', 'suppress message outputs' out.printf fmt, ' --verbose', 'output messages verbosely' out.printf fmt, ' --help', 'print this message' out.printf fmt, ' --version', 'print version and quit' out.printf fmt, ' --copyright', 'print copyright and quit' out.puts out.puts 'Tasks:' TASKS.each do |name, desc| out.printf fmt, name, desc end fmt = " %-24s %s [%s]\n" out.puts out.puts 'Options for CONFIG or ALL:' @config.each do |item| out.printf fmt, item.help_opt, item.description, item.help_default end out.printf fmt, '--rbconfig=path', 'rbconfig.rb to load',"running ruby's" out.puts out.puts 'Options for INSTALL:' out.printf fmt, '--no-harm', 'only display what to do if given', 'off' out.printf fmt, '--prefix=path', 'install path prefix', '' out.puts end # # Task Handlers # def exec_config @installer.exec_config @config.save # must be final end def exec_setup @installer.exec_setup end def exec_install @installer.exec_install end def exec_test @installer.exec_test end def exec_show @config.each do |i| printf "%-20s %s\n", i.name, i.value if i.value? end end def exec_clean @installer.exec_clean end def exec_distclean @installer.exec_distclean end end # class ToplevelInstaller class ToplevelInstallerMulti < ToplevelInstaller include FileOperations def initialize(ardir_root, config) super @packages = directories_of("#{@ardir}/packages") raise 'no package exists' if @packages.empty? @root_installer = Installer.new(@config, @ardir, File.expand_path('.')) end def run_metaconfigs @config.load_script "#{@ardir}/metaconfig", self @packages.each do |name| @config.load_script "#{@ardir}/packages/#{name}/metaconfig" end end attr_reader :packages def packages=(list) raise 'package list is empty' if list.empty? list.each do |name| raise "directory packages/#{name} does not exist"\ unless File.dir?("#{@ardir}/packages/#{name}") end @packages = list end def init_installers @installers = {} @packages.each do |pack| @installers[pack] = Installer.new(@config, "#{@ardir}/packages/#{pack}", "packages/#{pack}") end with = extract_selection(config('with')) without = extract_selection(config('without')) @selected = @installers.keys.select {|name| (with.empty? or with.include?(name)) \ and not without.include?(name) } end def extract_selection(list) a = list.split(/,/) a.each do |name| setup_rb_error "no such package: #{name}" unless @installers.key?(name) end a end def print_usage(f) super f.puts 'Inluded packages:' f.puts ' ' + @packages.sort.join(' ') f.puts end # # Task Handlers # def exec_config run_hook 'pre-config' each_selected_installers {|inst| inst.exec_config } run_hook 'post-config' @config.save # must be final end def exec_setup run_hook 'pre-setup' each_selected_installers {|inst| inst.exec_setup } run_hook 'post-setup' end def exec_install run_hook 'pre-install' each_selected_installers {|inst| inst.exec_install } run_hook 'post-install' end def exec_test run_hook 'pre-test' each_selected_installers {|inst| inst.exec_test } run_hook 'post-test' end def exec_clean rm_f @config.savefile run_hook 'pre-clean' each_selected_installers {|inst| inst.exec_clean } run_hook 'post-clean' end def exec_distclean rm_f @config.savefile run_hook 'pre-distclean' each_selected_installers {|inst| inst.exec_distclean } run_hook 'post-distclean' end # # lib # def each_selected_installers Dir.mkdir 'packages' unless File.dir?('packages') @selected.each do |pack| $stderr.puts "Processing the package `#{pack}' ..." if verbose? Dir.mkdir "packages/#{pack}" unless File.dir?("packages/#{pack}") Dir.chdir "packages/#{pack}" yield @installers[pack] Dir.chdir '../..' end end def run_hook(id) @root_installer.run_hook id end # module FileOperations requires this def verbose? @config.verbose? end # module FileOperations requires this def no_harm? @config.no_harm? end end # class ToplevelInstallerMulti class Installer FILETYPES = %w( bin lib ext data conf man ) include FileOperations include HookScriptAPI def initialize(config, srcroot, objroot) @config = config @srcdir = File.expand_path(srcroot) @objdir = File.expand_path(objroot) @currdir = '.' end def inspect "#<#{self.class} #{File.basename(@srcdir)}>" end def noop(rel) end # # Hook Script API base methods # def srcdir_root @srcdir end def objdir_root @objdir end def relpath @currdir end # # Config Access # # module FileOperations requires this def verbose? @config.verbose? end # module FileOperations requires this def no_harm? @config.no_harm? end def verbose_off begin save, @config.verbose = @config.verbose?, false yield ensure @config.verbose = save end end # # TASK config # def exec_config exec_task_traverse 'config' end alias config_dir_bin noop alias config_dir_lib noop def config_dir_ext(rel) extconf if extdir?(curr_srcdir()) end alias config_dir_data noop alias config_dir_conf noop alias config_dir_man noop def extconf ruby "#{curr_srcdir()}/extconf.rb", *@config.config_opt end # # TASK setup # def exec_setup exec_task_traverse 'setup' end def setup_dir_bin(rel) files_of(curr_srcdir()).each do |fname| update_shebang_line "#{curr_srcdir()}/#{fname}" end end alias setup_dir_lib noop def setup_dir_ext(rel) make if extdir?(curr_srcdir()) end alias setup_dir_data noop alias setup_dir_conf noop alias setup_dir_man noop def update_shebang_line(path) return if no_harm? return if config('shebang') == 'never' old = Shebang.load(path) if old $stderr.puts "warning: #{path}: Shebang line includes too many args. It is not portable and your program may not work." if old.args.size > 1 new = new_shebang(old) return if new.to_s == old.to_s else return unless config('shebang') == 'all' new = Shebang.new(config('rubypath')) end $stderr.puts "updating shebang: #{File.basename(path)}" if verbose? open_atomic_writer(path) {|output| File.open(path, 'rb') {|f| f.gets if old # discard output.puts new.to_s output.print f.read } } end def new_shebang(old) if /\Aruby/ =~ File.basename(old.cmd) Shebang.new(config('rubypath'), old.args) elsif File.basename(old.cmd) == 'env' and old.args.first == 'ruby' Shebang.new(config('rubypath'), old.args[1..-1]) else return old unless config('shebang') == 'all' Shebang.new(config('rubypath')) end end def open_atomic_writer(path, &block) tmpfile = File.basename(path) + '.tmp' begin File.open(tmpfile, 'wb', &block) File.rename tmpfile, File.basename(path) ensure File.unlink tmpfile if File.exist?(tmpfile) end end class Shebang def Shebang.load(path) line = nil File.open(path) {|f| line = f.gets } return nil unless /\A#!/ =~ line parse(line) end def Shebang.parse(line) cmd, *args = *line.strip.sub(/\A\#!/, '').split(' ') new(cmd, args) end def initialize(cmd, args = []) @cmd = cmd @args = args end attr_reader :cmd attr_reader :args def to_s "#! #{@cmd}" + (@args.empty? ? '' : " #{@args.join(' ')}") end end # # TASK install # def exec_install rm_f 'InstalledFiles' exec_task_traverse 'install' end def install_dir_bin(rel) install_files targetfiles(), "#{config('bindir')}/#{rel}", 0755 end def install_dir_lib(rel) install_files libfiles(), "#{config('rbdir')}/#{rel}", 0644 end def install_dir_ext(rel) return unless extdir?(curr_srcdir()) install_files rubyextentions('.'), "#{config('sodir')}/#{File.dirname(rel)}", 0555 end def install_dir_data(rel) install_files targetfiles(), "#{config('datadir')}/#{rel}", 0644 end def install_dir_conf(rel) # FIXME: should not remove current config files # (rename previous file to .old/.org) install_files targetfiles(), "#{config('sysconfdir')}/#{rel}", 0644 end def install_dir_man(rel) install_files targetfiles(), "#{config('mandir')}/#{rel}", 0644 end def install_files(list, dest, mode) mkdir_p dest, @config.install_prefix list.each do |fname| install fname, dest, mode, @config.install_prefix end end def libfiles glob_reject(%w(*.y *.output), targetfiles()) end def rubyextentions(dir) ents = glob_select("*.#{@config.dllext}", targetfiles()) if ents.empty? setup_rb_error "no ruby extention exists: 'ruby #{$0} setup' first" end ents end def targetfiles mapdir(existfiles() - hookfiles()) end def mapdir(ents) ents.map {|ent| if File.exist?(ent) then ent # objdir else "#{curr_srcdir()}/#{ent}" # srcdir end } end # picked up many entries from cvs-1.11.1/src/ignore.c JUNK_FILES = %w( core RCSLOG tags TAGS .make.state .nse_depinfo #* .#* cvslog.* ,* .del-* *.olb *~ *.old *.bak *.BAK *.orig *.rej _$* *$ *.org *.in .* ) def existfiles glob_reject(JUNK_FILES, (files_of(curr_srcdir()) | files_of('.'))) end def hookfiles %w( pre-%s post-%s pre-%s.rb post-%s.rb ).map {|fmt| %w( config setup install clean distclean ).map {|t| sprintf(fmt, t) } }.flatten end def glob_select(pat, ents) re = globs2re([pat]) ents.select {|ent| re =~ ent } end def glob_reject(pats, ents) re = globs2re(pats) ents.reject {|ent| re =~ ent } end GLOB2REGEX = { '.' => '\.', '$' => '\$', '#' => '\#', '*' => '.*' } def globs2re(pats) /\A(?:#{ pats.map {|pat| pat.gsub(/[\.\$\#\*]/) {|ch| GLOB2REGEX[ch] } }.join('|') })\z/ end # # TASK test # TESTDIR = 'test' def exec_test unless File.directory?('test') $stderr.puts 'no test in this package' if verbose? return end $stderr.puts 'Running tests...' if verbose? begin require 'test/unit' rescue LoadError setup_rb_error 'test/unit cannot loaded. You need Ruby 1.8 or later to invoke this task.' end runner = Test::Unit::AutoRunner.new(true) runner.to_run << TESTDIR runner.run end # # TASK clean # def exec_clean exec_task_traverse 'clean' rm_f @config.savefile rm_f 'InstalledFiles' end alias clean_dir_bin noop alias clean_dir_lib noop alias clean_dir_data noop alias clean_dir_conf noop alias clean_dir_man noop def clean_dir_ext(rel) return unless extdir?(curr_srcdir()) make 'clean' if File.file?('Makefile') end # # TASK distclean # def exec_distclean exec_task_traverse 'distclean' rm_f @config.savefile rm_f 'InstalledFiles' end alias distclean_dir_bin noop alias distclean_dir_lib noop def distclean_dir_ext(rel) return unless extdir?(curr_srcdir()) make 'distclean' if File.file?('Makefile') end alias distclean_dir_data noop alias distclean_dir_conf noop alias distclean_dir_man noop # # Traversing # def exec_task_traverse(task) run_hook "pre-#{task}" FILETYPES.each do |type| if type == 'ext' and config('without-ext') == 'yes' $stderr.puts 'skipping ext/* by user option' if verbose? next end traverse task, type, "#{task}_dir_#{type}" end run_hook "post-#{task}" end def traverse(task, rel, mid) dive_into(rel) { run_hook "pre-#{task}" __send__ mid, rel.sub(%r[\A.*?(?:/|\z)], '') directories_of(curr_srcdir()).each do |d| traverse task, "#{rel}/#{d}", mid end run_hook "post-#{task}" } end def dive_into(rel) return unless File.dir?("#{@srcdir}/#{rel}") dir = File.basename(rel) Dir.mkdir dir unless File.dir?(dir) prevdir = Dir.pwd Dir.chdir dir $stderr.puts '---> ' + rel if verbose? @currdir = rel yield Dir.chdir prevdir $stderr.puts '<--- ' + rel if verbose? @currdir = File.dirname(rel) end def run_hook(id) path = [ "#{curr_srcdir()}/#{id}", "#{curr_srcdir()}/#{id}.rb" ].detect {|cand| File.file?(cand) } return unless path $stderr.puts "invoking hook script #{path}" if verbose? begin instance_eval File.read(path), path, 1 rescue raise if $DEBUG setup_rb_error "hook #{path} failed:\n" + $!.message end end end # class Installer class SetupError < StandardError; end def setup_rb_error(msg) raise SetupError, msg end if $0 == __FILE__ begin ToplevelInstaller.invoke rescue SetupError raise if $DEBUG $stderr.puts $!.message $stderr.puts "Try 'ruby #{$0} --help' for detailed usage." exit 1 end end event-loop-0.3/ChangeLog0000644000175000017500000002215411574172561014251 0ustar gwolfgwolf2006-10-19 Daniel Brockman Release 0.3. * Rakefile: No longer depend on rubygems. * gemspec.rb: Move contents into `Rakefile'. * VERSION: New file. * setup.rb: New file (copied from upstream). * README.utf-8: Update to reflect changes. * lib/event-loop/timer.rb: Make lots and lots of changes without having enough patience to document them here. * lib/event-loop/signal-system.rb (SignalEmitterModule#define_signal_handler): Define `prehandle_...' and `posthandle_...' instead of `handle_...' and `after_handle_...'. (SignalEmitter#__signal__): Invoke `prehandle_...' and `posthandle_...'. 2006-04-23 Daniel Brockman * lib/event-loop/timer.rb: New file extracted from `lib/event-loop/event-loop.rb'. (Timer#initialize): New instance variable `@alarm_handler'. (Timer#replace_alarm_handler): New method. (Timer#initialize, Timer#restart, Timer#start): Use it. (Timer#initialize): Remove option parameter `:start?'. (TimerTest#test_start_monitoring_timer_while_running): Explicitly call `#start'. (Timer#start, Timer#stop): Set `@running'. (Timer#stop): Don't clear `@start_time'. (Timer#stopped?, Timer#running?): Check `@running' instead of `@start_time'. (Timer#started?): New method. (Timer#start_time, Timer#end_time, Timer#time_left, Timer#ready?): Fail if `@start_time' is nil. (Timer#end_time=, Timer#time_left=): New methods. (Timer#initialize): Make `interval' a mandatory positional parameter. Remove `:interval' keyword parameter. * lib/event-loop/io.rb: New file extracted from `lib/event-loop/event-loop.rb'. * lib/event-loop/event-loop.rb (EventLoop::Utitilies.validate_keyword_arguments): New method extracted from EventLoop::Timer::initialize. (EventLoop#initialize): Document why we have to explicitly extend the notify source with EventLoop::Watchable. 2006-02-06 Daniel Brockman * lib/event-loop/event-loop.rb (Numeric#seconds, Numeric#minutes) (Numeric#hours, Numeric#days, Numeric#weeks, Numeric#years) (Integer#years): New methods. 2006-02-05 Daniel Brockman * Rakefile (check): Run tests with `ruby -w'. Suggested by Tilman Sauerbeck. 2006-02-05 Tilman Sauerbeck * lib/event-loop/event-loop.rb (EventLoop#restart) (EventLoop#start): Add parentheses (this avoids warnings). 2006-01-31 Daniel Brockman Release 0.2. This time I won't use Ruby 1.8.3 to build the Gem package. Thanks to Tilman Sauerbeck for pointing out that problem. * lib/event-loop/event-loop.rb (Timer#interval=): Only invoke EventLoop#check_timer when the timer is running. (Timer#start, Timer#restart): Accept blocks (for convenience). (EventLoop#monitor_timer): Actually check the timer. (TimerTest): Create new event loops for each test. (TimerTest::test_start_monitoring_timer_while_running): New test. (Time.measure): New method. * Rakefile (README): Convert Unicode copyright symbols to conventional ASCII ones. Add note warning that README is automatically generated. * GFDL: New file. * README.utf-8: License this document under the GFDL. (Table of Contents): New section. (time-stamp-format, time-stamp-start, time-stamp-end): New local variables (for Emacs). 2006-01-30 Daniel Brockman * README.utf-8 (Event Sources): New section. Suggested by Luke Kanies. * lib/event-loop/event-loop.rb (Timer#end_time): Raise an exception if the timer is not running. (TimerTest#test_monitor_unstarted_timer): New test case. (Timer#restart, Timer#sound_alarm, Timer#start, Timer#stop) (EventLoop#wake_up): For convenience, return self. 2006-01-25 Daniel Brockman Release 0.1. * README.utf-8: Improve the documentation, explaining more in-depth how IO objects and timers interact with event loops, and providing some meaningful examples, including one that shows how to use the `IO#will_block' property, and one at the end that provides an outline for the typical application. * lib/event-loop/event-loop.rb (Timer.new): Fail if given unrecognized keyword arguments. Suggested by Luke Kanies. (Timer): Add reader for property `event_loop'. (EventLoop): Rename property `sleeping?' to `asleep?', for symmetry with `awake?'. (EventLoop.with_current): Warn the user if `EventLoop.current' is permanently changed within the dynamic extent of `with_current'. 2005-11-17 Daniel Brockman Release 0.0.20051116. * lib/event-loop/signal-system.rb (SignalEmitterModule.extended): Use `fcall' instead of `send' on Ruby 1.9. 2005-09-28 Daniel Brockman Release 0.0.20050928. * lib/event-loop/signal-system.rb (SignalEmitter#__maybe_initialize_signal_emitter): Avoid warnings about using an uninitialized instance variable. 2005-09-04 Daniel Brockman Release 0.0.20050904. * Rakefile (upload): Print an error message if this target is used by someone other than the maintaner. (README): Warn if the Unicode string handling is broken. 2005-09-04 Tilman Sauerbeck * Rakefile: Add `install' target. 2005-08-28 Daniel Brockman Release 0.0.20050829.0000. * lib/event-loop/signal-system.rb (SignalEmitterClass): Rename to SignalEmitterModule. (SignalEmitterModule.extended): New method. 2005-08-26 Daniel Brockman * lib/event-loop/signal-system.rb (SignalEmitterClass#define_signal_handler): New method. (SignalEmitterClass#define_signal): Use it. (SignalObserver#absorb_signals): New method. (SignalObserver#absorb_signal, SignalObserver#map_signal): New soft aliases. * lib/event-loop/better-definers.rb (Module#define_guarded_writers): Pass the block along to `guard_writers' (fixes an obvious bug). * lib/event-loop/signal-system.rb (SignalEmitter): New attribute `allow_dynamic_signals?'. (SignalEmitter#__signal__): Fail for undefined signals unless these are explicitly allowed (by the new attribute). 2005-08-25 Daniel Brockman Release 0.0.20050825.1600. * README.utf-8: Update installation instructions, thank Tilman some more for the gem specification, and add a section explaining what timer tolerances are for. * lib/event-loop.rb: New file. * event-loop.rb, signal-system.rb, better-definers.rb: Move source files to `lib/event-loop/'. Change `require' statements accordingly. * lib/, lib/event-loop/: New directories. * Rakefile: Add new `upload' target. * Makefile: Move `check' target to Rakefile and delete. * gemspec.rb: Clean up and copyedit. :-) 2005-08-23 Tilman Sauerbeck * Rakefile: New file. * gemspec.rb: New file. 2005-08-23 Daniel Brockman * event-loop.rb (EventLoop#select, EventLoop#run): Don't call `maybe_initialize_pipe'. * event-loop.rb (EventLoop#maybe_initialize_pipe): Remove unused method. 2005-08-21 Daniel Brockman Release 0.0.20050821.2356. * README.utf-8: Fix typos. * better-definers.rb (Module#define_soft_aliases): Parenthesize splatted arguments to avoid warnings. * event-loop.rb (EventLoop#initialize): Initialize @awake. 2005-08-20 Daniel Brockman * signal-system.rb (SignalObserver#ignore_signal): Fix fatal bug. (SignalEmitter#__signal__): Don't pass `self' as first argument. * better-definers.rb (Module#guard_writers): Use `define_hard_alias' rather than `define_alias'. * signal-system.rb: Likewise. * event-loop.rb: Use qualified names in `require'. (EventLoop#maybe_initialize_pipe): Use `sysread(256)' instead of `read' to read from the notification pipe. * Makefile (check): Pass `-I..' option to `ruby'. 2005-08-19 Daniel Brockman * README.utf-8 (IO Events): Update documentation to reflect the changes related to Watchable::Automatic. * event-loop.rb (Watchable::Automatic): New module. (Watchable#remove_signal_handler, Watchable#add_signal_handler): Move to new module. (Watchable#monitor_events, Watchable#ignore_events): New methods. (Watchable#close_read, Watchable#close_write) (Watchable#close): Use them. (IO#on_readable, IO#on_writable, IO#on_exceptional): Extend with Watchable::Automatic instead of just Watchable. (EventLoop#maybe_initialize_pipe): Use `read' instead of `sysread(1)' to read from the notification pipe. (Symbol#io_state?): New convenience method. * event-loop.rb (EventLoop#check_timer): New method. (EventLoop#monitor_timer): Use it. (EventLoop#select): New private method. (EventLoop#iterate): Refactor using new `select' helper method. * event-loop.rb (Timer.new): New option `event_loop'. Each timer is now associated with exactly one event loop. (Timer#fire_alarm): Only restart the timer if it is still running when the alarm returns. (Timer#start, Timer#stop): Use @event_loop instead of EventLoop. (Timer#fire_alarm): Rename to `sound_alarm'. (Timer#stopped?, Timer#running?): Instead of checking @running, assume that @start_time is nil iff the timer is running. * event-loop.rb (Timer#initialize): Allow a single numeric argument as a shorthand for specifying the interval. (Timer#interval=): New method. * ChangeLog: New file. event-loop-0.3/COPYING0000644000175000017500000004311111574172561013526 0ustar gwolfgwolf GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License.