lib/blockenspiel.rb in blockenspiel-0.0.3 vs lib/blockenspiel.rb in blockenspiel-0.0.4
- old
+ new
@@ -44,13 +44,34 @@
# the main entry point method "invoke".
module Blockenspiel
# Current gem version
- VERSION_STRING = '0.0.3'
+ VERSION_STRING = '0.0.4'
+ # Base exception for all exceptions raised by Blockenspiel
+
+ class BlockenspielError < RuntimeError
+ end
+
+
+ # This exception is rasied when attempting to use the <tt>:proxy</tt> or
+ # <tt>:mixin</tt> parameterless behavior with a target that does not have
+ # the DSL module included. It is an error made by the DSL implementor.
+
+ class DSLMissingError < BlockenspielError
+ end
+
+
+ # This exception is raised when the block provided does not take the
+ # expected number of parameters. It is an error made by the caller.
+
+ class BlockParameterError < BlockenspielError
+ end
+
+
# === DSL setup methods
#
# These class methods are available after you have included the
# Blockenspiel::DSL module.
#
@@ -363,31 +384,32 @@
#
# <tt>:parameterless</tt>::
# If set to false, disables parameterless blocks and always attempts to
# pass a parameter to the block. Otherwise, you may set it to one of
# three behaviors for parameterless blocks: <tt>:mixin</tt> (the
- # default), <tt>:mixin_inheriting</tt>, and <tt>:instance</tt>. See
- # below for a description of these behaviors.
+ # default), <tt>:instance</tt>, and <tt>:proxy</tt>. See below for
+ # detailed descriptions of these behaviors.
# <tt>:parameter</tt>::
# If set to false, disables blocks with parameters, and always attempts
# to use parameterless blocks. Default is true, enabling parameter mode.
#
# The following values control the precise behavior of parameterless
# blocks. These are values for the <tt>:parameterless</tt> option.
#
# <tt>:mixin</tt>::
# This is the default behavior. DSL methods from the target are
- # temporarily overlayed on the caller's self object, but self is itself
- # not modified, so the helper methods and instance variables from the
- # caller's closure remain available. The DSL methods are removed when
- # the block completes.
+ # temporarily overlayed on the caller's +self+ object, but +self+ still
+ # points to the same object, so the helper methods and instance
+ # variables from the caller's closure remain available. The DSL methods
+ # are removed when the block completes.
# <tt>:instance</tt>::
# This behavior actually changes +self+ to the target object using
# <tt>instance_eval</tt>. Thus, the caller loses access to its own
# helper methods and instance variables, and instead gains access to the
- # target object's instance variables. Any DSL method changes applied
- # using <tt>dsl_method</tt> directives are ignored.
+ # target object's instance variables. The target object's methods are
+ # not modified: this behavior does not apply any DSL method changes
+ # specified using <tt>dsl_method</tt> directives.
# <tt>:proxy</tt>::
# This behavior changes +self+ to a proxy object created by applying the
# DSL methods to an empty object, whose <tt>method_missing</tt> points
# back at the block's context. This behavior is a compromise between
# instance and mixin. As with instance, +self+ is changed, so the caller
@@ -446,130 +468,131 @@
# (And yes, you guessed it: this API is a DSL block, and is itself
# implemented using Blockenspiel.)
def self.invoke(block_, target_=nil, opts_={}, &builder_block_)
- # Handle this case gracefully
- return nil unless block_
+ parameter_ = opts_[:parameter]
+ parameterless_ = opts_[:parameterless]
- # Handle dynamic target generation
+ # Handle no-target behavior
+ if parameter_ == false && parameterless_ == false
+ if block_.arity != 0 && block_.arity != -1
+ raise Blockenspiel::BlockParameterError, "Block should not take parameters"
+ end
+ return block_.call
+ end
+
+ # Perform dynamic target generation if requested
if builder_block_
opts_ = target_ || opts_
builder_ = Blockenspiel::Builder.new
invoke(builder_block_, builder_)
target_ = builder_._create_target
end
- # Attempt parameterless block
- parameterless_ = opts_[:parameterless]
- if parameterless_ != false && (block_.arity == 0 || block_.arity == -1)
- if parameterless_ == :instance
+ # Handle parametered block case
+ if parameter_ != false && (block_.arity == 1 || block_.arity == -2) || parameterless_ == false
+ if block_.arity != 1 && block_.arity != -1 && block_.arity != -2
+ raise Blockenspiel::BlockParameterError, "Block should take exactly one parameter"
+ end
+ return block_.call(target_)
+ end
+
+ # Check arity for parameterless case
+ if block_.arity != 0 && block_.arity != -1
+ raise Blockenspiel::BlockParameterError, "Block should not take parameters"
+ end
+
+ # Handle instance-eval behavior
+ if parameterless_ == :instance
+ return target_.instance_eval(&block_)
+ end
+
+ # Get the module of dsl methods
+ mod_ = target_.class._get_blockenspiel_module rescue nil
+ unless mod_
+ raise Blockenspiel::DSLMissingError
+ end
+
+ # Get the block's calling context object
+ object_ = Kernel.eval('self', block_.binding)
+
+ # Handle proxy behavior
+ if parameterless_ == :proxy
+
+ # Create proxy object
+ proxy_ = Blockenspiel::ProxyDelegator.new
+ proxy_.extend(mod_)
+
+ # Store the target and proxy object so dispatchers can get them
+ proxy_delegator_key_ = proxy_.object_id
+ target_stack_key_ = [Thread.current.object_id, proxy_.object_id]
+ @_proxy_delegators[proxy_delegator_key_] = object_
+ @_target_stacks[target_stack_key_] = [target_]
+
+ begin
- # Instance-eval behavior.
- return target_.instance_eval(&block_)
+ # Call the block with the proxy as self
+ return proxy_.instance_eval(&block_)
- else
+ ensure
- # Remaining behaviors use the module of dsl methods
- mod_ = target_.class._get_blockenspiel_module rescue nil
- if mod_
-
- # Get the block's calling context object
- object_ = Kernel.eval('self', block_.binding)
-
- if parameterless_ == :proxy
-
- # Proxy behavior:
- # Create proxy object
- proxy_ = ProxyDelegator.new
- proxy_.extend(mod_)
-
- # Store the target and proxy object so dispatchers can get them
- proxy_delegator_key_ = proxy_.object_id
- target_stack_key_ = [Thread.current.object_id, proxy_.object_id]
- @_proxy_delegators[proxy_delegator_key_] = object_
- @_target_stacks[target_stack_key_] = [target_]
-
- begin
-
- # Call the block with the proxy as self
- return proxy_.instance_eval(&block_)
-
- ensure
-
- # Clean up the dispatcher information
- @_proxy_delegators.delete(proxy_delegator_key_)
- @_target_stacks.delete(target_stack_key_)
-
- end
-
- else
-
- # Mixin behavior:
- # Create hash keys
- mixin_count_key_ = [object_.object_id, mod_.object_id]
- target_stack_key_ = [Thread.current.object_id, object_.object_id]
-
- # Store the target for inheriting.
- # We maintain a target call stack per thread.
- target_stack_ = @_target_stacks[target_stack_key_] ||= Array.new
- target_stack_.push(target_)
-
- # Mix this module into the object, if required.
- # This ensures that we keep track of the number of requests to
- # mix this module in, from nested blocks and possibly multiple threads.
- @_mutex.synchronize do
- count_ = @_mixin_counts[mixin_count_key_]
- if count_
- @_mixin_counts[mixin_count_key_] = count_ + 1
- else
- @_mixin_counts[mixin_count_key_] = 1
- object_.mixin(mod_)
- end
- end
-
- begin
-
- # Now call the block
- return block_.call
-
- ensure
-
- # Clean up the target stack
- target_stack_.pop
- @_target_stacks.delete(target_stack_key_) if target_stack_.size == 0
-
- # Remove the mixin from the object, if required.
- @_mutex.synchronize do
- count_ = @_mixin_counts[mixin_count_key_]
- if count_ == 1
- @_mixin_counts.delete(mixin_count_key_)
- object_.unmix(mod_)
- else
- @_mixin_counts[mixin_count_key_] = count_ - 1
- end
- end
-
- end
- # End mixin behavior
-
- end
-
- end
- # End use of dsl methods module
+ # Clean up the dispatcher information
+ @_proxy_delegators.delete(proxy_delegator_key_)
+ @_target_stacks.delete(target_stack_key_)
end
+
end
- # End attempt of parameterless block
+
+ # Handle mixin behavior (default)
- # Attempt parametered block
- if opts_[:parameter] != false && block_.arity != 0
- return block_.call(target_)
+ # Create hash keys
+ mixin_count_key_ = [object_.object_id, mod_.object_id]
+ target_stack_key_ = [Thread.current.object_id, object_.object_id]
+
+ # Store the target for inheriting.
+ # We maintain a target call stack per thread.
+ target_stack_ = @_target_stacks[target_stack_key_] ||= Array.new
+ target_stack_.push(target_)
+
+ # Mix this module into the object, if required.
+ # This ensures that we keep track of the number of requests to
+ # mix this module in, from nested blocks and possibly multiple threads.
+ @_mutex.synchronize do
+ count_ = @_mixin_counts[mixin_count_key_]
+ if count_
+ @_mixin_counts[mixin_count_key_] = count_ + 1
+ else
+ @_mixin_counts[mixin_count_key_] = 1
+ object_.mixin(mod_)
+ end
end
- # Last resort fall-back
- return block_.call
+ begin
+
+ # Now call the block
+ return block_.call
+
+ ensure
+
+ # Clean up the target stack
+ target_stack_.pop
+ @_target_stacks.delete(target_stack_key_) if target_stack_.size == 0
+
+ # Remove the mixin from the object, if required.
+ @_mutex.synchronize do
+ count_ = @_mixin_counts[mixin_count_key_]
+ if count_ == 1
+ @_mixin_counts.delete(mixin_count_key_)
+ object_.unmix(mod_)
+ else
+ @_mixin_counts[mixin_count_key_] = count_ - 1
+ end
+ end
+
+ end
end
# This implements the mapping between DSL module methods and target object methods.
@@ -577,18 +600,18 @@
# Then we attempt to call the given method on that object.
# If we can't find an appropriate method to call, return the special value TARGET_MISMATCH.
def self._target_dispatch(object_, name_, params_, block_) # :nodoc:
target_stack_ = @_target_stacks[[Thread.current.object_id, object_.object_id]]
- return TARGET_MISMATCH unless target_stack_
+ return Blockenspiel::TARGET_MISMATCH unless target_stack_
target_stack_.reverse_each do |target_|
target_class_ = target_.class
delegate_ = target_class_._get_blockenspiel_delegate(name_)
if delegate_ && target_class_.public_method_defined?(delegate_)
return target_.send(delegate_, *params_, &block_)
end
end
- return TARGET_MISMATCH
+ return Blockenspiel::TARGET_MISMATCH
end
# This implements the proxy fall-back behavior.
# We look up the context object, and call the given method on that object.