Module Blockenspiel
In: lib/blockenspiel.rb
lib/blockenspiel/unmixer_unimplemented.rb
lib/blockenspiel/errors.rb
lib/blockenspiel/version.rb
lib/blockenspiel/unmixer_rubinius.rb
lib/blockenspiel/versionomy.rb
lib/blockenspiel/impl.rb
lib/blockenspiel/dsl_setup.rb
lib/blockenspiel/builder.rb

Blockenspiel

The Blockenspiel module provides a namespace for Blockenspiel, as well as the main entry point method "invoke".

Methods

invoke  

Classes and Modules

Module Blockenspiel::DSL
Module Blockenspiel::DSLSetupMethods
Class Blockenspiel::Base
Class Blockenspiel::BlockParameterError
Class Blockenspiel::BlockenspielError
Class Blockenspiel::Builder
Class Blockenspiel::DSLMissingError

Constants

VERSION_STRING = ::File.read(::File.dirname(__FILE__)+'/../../Version').strip.freeze   Current gem version, as a frozen string.
VERSION = defined?(::Versionomy) ? ::Versionomy.parse(VERSION_STRING, :standard) : VERSION_STRING   Current gem version, as a Versionomy::Value if the versionomy library is available, or as a frozen string if not.

Public Class methods

Invoke a given DSL

This is the entry point for Blockenspiel. Call this function to invoke a set of DSL code provided by the user of your API.

For example, if you want users of your API to be able to do this:

 call_dsl do
   foo(1)
   bar(2)
 end

Then you should implement call_dsl like this:

 def call_dsl(&block)
   my_dsl = create_block_implementation
   Blockenspiel.invoke(block, my_dsl)
   do_something_with(my_dsl)
 end

In the above, create_block_implementation is a placeholder that returns an instance of your DSL methods class. This class includes the Blockenspiel::DSL module and defines the DSL methods foo and bar. See Blockenspiel::DSLSetupMethods for a set of tools you can use in your DSL methods class for creating a DSL.

Usage patterns

The invoke method has a number of forms, depending on whether the API user‘s DSL code is provided as a block or a string, and depending on whether the DSL methods are specified statically using a DSL class or dynamically using a block.

Blockenspiel.invoke(user_block, my_dsl, opts)
This form takes the user‘s code as a block, and the DSL itself as an object with DSL methods. The opts hash is optional and provides a set of arguments as described below under "Block DSL options".
Blockenspiel.invoke(user_block, opts) { … }
This form takes the user‘s code as a block, while the DSL itself is specified in the given block, as described below under "Dynamic target generation". The opts hash is optional and provides a set of arguments as described below under "Block DSL options".
Blockenspiel.invoke(user_string, my_dsl, opts)
This form takes the user‘s code as a string, and the DSL itself as an object with DSL methods. The opts hash is optional and provides a set of arguments as described below under "String DSL options".
Blockenspiel.invoke(user_string, opts) { … }
This form takes the user‘s code as a block, while the DSL itself is specified in the given block, as described below under "Dynamic target generation". The opts hash is optional and provides a set of arguments as described below under "String DSL options".
Blockenspiel.invoke(my_dsl, opts)
This form reads the user‘s code from a file, and takes the DSL itself as an object with DSL methods. The opts hash is required and provides a set of arguments as described below under "String DSL options". The :file option is required.
Blockenspiel.invoke(opts) { … }
This form reads the user‘s code from a file, while the DSL itself is specified in the given block, as described below under "Dynamic target generation". The opts hash is required and provides a set of arguments as described below under "String DSL options". The :file option is required.

Block DSL options

When a user provides DSL code using a block, you simply pass that block as the first parameter to Blockenspiel.invoke. Normally, Blockenspiel will first check the block‘s arity to see whether it takes a parameter. If so, it will pass the given target to the block. If the block takes no parameter, and the given target is an instance of a class with DSL capability, the DSL methods are made available on the caller‘s self object so they may be called without a block parameter.

Following are the options understood by Blockenspiel when providing code using a block:

:parameterless
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: :mixin (the default), :instance, and :proxy. See below for detailed descriptions of these behaviors. This option key is also available as :behavior.
:parameter
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 :parameterless option.

:mixin
This is the default behavior. DSL methods from the target are 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.
:instance
This behavior actually changes self to the target object using instance_eval. Thus, the caller loses access to its own helper methods and instance variables, and instead gains access to the target object‘s instance variables. The target object‘s methods are not modified: this behavior does not apply any DSL method changes specified using dsl_method directives.
:proxy
This behavior changes self to a proxy object created by applying the DSL methods to an empty object, whose method_missing 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 loses access to its own instance variables. However, the caller‘s own methods should still be available since any methods not handled by the DSL are delegated back to the caller. Also, as with mixin, the target object‘s instance variables are not available (and thus cannot be clobbered) in the block, and the transformations specified by dsl_method directives are honored.

String DSL options

When a user provides DSL code using a string (either directly or via a file), Blockenspiel always treats it as a "parameterless" invocation, since there is no way to "pass a parameter" to a string. Thus, the two options recognized for block DSLs, :parameterless, and :parameter, are meaningless and ignored. However, the following new options are recognized:

:file
The value of this option should be a string indicating the path to the file from which the user‘s DSL code is coming. It is passed as the "file" parameter to eval; that is, it is included in the stack trace should an exception be thrown out of the DSL. If no code string is provided directly, this option is required and must be set to the path of the file from which to load the code.
:line
This option is passed as the "line" parameter to eval; that is, it indicates the starting line number for the code string, and is used to compute line numbers for the stack trace should an exception be thrown out of the DSL. This option is optional and defaults to 1.
:behavior
Controls how the DSL is called. Recognized values are :proxy (the default) and :instance. See below for detailed descriptions of these behaviors. Note that :mixin is not allowed in this case because its behavior would be indistinguishable from the proxy behavior.

The following values are recognized for the :behavior option:

:proxy
This behavior changes self to a proxy object created by applying the DSL methods to an empty object. Thus, the code in the DSL string does not have access to the target object‘s internal instance variables or private methods. Furthermore, the transformations specified by dsl_method directives are honored. This is the default behavior.
:instance
This behavior actually changes self to the target object using instance_eval. Thus, the code in the DSL string gains access to the target object‘s instance variables and private methods. Also, the target object‘s methods are not modified: this behavior does not apply any DSL method changes specified using dsl_method directives.

Dynamic target generation

It is also possible to dynamically generate a target object by passing a block to this method. This is probably best illustrated by example:

 Blockenspiel.invoke(block) do
   add_method(:set_foo) do |value|
     my_foo = value
   end
   add_method(:set_things_from_block) do |value, &blk|
     my_foo = value
     my_bar = blk.call
   end
 end

The above is roughly equivalent to invoking Blockenspiel with an instance of this target class:

 class MyFooTarget
   include Blockenspiel::DSL
   def set_foo(value)
     set_my_foo_from(value)
   end
   def set_things_from_block(value)
     set_my_foo_from(value)
     set_my_bar_from(yield)
   end
 end

 Blockenspiel.invoke(block, MyFooTarget.new)

The obvious advantage of using dynamic object generation is that you are creating methods using closures, which provides the opportunity to, for example, modify closure local variables such as my_foo. This is more difficult to do when you create a target class since its methods do not have access to outside data. Hence, in the above example, we hand-waved, assuming the existence of some method called "set_my_foo_from".

The disadvantage is performance. If you dynamically generate a target object, it involves parsing and creating a new class whenever it is invoked. Thus, it is recommended that you use this technique for calls that are not used repeatedly, such as one-time configuration.

See the Blockenspiel::Builder class for more details on add_method.

(And yes, you guessed it: this API is a DSL block, and is itself implemented using Blockenspiel.)

[Validate]