blockenspiel-0.5.0/ 0000755 0000041 0000041 00000000000 12652620702 014220 5 ustar www-data www-data blockenspiel-0.5.0/History.rdoc 0000644 0000041 0000041 00000012033 12652620702 016531 0 ustar www-data www-data === 0.5.0 / 2016-01-07
* Fixed an issue with the proxy strategy, where if a block spawns blocks that live longer than it, the sub-blocks lost their context.
* Changed the default strategy to proxy due to semantic issues with mixin and difficulty supporting it.
* Dropped support for the mixin strategy on MRI because Ruby 2.3.0 broke it and I don't have the bandwidth to find a remedy.
* Updated the Rakefile, tests, and general infrastructure to play better with modern Rubies.
* Dropped support for Ruby 1.8, because who still uses 1.8???
=== 0.4.6 / (never actually released)
* Compatibility with the signature change to reset_method_cache in recent builds of Rubinius 2.0.
* The gemspec no longer includes the timestamp in the version, so that bundler can pull from github. (Reported by corneverbruggen)
* The Rakefile is now compatible with Ruby 2.0 and RubyGems 2.0.
=== 0.4.5 / 2012-06-27
* The 0.4.4 build was missing the JRuby unmixer. Fixed.
=== 0.4.4 / 2012-06-27
* Under JRuby 1.9 mode, if two threads mixed into the same object, the calls sometimes went to the wrong place. Fixed.
* The VERSION constant behaved very oddly under Rubinius 1.9 mode. Fixed.
* Eliminate some warnings.
* Integrate with Travis CI.
=== 0.4.3 / 2011-06-22
* MRI C extension experienced a compile error under the current MRI head (e.g. 1.9.3). Fixed.
* Rake-based build of the MRI C extension failed under newer rakes. Fixed.
* Eliminated some Rakefile deprecation warnings under newer rakes.
=== 0.4.2 / 2011-06-02
* Fixed an unmixer compatibility issue with Rubinius > 1.2.x. (Thanks to @meh for the fix.)
* Recent versions of Rubinius raised exceptions involving Fiber. Fixed.
* Workaround for a JRuby NullPointerException (JRUBY-5842).
* Integrated JRuby platform gem back into main gem.
* A .gemspec file is now available for gem building and bundler git integration.
* Some cleanup of the Rakefile and tests.
=== 0.4.1 / 2010-06-23
* Support for rubinius 1.0.
=== 0.4.0 / 2010-06-21
* Implemented string- and file-based DSLs (in addition to block-based).
* Correctly handle separate active DSLs in different fibers within the
same thread, when fibers are avaialble.
* Updated ruby runtime dependencies to reflect what I'm actually testing.
* Organized the source a little better, and fixed some Rakefile quirks.
=== 0.3.3 / 2010-05-24
* Some Rakefile fixes to match RDoc and Ruby 1.9 changes.
* Minor documentation updates.
=== 0.3.2 / 2009-11-17
* Modules included in a DSL-ized class now have their methods included in
the DSL.
* Raise a more informative error (for now) when trying to include
Blockenspiel::DSL in a module. At some point, we'll support this usage,
once I figure out the right semantics for it.
=== 0.3.1 / 2009-11-08
* Blockenspiel#invoke can now take its options hash as the second argument
(instead of the third) when using dynamic target generation, since the
second argument is otherwise unused in this case.
* Now defines Blockenspiel::VERSION, as a versionomy object if the
versionomy library is available, or as a version string if not.
=== 0.3.0 / 2009-11-04
* dsl_attr_writer and dsl_attr_accessor convenience methods are available
for creating DSL-friendly attributes.
* Dynamic DSL methods can now take real block arguments, if supported by
the Ruby interpreter.
* Shortened README.rdoc and renamed the longer version to Blockenspiel.rdoc.
* Some documentation updates.
=== 0.2.2 / 2009-10-28
* Support for gemcutter hosting in the build/release scripts.
* Some clarifications to constant scopes internal in the code.
* A few documentation updates.
* Minor changes to the Implementing DSL Blocks paper to deal with
Why's disappearance.
=== 0.2.1 / 2009-04-16
* Now compatible with Ruby 1.9.
* Now compatible with JRuby 1.2.
* No longer requires the mixology gem.
* Building no longer requires hoe.
=== 0.2.0 / 2009-04-15
* Earlier build of 0.2.1 that had some problems with JRuby.
=== 0.1.1 / 2008-11-06
* Added ability to pass the block as the first parameter in
the dynamic DSL builder API; cleaned up the API a little
* Minor fixes to Implementing DSL Blocks paper
* Some updates to rdocs
* More test coverage
=== 0.1.0 / 2008-10-29
* Alpha release, opened for public feedback
* Tightened constraints on block parameters
* Added some test cases for threads and parameter constraints
* Revisions to the Implementing DSL Blocks paper
=== 0.0.4 / 2008-10-24
* Improvements to the logic for choosing behaviors
* Added exception classes and provided better error handling
* Actually added the behavior test case to the gem manifest...
* Documentation revisions
* Revisions to the Implementing DSL Blocks paper
=== 0.0.3 / 2008-10-23
* Added :proxy behavior for parameterless blocks
* Removed option to turn off inheriting, since the semantics are somewhat
ill-defined and inconsistent. All parameterless blocks now exhibit the
inheriting behavior.
* Added tests for the different behavior settings.
=== 0.0.2 / 2008-10-21
* Cleaned up some of the documentation
* Revisions to the Implementing DSL Blocks paper
=== 0.0.1 / 2008-10-20
* Initial test release
blockenspiel-0.5.0/README.rdoc 0000644 0000041 0000041 00000012510 12652620702 016025 0 ustar www-data www-data == Blockenspiel
Blockenspiel is a helper library designed to make it easy to implement DSL
blocks. It is designed to be comprehensive and robust, supporting most common
usage patterns, and working correctly in the presence of nested blocks and
multithreading.
=== Summary
Blockenspiel is a helper library providing several different strategies for
implementing DSL blocks. It supports both DSLs that take a block parameter
and those that do not. For example:
# Call DSL block with parameter
configure_me do |config|
config.add_foo(1)
config.add_bar(2)
end
# Call DSL block without parameter
configure_me do
add_foo(3)
add_bar(4)
end
To support the above usage, you can do this:
# Implement DSL block methods
class ConfigMethods
include Blockenspiel::DSL
def add_foo(value)
# do something
end
def add_bar(value)
# do something
end
end
# Implement configure_me method
def configure_me(&block)
Blockenspiel.invoke(block, ConfigMethods.new)
end
By default, Blockenspiel uses a "delegation" technique (to my knowledge first
proposed by Dan Manges) to support parameterless blocks while mitigating some
of the issues with instance_eval. It supports nested blocks and
multithreaded access, and provides a variety of tools for handling the
typical issues you may encounter when writing DSLs. On some ruby platforms,
Blockenspiel also supports a mixin technique (proposed by Why The Lucky Stiff).
For more detailed usage and examples, see
{Blockenspiel.rdoc}[link:Blockenspiel\_rdoc.html].
For an extended analysis of different ways to implement DSL blocks, see
{ImplementingDSLblocks.rdoc}[link:ImplementingDSLblocks\_rdoc.html].
=== Requirements
* Ruby 1.9.3 or later, JRuby 1.5 or later, or Rubinius 1.0 or later.
=== Installation
gem install blockenspiel
=== Known issues and to-do items
* Implementing wildcard DSL methods using method_missing doesn't
work. I haven't yet decided on the right semantics for this case, or
whether it is even a reasonable feature at all.
* Including Blockenspiel::DSL in a module (rather than a class) is not
supported, but this could appear in a future release.
* Find a way to implement mixin behavior reliably on MRI.
=== Development and support
Documentation is available at http://dazuma.github.com/blockenspiel/rdoc
Source code is hosted on Github at http://github.com/dazuma/blockenspiel
Contributions are welcome. Fork the project on Github.
Build status: {
}[http://travis-ci.org/dazuma/blockenspiel]
Report bugs on Github issues at http://github.org/dazuma/blockenspiel/issues
Contact the author at dazuma at gmail dot com.
=== Author / Credits
Blockenspiel is written by Daniel Azuma (http://www.daniel-azuma.com/).
The mixin implementation is based on a concept by the late Why The Lucky
Stiff, documented in his 6 October 2008 blog posting entitled "Mixing Our
Way Out Of Instance Eval?". The original link has disappeared along with
its author, but you may find copies or mirrors out there.
The unmixer code is based on {Mixology}[http://rubyforge.org/projects/mixology],
version by Patrick Farley, anonymous z, Dan Manges, and Clint Bishop.
The JRuby code is adapted from Mixology 0.1, and has been stripped down and
modified to support JRuby >= 1.2. The Rubinius code was adapted from unreleased
code in the Mixology source tree and modified to support Rubinius 1.0. I know
Mixology 0.2 is now available, but its Rubinius support is not active, and I'd
rather keep the unmixer bundled with Blockenspiel for now to reduce
dependencies. Earlier versions of Blockenspiel also included a C extension,
adapted from Mixology, to support mixins for MRI, but this code has been
disabled due to issues with newer versions of Ruby.
The dsl_attr_writer and dsl_attr_accessor feature came from a suggestion by
Luis Lavena.
=== License
Copyright 2008 Daniel Azuma.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the copyright holder, nor the names of any other
contributors to this software, may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
blockenspiel-0.5.0/Version 0000644 0000041 0000041 00000000006 12652620702 015564 0 ustar www-data www-data 0.5.0
blockenspiel-0.5.0/lib/ 0000755 0000041 0000041 00000000000 12652620702 014766 5 ustar www-data www-data blockenspiel-0.5.0/lib/blockenspiel/ 0000755 0000041 0000041 00000000000 12652620702 017440 5 ustar www-data www-data blockenspiel-0.5.0/lib/blockenspiel/errors.rb 0000644 0000041 0000041 00000004661 12652620702 021310 0 ustar www-data www-data # -----------------------------------------------------------------------------
#
# Blockenspiel error classes
#
# -----------------------------------------------------------------------------
# Copyright 2008 Daniel Azuma
#
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of the copyright holder, nor the names of any other
# contributors to this software, may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# -----------------------------------------------------------------------------
;
module Blockenspiel
# Base exception for all exceptions raised by Blockenspiel
class BlockenspielError < ::RuntimeError
end
# This exception is rasied when attempting to use the :proxy or
# :mixin parameterless behavior with a target that does not have
# the DSL module included. It is an error made by the DSL implementor.
class DSLMissingError < ::Blockenspiel::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 < ::Blockenspiel::BlockenspielError
end
end
blockenspiel-0.5.0/lib/blockenspiel/versionomy.rb 0000644 0000041 0000041 00000004123 12652620702 022177 0 ustar www-data www-data # -----------------------------------------------------------------------------
#
# Blockenspiel version
#
# -----------------------------------------------------------------------------
# Copyright 2008 Daniel Azuma
#
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of the copyright holder, nor the names of any other
# contributors to this software, may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# -----------------------------------------------------------------------------
;
begin
require 'versionomy'
rescue ::LoadError
end
module Blockenspiel
# Current gem version, as a Versionomy::Value if the versionomy library
# is available, or as a frozen string if not.
VERSION = defined?(::Versionomy) ? ::Versionomy.parse(VERSION_STRING, :standard) : VERSION_STRING
end
blockenspiel-0.5.0/lib/blockenspiel/unmixer_rubinius.rb 0000644 0000041 0000041 00000006523 12652620702 023402 0 ustar www-data www-data # -----------------------------------------------------------------------------
#
# Blockenspiel unmixer for Rubinius
#
# -----------------------------------------------------------------------------
# Copyright 2011 Daniel Azuma
#
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of the copyright holder, nor the names of any other
# contributors to this software, may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# -----------------------------------------------------------------------------
;
module Blockenspiel
# :stopdoc:
module Unmixer
@old_metaclass = ''.respond_to?(:metaclass)
# Unmix a module from an object in Rubinius.
#
# This implementation is based on unreleased code from the Mixology
# source, written by Dan Manges.
# See http://github.com/dan-manges/mixology
#
# It has been stripped down and modified for compatibility with the
# Rubinius 1.0 release.
def self.unmix(obj_, mod_) # :nodoc:
last_super_ = @old_metaclass ? obj_.metaclass : obj_.singleton_class
this_super_ = last_super_.direct_superclass
while this_super_
if (this_super_ == mod_ || this_super_.respond_to?(:module) && this_super_.module == mod_)
_reset_method_cache(obj_)
last_super_.superclass = this_super_.direct_superclass
_reset_method_cache(obj_)
return
else
last_super_ = this_super_
this_super_ = this_super_.direct_superclass
end
end
nil
end
if ::Rubinius::VM.method(:reset_method_cache).arity == 1
# Older versions of Rubinius
def self._reset_method_cache(obj_) # :nodoc:
obj_.methods.each do |name_|
::Rubinius::VM.reset_method_cache(name_.to_sym)
end
end
else
# Newer versions of Rubinius
def self._reset_method_cache(obj_) # :nodoc:
klass_ = obj_.class
obj_.methods.each do |name_|
::Rubinius::VM.reset_method_cache(klass_, name_.to_sym)
end
end
end
end
# :startdoc:
end
blockenspiel-0.5.0/lib/blockenspiel/version.rb 0000644 0000041 0000041 00000004004 12652620702 021450 0 ustar www-data www-data # -----------------------------------------------------------------------------
#
# Blockenspiel version
#
# -----------------------------------------------------------------------------
# Copyright 2008 Daniel Azuma
#
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of the copyright holder, nor the names of any other
# contributors to this software, may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# -----------------------------------------------------------------------------
;
module Blockenspiel
# Current gem version, as a frozen string.
VERSION_STRING = ::File.read(::File.dirname(__FILE__)+'/../../Version').strip.freeze
autoload(:VERSION, ::File.dirname(__FILE__)+'/versionomy.rb')
end
blockenspiel-0.5.0/lib/blockenspiel/unmixer_unimplemented.rb 0000644 0000041 0000041 00000004244 12652620702 024406 0 ustar www-data www-data # -----------------------------------------------------------------------------
#
# Blockenspiel unmixer module when unmixer is not implemented
#
# -----------------------------------------------------------------------------
# Copyright 2010 Daniel Azuma
#
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of the copyright holder, nor the names of any other
# contributors to this software, may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# -----------------------------------------------------------------------------
;
module Blockenspiel
# :stopdoc:
module Unmixer
UNIMPLEMENTED = true
# Unmixer stub.
# This throws an exception indicating the unmixer is not implemented.
def self.unmix(obj_, mod_) # :nodoc:
raise "Blockenspiel's mixin behavior is not implemented on this ruby platform."
end
end
# :startdoc:
end
blockenspiel-0.5.0/lib/blockenspiel/dsl_setup.rb 0000644 0000041 0000041 00000030723 12652620702 021774 0 ustar www-data www-data # -----------------------------------------------------------------------------
#
# Blockenspiel DSL definition
#
# -----------------------------------------------------------------------------
# Copyright 2008 Daniel Azuma
#
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of the copyright holder, nor the names of any other
# contributors to this software, may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# -----------------------------------------------------------------------------
;
require 'thread'
module Blockenspiel
# === DSL setup methods
#
# These class methods are available after you have included the
# Blockenspiel::DSL module.
#
# By default, a class that has DSL capability will automatically make
# all public methods available to parameterless blocks, except for the
# +initialize+ method, any methods whose names begin with an underscore,
# and any methods whose names end with an equals sign.
#
# If you want to change this behavior, use the directives defined here to
# control exactly which methods are available to parameterless blocks.
module DSLSetupMethods
# :stopdoc:
# Called when DSLSetupMethods extends a class.
# This sets up the current class, and adds a hook that causes
# any subclass of the current class also to be set up.
def self.extended(klass_)
unless klass_.instance_variable_defined?(:@_blockenspiel_module)
_setup_class(klass_)
def klass_.inherited(subklass_)
::Blockenspiel::DSLSetupMethods._setup_class(subklass_)
super
end
class << klass_
unless private_method_defined?(:_blockenspiel_default_include)
alias_method :_blockenspiel_default_include, :include
alias_method :include, :_blockenspiel_custom_include
end
end
end
end
# :startdoc:
# Set up a class.
# Creates a DSL module for this class, optionally delegating to the superclass's module.
# Also initializes the class's methods hash and active flag.
def self._setup_class(klass_) # :nodoc:
superclass_ = klass_.superclass
superclass_ = nil unless superclass_.respond_to?(:_get_blockenspiel_module)
mod_ = ::Module.new
if superclass_
mod_.module_eval do
include superclass_._get_blockenspiel_module
end
end
klass_.instance_variable_set(:@_blockenspiel_superclass, superclass_)
klass_.instance_variable_set(:@_blockenspiel_module, mod_)
klass_.instance_variable_set(:@_blockenspiel_methods, {})
klass_.instance_variable_set(:@_blockenspiel_active, nil)
end
# Automatically make the given method a DSL method according to the current setting.
def _blockenspiel_auto_dsl_method(symbol_) # :nodoc:
if @_blockenspiel_active
dsl_method(symbol_)
elsif @_blockenspiel_active.nil?
if symbol_ != :initialize && symbol_.to_s !~ /^_/ && symbol_.to_s !~ /=$/
dsl_method(symbol_)
end
end
end
# Hook called when a method is added.
# This calls _blockenspiel_auto_dsl_method to auto-handle the method,
# possibly making it a DSL method according to the current setting.
def method_added(symbol_) # :nodoc:
_blockenspiel_auto_dsl_method(symbol_)
super
end
# Custom include method. Calls the main include implementation, but also
# goes through the public methods of the included module and calls
# _blockenspiel_auto_dsl_method on each to make them DSL methods
# (possibly) according to the current setting.
def _blockenspiel_custom_include(*modules_) # :nodoc:
_blockenspiel_default_include(*modules_)
modules_.reverse_each do |mod_|
mod_.public_instance_methods.each do |method_|
_blockenspiel_auto_dsl_method(method_)
end
end
end
# Get this class's corresponding DSL module
def _get_blockenspiel_module # :nodoc:
@_blockenspiel_module
end
# Get information on the given DSL method name.
# Possible values are the name of the delegate method, false for method disabled,
# or nil for method never defined.
def _get_blockenspiel_delegate(name_) # :nodoc:
delegate_ = @_blockenspiel_methods[name_]
if delegate_.nil? && @_blockenspiel_superclass
@_blockenspiel_superclass._get_blockenspiel_delegate(name_)
else
delegate_
end
end
# Make a particular method available to parameterless DSL blocks.
#
# To explicitly make a method available to parameterless blocks:
# dsl_method :my_method
#
# To explicitly exclude a method from parameterless blocks:
# dsl_method :my_method, false
#
# To explicitly make a method available to parameterless blocks, but
# point it to a method of a different name on the target class:
# dsl_method :my_method, :target_class_method
def dsl_method(name_, delegate_=nil)
name_ = name_.to_sym
if delegate_
delegate_ = delegate_.to_sym
elsif delegate_.nil?
delegate_ = name_
end
@_blockenspiel_methods[name_] = delegate_
unless @_blockenspiel_module.public_method_defined?(name_)
@_blockenspiel_module.module_eval("def #{name_}(*params_, &block_); val_ = ::Blockenspiel._target_dispatch(self, :#{name_}, params_, block_); ::Blockenspiel::NO_VALUE.equal?(val_) ? super(*params_, &block_) : val_; end\n")
end
end
# Control the behavior of methods with respect to parameterless blocks,
# or make a list of methods available to parameterless blocks in bulk.
#
# To enable automatic exporting of methods to parameterless blocks.
# After executing this command, all public methods defined in the class
# will be available on parameterless blocks, until
# dsl_methods false is called:
# dsl_methods true
#
# To disable automatic exporting of methods to parameterless blocks.
# After executing this command, methods defined in this class will be
# excluded from parameterless blocks, until dsl_methods true
# is called:
# dsl_methods false
#
# To make a list of methods available to parameterless blocks in bulk:
# dsl_methods :my_method1, :my_method2, ...
#
# You can also point dsl methods to a method of a different name on the
# target class, by using a hash syntax, as follows:
# dsl_methods :my_method1 => :target_class_method1,
# :my_method2 => :target_class_method2
#
# You can mix non-renamed and renamed method declarations as long as
# the renamed (hash) methods are at the end. e.g.:
# dsl_methods :my_method1, :my_method2 => :target_class_method2
def dsl_methods(*names_)
if names_.size == 0 || names_ == [true]
@_blockenspiel_active = true
elsif names_ == [false]
@_blockenspiel_active = false
else
if names_.last.kind_of?(::Hash)
names_.pop.each do |name_, delegate_|
dsl_method(name_, delegate_)
end
end
names_.each do |name_|
dsl_method(name_, name_)
end
end
end
# A DSL-friendly attr_accessor.
#
# This creates the usual "name" and "name=" methods in the current
# class that can be used in the usual way. However, its implementation
# of the "name" method (the getter) also takes an optional parameter
# that causes it to behave as a setter. This is done because the usual
# setter syntax cannot be used in a parameterless block, since it is
# syntactically indistinguishable from a local variable assignment.
# The "name" method is exposed as a dsl_method.
#
# For example:
#
# dsl_attr_accessor :foo
#
# enables the following:
#
# my_block do |param|
# param.foo = 1 # Usual setter syntax works
# param.foo 2 # Alternate setter syntax also works
# puts param.foo # Usual getter syntax still works
# end
#
# my_block do
# # foo = 1 # Usual setter syntax does NOT work since it
# # looks like a local variable assignment
# foo 2 # Alternate setter syntax does work
# puts foo # Usual getter syntax still works
# end
def dsl_attr_accessor(*names_)
names_.each do |name_|
unless name_.kind_of?(::String) || name_.kind_of?(::Symbol)
raise ::TypeError, "#{name_.inspect} is not a symbol"
end
unless name_.to_s =~ /^[_a-zA-Z]\w+$/
raise ::NameError, "invalid attribute name #{name_.inspect}"
end
module_eval("def #{name_}(value_=::Blockenspiel::NO_VALUE); ::Blockenspiel::NO_VALUE.equal?(value_) ? @#{name_} : @#{name_} = value_; end\n")
alias_method("#{name_}=", name_)
dsl_method(name_)
end
end
# A DSL-friendly attr_writer.
#
# This creates the usual "name=" method in the current class that can
# be used in the usual way. However, it also creates the method "name",
# which also functions as a setter (but not a getter). This is done
# because the usual setter syntax cannot be used in a parameterless
# block, since it is syntactically indistinguishable from a local
# variable assignment. The "name" method is exposed as a dsl_method.
#
# For example:
#
# dsl_attr_writer :foo
#
# is functionally equivalent to:
#
# attr_writer :foo
# alias_method :foo, :foo=
# dsl_method :foo
#
# which enables the following:
#
# my_block do |param|
# param.foo = 1 # Usual setter syntax works
# param.foo 2 # Alternate setter syntax also works
# end
# my_block do
# # foo = 1 # Usual setter syntax does NOT work since it
# # looks like a local variable assignment
# foo(2) # Alternate setter syntax does work
# end
def dsl_attr_writer(*names_)
names_.each do |name_|
attr_writer(name_)
alias_method(name_, "#{name_}=")
dsl_method(name_)
end
end
end
# === DSL activation module
#
# Include this module in a class to mark this class as a DSL class and
# make it possible for its methods to be called from a block that does not
# take a parameter.
#
# After you include this module, you can use the directives defined in
# DSLSetupMethods to control what methods are available to DSL blocks
# that do not take parameters.
module DSL
def self.included(klass_) # :nodoc:
unless klass_.kind_of?(::Class)
raise ::Blockenspiel::BlockenspielError, "You cannot include Blockenspiel::DSL in a module (yet)"
end
klass_.extend(::Blockenspiel::DSLSetupMethods)
end
end
# === DSL activation base class
#
# Subclasses of this base class are considered DSL classes.
# Methods of the class can be made available to be called from a block that
# doesn't take an explicit block parameter.
# You may use the directives defined in DSLSetupMethods to control how
# methods of the class are handled in such blocks.
#
# Subclassing this base class is functionally equivalent to simply
# including Blockenspiel::DSL in the class.
class Base
include ::Blockenspiel::DSL
end
end
blockenspiel-0.5.0/lib/blockenspiel/impl.rb 0000644 0000041 0000041 00000051245 12652620702 020735 0 ustar www-data www-data # -----------------------------------------------------------------------------
#
# Blockenspiel implementation
#
# -----------------------------------------------------------------------------
# Copyright 2008 Daniel Azuma
#
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of the copyright holder, nor the names of any other
# contributors to this software, may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# -----------------------------------------------------------------------------
;
require 'thread'
module Blockenspiel
# === Determine whether the mixin strategy is available
#
# Returns true if the mixin strategy is available on the current ruby
# platform. This will be false for most platforms.
def self.mixin_available?
!::Blockenspiel::Unmixer.const_defined?(:UNIMPLEMENTED)
end
# === 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.
#
# [:proxy]
# This is the default behavior for parameterless blocks.
# 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.
# [:instance]
# This behavior changes +self+ directly 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.
# [:mixin]
# This behavior is not available on all ruby platforms. DSL methods from
# the target are temporarily overlayed on the caller's +self+ object, but
# +self+ still points to the same object. Thus the helper methods and
# instance variables from the caller's closure remain available. The DSL
# methods are removed when the block completes.
#
# === 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.)
def self.invoke(*args_, &builder_block_)
# This method itself is responsible for parsing the args to invoke,
# and handling the dynamic target generation. It then passes control
# to one of the _invoke_with_* methods.
# The arguments.
block_ = nil
eval_str_ = nil
target_ = nil
opts_ = {}
# Get the code
case args_.first
when ::String
eval_str_ = args_.shift
when ::Proc
block_ = args_.shift
end
# Get the target, performing dynamic target generation if requested
if builder_block_
builder_ = ::Blockenspiel::Builder.new
invoke(builder_block_, builder_)
target_ = builder_._create_target
args_.shift if args_.first.nil?
else
target_ = args_.shift
unless target_
raise ::ArgumentError, "No DSL target provided"
end
end
# Get the options hash
if args_.first.kind_of?(::Hash)
opts_ = args_.shift
end
if args_.size > 0
raise ::ArgumentError, "Unexpected arguments"
end
# Invoke
if block_
_invoke_with_block(block_, target_, opts_)
else
_invoke_with_string(eval_str_, target_, opts_)
end
end
# Invoke when the DSL user provides code as a string or file.
# We open and read the file if need be, and then pass control
# to the _execute method.
def self._invoke_with_string(eval_str_, target_, opts_) # :nodoc:
# Read options
file_ = opts_[:file]
line_ = opts_[:line] || 1
# Read file if no string provided directly
unless eval_str_
if file_
eval_str_ = ::File.read(file_)
else
raise ::ArgumentError, "No code or file provided."
end
else
file_ ||= "(String passed to Blockenspiel)"
end
# Handle instance-eval behavior
if opts_[:behavior] == :instance
return target_.instance_eval(eval_str_, file_, line_)
end
# Execute the DSL using the proxy method.
_execute_dsl(false, nil, eval_str_, target_, file_, line_)
end
# Invoke when the DSL user provides code as a block. We read the given
# options hash, handle a few special cases, and then pass control to the
# _execute method.
def self._invoke_with_block(block_, target_, opts_) # :nodoc:
# Read options
parameter_ = opts_[:parameter]
parameterless_ = opts_.include?(:behavior) ? opts_[:behavior] : opts_[:parameterless]
# 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
# Handle parametered block case
if parameter_ != false && block_.arity == 1 || parameterless_ == false
if block_.arity != 1
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
# Execute the DSL
_execute_dsl(parameterless_ == :mixin, block_, nil, target_, nil, nil)
end
# Class for proxy delegators.
# The proxy behavior creates one of these delegators, mixes in the dsl
# methods, and uses instance_eval to invoke the block. This class delegates
# non-handled methods to the context object.
class ProxyDelegator # :nodoc:
def initialize(delegate_)
@_blockenspiel_delegate = delegate_
end
def method_missing(symbol_, *params_, &block_)
::Blockenspiel._proxy_dispatch(self, symbol_, params_, block_)
end
end
# :stopdoc:
NO_VALUE = ::Object.new
# :startdoc:
@_target_stacks = {}
@_mixin_counts = {}
@_proxy_delegators = {}
@_mutex = ::Mutex.new
# This is the "meat" of Blockenspiel, implementing both the proxy and
# mixin methods.
def self._execute_dsl(use_mixin_method_, block_, eval_str_, target_, file_, line_) # :nodoc:
# Get the module of dsl methods
mod_ = target_.class._get_blockenspiel_module rescue nil
unless mod_
raise ::Blockenspiel::DSLMissingError, "Given DSL target does not include Blockenspiel::DSL"
end
# Get the block's calling context object
context_object_ = block_ ? ::Kernel.eval('self', block_.binding) : nil
if use_mixin_method_
# Create hash keys
mixin_count_key_ = [context_object_.object_id, mod_.object_id]
target_stack_key_ = _current_context_id(context_object_)
# Store the target for inheriting.
# We maintain a target call stack per thread.
target_stack_ = @_target_stacks[target_stack_key_] ||= []
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
context_object_.extend(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_)
::Blockenspiel::Unmixer.unmix(context_object_, mod_)
else
@_mixin_counts[mixin_count_key_] = count_ - 1
end
end
end
else
# Create proxy object
proxy_ = ::Blockenspiel::ProxyDelegator.new(context_object_)
proxy_.extend(mod_)
# Store the target object so the dispatcher can get it
target_stack_key_ = _current_context_id(proxy_)
@_target_stacks[target_stack_key_] = [target_]
begin
# Evaluate with the proxy as self
if block_
return proxy_.instance_eval(&block_)
else
return proxy_.instance_eval(eval_str_, file_, line_)
end
ensure
# Clean up the dispatcher information
@_target_stacks.delete(target_stack_key_)
end
end
end
# This implements the mapping between DSL module methods and target object methods.
# We look up the current target object based on the current thread.
# 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 NO_VALUE.
def self._target_dispatch(object_, name_, params_, block_) # :nodoc:
target_stack_ = @_target_stacks[_current_context_id(object_)]
return ::Blockenspiel::NO_VALUE 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 ::Blockenspiel::NO_VALUE
end
# This implements the proxy fall-back behavior.
# We look up the context object, and call the given method on that object.
def self._proxy_dispatch(proxy_, name_, params_, block_) # :nodoc:
delegate_ = proxy_.instance_variable_get(:@_blockenspiel_delegate)
if delegate_
delegate_.send(name_, *params_, &block_)
else
raise ::NoMethodError, "undefined method `#{name_}' in DSL"
end
end
# This returns a current context ID, which includes both the curren thread
# object_id and the current fiber object_id (if available).
begin
require 'fiber'
raise ::LoadError unless defined?(::Fiber)
def self._current_context_id(object_) # :nodoc:
thid_ = ::Thread.current.object_id
begin
[thid_, ::Fiber.current.object_id, object_.object_id]
rescue ::Exception
# JRuby hack (see JRUBY-5842)
[thid_, 0, object_.object_id]
end
end
rescue ::LoadError
def self._current_context_id(object_) # :nodoc:
[::Thread.current.object_id, object_.object_id]
end
end
end
blockenspiel-0.5.0/lib/blockenspiel/builder.rb 0000644 0000041 0000041 00000017152 12652620702 021421 0 ustar www-data www-data # -----------------------------------------------------------------------------
#
# Blockenspiel dynamic target construction
#
# -----------------------------------------------------------------------------
# Copyright 2008 Daniel Azuma
#
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of the copyright holder, nor the names of any other
# contributors to this software, may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# -----------------------------------------------------------------------------
;
module Blockenspiel
# === Dynamically construct a target
#
# These methods are available in a block passed to Blockenspiel#invoke and
# can be used to dynamically define what methods are available from a block.
# See Blockenspiel#invoke for more information.
class Builder
include ::Blockenspiel::DSL
# This is a base class for dynamically constructed targets.
# The actual target class is an anonymous subclass of this base class.
class Target # :nodoc:
include ::Blockenspiel::DSL
# Add a method specification to the subclass.
def self._add_methodinfo(name_, block_, yields_)
(@_blockenspiel_methodinfo ||= {})[name_] = [block_, yields_]
module_eval("def #{name_}(*params_, &block_); self.class._invoke_methodinfo(:#{name_}, params_, block_); end\n")
end
# Attempt to invoke the given method on the subclass.
def self._invoke_methodinfo(name_, params_, block_)
info_ = @_blockenspiel_methodinfo[name_]
case info_[1]
when :first
params_.unshift(block_)
when :last
params_.push(block_)
end
info_[0].call(*params_, &block_)
end
end
# Sets up the dynamic target class.
def initialize # :nodoc:
@target_class = ::Class.new(::Blockenspiel::Builder::Target)
@target_class.dsl_methods(false)
end
# Creates a new instance of the dynamic target class
def _create_target # :nodoc:
@target_class.new
end
# === Declare a DSL method.
#
# This call creates a method that can be called from the DSL block.
# Provide a name for the method, a block defining the method's
# implementation, and an optional hash of options.
#
# By default, a method of the same name is also made available to
# parameterless blocks. To change the name of the parameterless method,
# provide its name as the value of the :dsl_method option.
# To disable this method for parameterless blocks, set the
# :dsl_method option to +false+.
#
# The :mixin option is a deprecated alias for
# :dsl_method.
#
# === Warning about the +return+ keyword
#
# Because you are implementing your method using a block, remember the
# distinction between Proc.new and +lambda+. Invoking +return+
# from the former does not return from the block, but returns from the
# surrounding method scope. Since normal blocks passed to methods are
# of the former type, be very careful about using the +return+ keyword:
#
# add_method(:foo) do |param|
# puts "foo called with parameter "+param.inspect
# return "a return value" # DOESN'T WORK LIKE YOU EXPECT!
# end
#
# To return a value from the method you are creating, set the evaluation
# value at the end of the block:
#
# add_method(:foo) do |param|
# puts "foo called with parameter "+param.inspect
# "a return value" # Returns from method foo
# end
#
# If you must use the +return+ keyword, create your block as a lambda
# as in this example:
#
# code = lambda do |param|
# puts "foo called with parameter "+param.inspect
# return "a return value" # Returns from method foo
# end
# add_method(:foo, &code)
#
# === Accepting a block argument
#
# If you want your method to take a block, you have several options
# depending on your Ruby version. If you are running the standard Matz
# Ruby interpreter (MRI) version 1.8.7 or later (including 1.9.x), or a
# compatible interpreter such as JRuby 1.5 or later, you can use the
# standard "&" block argument notation to receive the block.
# Note that you must call the passed block using the +call+ method since
# Ruby doesn't support invoking such a block with +yield+.
# For example, to create a method named "foo" that takes one parameter
# and a block, do this:
#
# add_method(:foo) do |param, &block|
# puts "foo called with parameter "+param.inspect
# puts "the block returned "+block.call.inspect
# end
#
# In your DSL, you can then call:
#
# foo("hello"){ "a value" }
#
# If you are using MRI 1.8.6, or another Ruby interpreter that doesn't
# fully support this syntax (such as JRuby versions older than 1.5),
# Blockenspiel provides an alternative in the form of the :block
# option. This option causes blocks provided by the caller to be included
# in the normal parameter list to your method, instead of as a block
# parameter. It can be set to :first or :last to
# prepend or append, respectively, the block (as a +Proc+ object) to
# the parameter list. If the caller does not include a block when
# calling your DSL method, nil is prepended/appended. For example:
#
# add_method(:foo, :block => :last) do |param, block|
# puts "foo called with parameter "+param.inspect
# if block
# puts "the block returned "+block.call.inspect
# else
# puts "no block passed"
# end
# end
#
# The :receive_block option is a deprecated alternative.
# Setting :receive_block => true is currently equivalent to
# setting :block => :last.
def add_method(name_, opts_={}, &block_)
receive_block_ = opts_[:receive_block] ? :last : opts_[:block]
receive_block_ = :first if receive_block_ && receive_block_ != :last
@target_class._add_methodinfo(name_, block_, receive_block_)
dsl_method_name_ = opts_[:dsl_method] || opts_[:mixin]
if dsl_method_name_ != false
dsl_method_name_ = name_ if dsl_method_name_.nil? || dsl_method_name_ == true
@target_class.dsl_method(dsl_method_name_, name_)
end
end
end
end
blockenspiel-0.5.0/lib/blockenspiel_unmixer_jruby.jar 0000644 0000041 0000041 00000003374 12652620702 023127 0 ustar www-data www-data PK г&H