diff options
Diffstat (limited to 'lib/puppet/event-loop/signal-system.rb')
-rw-r--r-- | lib/puppet/event-loop/signal-system.rb | 220 |
1 files changed, 220 insertions, 0 deletions
diff --git a/lib/puppet/event-loop/signal-system.rb b/lib/puppet/event-loop/signal-system.rb new file mode 100644 index 000000000..f7fe9b52c --- /dev/null +++ b/lib/puppet/event-loop/signal-system.rb @@ -0,0 +1,220 @@ +## signal-system.rb --- simple intra-process signal system +# 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. + +require "puppet/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 "handle_#{name}", &body + when :after + define_protected_method "after_handle_#{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__("handle_#{name}", *args, &block) if + respond_to? "handle_#{name}" + @signal_handlers[name].each { |x| x.call(*args, &block) } + __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 + +## application-signals.rb ends here. |