blockenspiel 0.2.0.1-java
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/History.rdoc +46 -0
- data/ImplementingDSLblocks.rdoc +934 -0
- data/README.rdoc +350 -0
- data/Rakefile +218 -0
- data/ext/blockenspiel/BlockenspielUnmixerService.java +140 -0
- data/ext/blockenspiel/extconf.rb +2 -0
- data/ext/blockenspiel/unmixer.c +86 -0
- data/lib/blockenspiel.rb +43 -0
- data/lib/blockenspiel/impl.rb +670 -0
- data/lib/blockenspiel/version.rb +42 -0
- data/lib/blockenspiel_unmixer.jar +0 -0
- data/tests/tc_basic.rb +137 -0
- data/tests/tc_behaviors.rb +191 -0
- data/tests/tc_dsl_methods.rb +324 -0
- data/tests/tc_dynamic.rb +175 -0
- data/tests/tc_mixins.rb +240 -0
- metadata +77 -0
@@ -0,0 +1,140 @@
|
|
1
|
+
/*
|
2
|
+
-----------------------------------------------------------------------------
|
3
|
+
|
4
|
+
Blockenspiel unmixer (JRuby implementation)
|
5
|
+
|
6
|
+
-----------------------------------------------------------------------------
|
7
|
+
Copyright 2008-2009 Daniel Azuma
|
8
|
+
|
9
|
+
All rights reserved.
|
10
|
+
|
11
|
+
Redistribution and use in source and binary forms, with or without
|
12
|
+
modification, are permitted provided that the following conditions are met:
|
13
|
+
|
14
|
+
* Redistributions of source code must retain the above copyright notice,
|
15
|
+
this list of conditions and the following disclaimer.
|
16
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
17
|
+
this list of conditions and the following disclaimer in the documentation
|
18
|
+
and/or other materials provided with the distribution.
|
19
|
+
* Neither the name of the copyright holder, nor the names of any other
|
20
|
+
contributors to this software, may be used to endorse or promote products
|
21
|
+
derived from this software without specific prior written permission.
|
22
|
+
|
23
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
24
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
25
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
26
|
+
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
27
|
+
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
28
|
+
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
29
|
+
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
30
|
+
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
31
|
+
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
32
|
+
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
33
|
+
POSSIBILITY OF SUCH DAMAGE.
|
34
|
+
-----------------------------------------------------------------------------
|
35
|
+
*/
|
36
|
+
|
37
|
+
|
38
|
+
/*
|
39
|
+
This implementation based on Mixology,
|
40
|
+
written by Patrick Farley, anonymous z, Dan Manges, and Clint Bishop.
|
41
|
+
http://rubyforge.org/projects/mixology
|
42
|
+
http://github.com/dan-manges/mixology/tree/master
|
43
|
+
|
44
|
+
It has been stripped down and modified for JRuby 1.2 compatibility.
|
45
|
+
*/
|
46
|
+
|
47
|
+
import java.io.IOException;
|
48
|
+
import java.lang.reflect.InvocationTargetException;
|
49
|
+
import java.lang.reflect.Method;
|
50
|
+
import java.util.Iterator;
|
51
|
+
import java.util.List;
|
52
|
+
import java.util.ArrayList;
|
53
|
+
|
54
|
+
import org.jruby.Ruby;
|
55
|
+
import org.jruby.RubyArray;
|
56
|
+
import org.jruby.RubyClass;
|
57
|
+
import org.jruby.RubyModule;
|
58
|
+
import org.jruby.anno.JRubyMethod;
|
59
|
+
import org.jruby.IncludedModuleWrapper;
|
60
|
+
import org.jruby.runtime.Block;
|
61
|
+
import org.jruby.runtime.builtin.IRubyObject;
|
62
|
+
import org.jruby.exceptions.RaiseException;
|
63
|
+
import org.jruby.runtime.load.BasicLibraryService;
|
64
|
+
|
65
|
+
|
66
|
+
public class BlockenspielUnmixerService implements BasicLibraryService
|
67
|
+
{
|
68
|
+
|
69
|
+
public boolean basicLoad(final Ruby runtime) throws IOException
|
70
|
+
{
|
71
|
+
RubyModule blockenspielModule = runtime.getOrCreateModule("Blockenspiel");
|
72
|
+
RubyModule unmixerModule = runtime.defineModuleUnder("Unmixer", blockenspielModule);
|
73
|
+
unmixerModule.getSingletonClass().defineAnnotatedMethods(BlockenspielUnmixerService.class);
|
74
|
+
return true;
|
75
|
+
}
|
76
|
+
|
77
|
+
|
78
|
+
@JRubyMethod(name = "unmix", required = 2)
|
79
|
+
public synchronized static IRubyObject unmix(IRubyObject self, IRubyObject receiver, IRubyObject module, Block block)
|
80
|
+
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException
|
81
|
+
{
|
82
|
+
RubyModule actualModule = (RubyModule)module;
|
83
|
+
RubyClass klass = receiver.getMetaClass();
|
84
|
+
while (klass != receiver.getMetaClass().getRealClass())
|
85
|
+
{
|
86
|
+
RubyClass superClass = klass.getSuperClass();
|
87
|
+
if (superClass != null && superClass.getNonIncludedClass() == actualModule)
|
88
|
+
{
|
89
|
+
if (actualModule.getSuperClass() != null &&
|
90
|
+
actualModule.getSuperClass() instanceof IncludedModuleWrapper)
|
91
|
+
{
|
92
|
+
removeNestedModule(superClass, actualModule);
|
93
|
+
}
|
94
|
+
setSuperClass(klass, superClass.getSuperClass());
|
95
|
+
invalidateCacheDescendants(klass);
|
96
|
+
}
|
97
|
+
klass = superClass;
|
98
|
+
}
|
99
|
+
return receiver;
|
100
|
+
}
|
101
|
+
|
102
|
+
|
103
|
+
protected synchronized static void removeNestedModule(RubyClass klass, RubyModule module)
|
104
|
+
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException
|
105
|
+
{
|
106
|
+
if ((klass.getSuperClass() instanceof IncludedModuleWrapper) &&
|
107
|
+
((IncludedModuleWrapper)klass.getSuperClass()).getNonIncludedClass() ==
|
108
|
+
((IncludedModuleWrapper)module.getSuperClass()).getNonIncludedClass())
|
109
|
+
{
|
110
|
+
if (module.getSuperClass().getSuperClass() != null &&
|
111
|
+
module.getSuperClass() instanceof IncludedModuleWrapper)
|
112
|
+
{
|
113
|
+
removeNestedModule(klass.getSuperClass(), module.getSuperClass());
|
114
|
+
}
|
115
|
+
setSuperClass(klass, klass.getSuperClass().getSuperClass());
|
116
|
+
}
|
117
|
+
}
|
118
|
+
|
119
|
+
|
120
|
+
protected synchronized static void setSuperClass(RubyModule klass, RubyModule superClass)
|
121
|
+
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException
|
122
|
+
{
|
123
|
+
Method method = RubyModule.class.getDeclaredMethod("setSuperClass",
|
124
|
+
new Class[] {RubyClass.class} );
|
125
|
+
method.setAccessible(true);
|
126
|
+
Object[] superClassArg = new Object[] { superClass };
|
127
|
+
method.invoke(klass, superClassArg);
|
128
|
+
}
|
129
|
+
|
130
|
+
|
131
|
+
protected synchronized static void invalidateCacheDescendants(RubyModule klass)
|
132
|
+
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException
|
133
|
+
{
|
134
|
+
Method method = RubyModule.class.getDeclaredMethod("invalidateCacheDescendants", new Class[0]);
|
135
|
+
method.setAccessible(true);
|
136
|
+
method.invoke(klass, new Object[0]);
|
137
|
+
}
|
138
|
+
|
139
|
+
}
|
140
|
+
|
@@ -0,0 +1,86 @@
|
|
1
|
+
/*
|
2
|
+
-----------------------------------------------------------------------------
|
3
|
+
|
4
|
+
Blockenspiel unmixer (MRI implementation)
|
5
|
+
|
6
|
+
-----------------------------------------------------------------------------
|
7
|
+
Copyright 2008-2009 Daniel Azuma
|
8
|
+
|
9
|
+
All rights reserved.
|
10
|
+
|
11
|
+
Redistribution and use in source and binary forms, with or without
|
12
|
+
modification, are permitted provided that the following conditions are met:
|
13
|
+
|
14
|
+
* Redistributions of source code must retain the above copyright notice,
|
15
|
+
this list of conditions and the following disclaimer.
|
16
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
17
|
+
this list of conditions and the following disclaimer in the documentation
|
18
|
+
and/or other materials provided with the distribution.
|
19
|
+
* Neither the name of the copyright holder, nor the names of any other
|
20
|
+
contributors to this software, may be used to endorse or promote products
|
21
|
+
derived from this software without specific prior written permission.
|
22
|
+
|
23
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
24
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
25
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
26
|
+
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
27
|
+
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
28
|
+
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
29
|
+
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
30
|
+
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
31
|
+
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
32
|
+
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
33
|
+
POSSIBILITY OF SUCH DAMAGE.
|
34
|
+
-----------------------------------------------------------------------------
|
35
|
+
*/
|
36
|
+
|
37
|
+
|
38
|
+
/*
|
39
|
+
This implementation based on Mixology,
|
40
|
+
written by Patrick Farley, anonymous z, Dan Manges, and Clint Bishop.
|
41
|
+
http://rubyforge.org/projects/mixology
|
42
|
+
http://github.com/dan-manges/mixology/tree/master
|
43
|
+
|
44
|
+
It has been stripped down and modified for compatibility with Ruby 1.9.
|
45
|
+
*/
|
46
|
+
|
47
|
+
|
48
|
+
#include "ruby.h"
|
49
|
+
|
50
|
+
|
51
|
+
#ifndef RCLASS_SUPER
|
52
|
+
#define RCLASS_SUPER(c) (RCLASS(c)->super)
|
53
|
+
#endif
|
54
|
+
|
55
|
+
|
56
|
+
static void remove_nested_module(VALUE klass, VALUE module) {
|
57
|
+
if (RBASIC(RCLASS_SUPER(klass))->klass == RBASIC(RCLASS_SUPER(module))->klass) {
|
58
|
+
if (RCLASS_SUPER(RCLASS_SUPER(module)) && BUILTIN_TYPE(RCLASS_SUPER(module)) == T_ICLASS) {
|
59
|
+
remove_nested_module(RCLASS_SUPER(klass), RCLASS_SUPER(module));
|
60
|
+
}
|
61
|
+
RCLASS_SUPER(klass) = RCLASS_SUPER(RCLASS_SUPER(klass));
|
62
|
+
}
|
63
|
+
}
|
64
|
+
|
65
|
+
|
66
|
+
static VALUE do_unmix(VALUE self, VALUE receiver, VALUE module) {
|
67
|
+
VALUE klass = RBASIC(receiver)->klass;
|
68
|
+
while (klass != rb_class_real(klass)) {
|
69
|
+
VALUE super = RCLASS_SUPER(klass);
|
70
|
+
if (BUILTIN_TYPE(super) == T_ICLASS && RBASIC(super)->klass == module) {
|
71
|
+
if (RCLASS_SUPER(module) && BUILTIN_TYPE(RCLASS_SUPER(module)) == T_ICLASS) {
|
72
|
+
remove_nested_module(super, module);
|
73
|
+
}
|
74
|
+
RCLASS_SUPER(klass) = RCLASS_SUPER(super);
|
75
|
+
rb_clear_cache();
|
76
|
+
}
|
77
|
+
klass = super;
|
78
|
+
}
|
79
|
+
return receiver;
|
80
|
+
}
|
81
|
+
|
82
|
+
|
83
|
+
void Init_unmixer() {
|
84
|
+
VALUE container = rb_singleton_class(rb_define_module_under(rb_define_module("Blockenspiel"), "Unmixer"));
|
85
|
+
rb_define_method(container, "unmix", do_unmix, 2);
|
86
|
+
}
|
data/lib/blockenspiel.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
# -----------------------------------------------------------------------------
|
2
|
+
#
|
3
|
+
# Blockenspiel entry point
|
4
|
+
#
|
5
|
+
# -----------------------------------------------------------------------------
|
6
|
+
# Copyright 2008 Daniel Azuma
|
7
|
+
#
|
8
|
+
# All rights reserved.
|
9
|
+
#
|
10
|
+
# Redistribution and use in source and binary forms, with or without
|
11
|
+
# modification, are permitted provided that the following conditions are met:
|
12
|
+
#
|
13
|
+
# * Redistributions of source code must retain the above copyright notice,
|
14
|
+
# this list of conditions and the following disclaimer.
|
15
|
+
# * Redistributions in binary form must reproduce the above copyright notice,
|
16
|
+
# this list of conditions and the following disclaimer in the documentation
|
17
|
+
# and/or other materials provided with the distribution.
|
18
|
+
# * Neither the name of the copyright holder, nor the names of any other
|
19
|
+
# contributors to this software, may be used to endorse or promote products
|
20
|
+
# derived from this software without specific prior written permission.
|
21
|
+
#
|
22
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
23
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
24
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
25
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
26
|
+
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
27
|
+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
28
|
+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
29
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
30
|
+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
31
|
+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
32
|
+
# POSSIBILITY OF SUCH DAMAGE.
|
33
|
+
# -----------------------------------------------------------------------------
|
34
|
+
;
|
35
|
+
|
36
|
+
|
37
|
+
if RUBY_PLATFORM =~ /java/
|
38
|
+
require "blockenspiel_unmixer"
|
39
|
+
else
|
40
|
+
require "#{File.dirname(__FILE__)}/blockenspiel/unmixer"
|
41
|
+
end
|
42
|
+
require "#{File.dirname(__FILE__)}/blockenspiel/impl"
|
43
|
+
require "#{File.dirname(__FILE__)}/blockenspiel/version"
|
@@ -0,0 +1,670 @@
|
|
1
|
+
# -----------------------------------------------------------------------------
|
2
|
+
#
|
3
|
+
# Blockenspiel implementation
|
4
|
+
#
|
5
|
+
# -----------------------------------------------------------------------------
|
6
|
+
# Copyright 2008-2009 Daniel Azuma
|
7
|
+
#
|
8
|
+
# All rights reserved.
|
9
|
+
#
|
10
|
+
# Redistribution and use in source and binary forms, with or without
|
11
|
+
# modification, are permitted provided that the following conditions are met:
|
12
|
+
#
|
13
|
+
# * Redistributions of source code must retain the above copyright notice,
|
14
|
+
# this list of conditions and the following disclaimer.
|
15
|
+
# * Redistributions in binary form must reproduce the above copyright notice,
|
16
|
+
# this list of conditions and the following disclaimer in the documentation
|
17
|
+
# and/or other materials provided with the distribution.
|
18
|
+
# * Neither the name of the copyright holder, nor the names of any other
|
19
|
+
# contributors to this software, may be used to endorse or promote products
|
20
|
+
# derived from this software without specific prior written permission.
|
21
|
+
#
|
22
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
23
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
24
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
25
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
26
|
+
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
27
|
+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
28
|
+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
29
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
30
|
+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
31
|
+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
32
|
+
# POSSIBILITY OF SUCH DAMAGE.
|
33
|
+
# -----------------------------------------------------------------------------
|
34
|
+
;
|
35
|
+
|
36
|
+
|
37
|
+
require 'thread'
|
38
|
+
|
39
|
+
|
40
|
+
# == Blockenspiel
|
41
|
+
#
|
42
|
+
# The Blockenspiel module provides a namespace for Blockenspiel, as well as
|
43
|
+
# the main entry point method "invoke".
|
44
|
+
|
45
|
+
module Blockenspiel
|
46
|
+
|
47
|
+
|
48
|
+
# Base exception for all exceptions raised by Blockenspiel
|
49
|
+
|
50
|
+
class BlockenspielError < RuntimeError
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
# This exception is rasied when attempting to use the <tt>:proxy</tt> or
|
55
|
+
# <tt>:mixin</tt> parameterless behavior with a target that does not have
|
56
|
+
# the DSL module included. It is an error made by the DSL implementor.
|
57
|
+
|
58
|
+
class DSLMissingError < BlockenspielError
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
# This exception is raised when the block provided does not take the
|
63
|
+
# expected number of parameters. It is an error made by the caller.
|
64
|
+
|
65
|
+
class BlockParameterError < BlockenspielError
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
# === DSL setup methods
|
70
|
+
#
|
71
|
+
# These class methods are available after you have included the
|
72
|
+
# Blockenspiel::DSL module.
|
73
|
+
#
|
74
|
+
# By default, a class that has DSL capability will automatically make
|
75
|
+
# all public methods available to parameterless blocks, except for the
|
76
|
+
# +initialize+ method, any methods whose names begin with an underscore,
|
77
|
+
# and any methods whose names end with an equals sign.
|
78
|
+
#
|
79
|
+
# If you want to change this behavior, use the directives defined here to
|
80
|
+
# control exactly which methods are available to parameterless blocks.
|
81
|
+
|
82
|
+
module DSLSetupMethods
|
83
|
+
|
84
|
+
# Called when DSLSetupMethods extends a class.
|
85
|
+
# This sets up the current class, and adds a hook that causes
|
86
|
+
# any subclass of the current class to also be set up.
|
87
|
+
|
88
|
+
# :stopdoc:
|
89
|
+
def self.extended(klass_)
|
90
|
+
unless klass_.instance_variable_defined?(:@_blockenspiel_module)
|
91
|
+
_setup_class(klass_)
|
92
|
+
def klass_.inherited(subklass_)
|
93
|
+
Blockenspiel::DSLSetupMethods._setup_class(subklass_)
|
94
|
+
super
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
# :startdoc:
|
99
|
+
|
100
|
+
|
101
|
+
# Set up a class.
|
102
|
+
# Creates a DSL module for this class, optionally delegating to the superclass's module.
|
103
|
+
# Also initializes the class's methods hash and active flag.
|
104
|
+
|
105
|
+
def self._setup_class(klass_) # :nodoc:
|
106
|
+
superclass_ = klass_.superclass
|
107
|
+
superclass_ = nil unless superclass_.respond_to?(:_get_blockenspiel_module)
|
108
|
+
mod_ = Module.new
|
109
|
+
if superclass_
|
110
|
+
mod_.module_eval do
|
111
|
+
include superclass_._get_blockenspiel_module
|
112
|
+
end
|
113
|
+
end
|
114
|
+
klass_.instance_variable_set(:@_blockenspiel_superclass, superclass_)
|
115
|
+
klass_.instance_variable_set(:@_blockenspiel_module, mod_)
|
116
|
+
klass_.instance_variable_set(:@_blockenspiel_methods, Hash.new)
|
117
|
+
klass_.instance_variable_set(:@_blockenspiel_active, nil)
|
118
|
+
end
|
119
|
+
|
120
|
+
|
121
|
+
# Hook called when a method is added.
|
122
|
+
# This automatically makes the method a DSL method according to the current setting.
|
123
|
+
|
124
|
+
def method_added(symbol_) # :nodoc:
|
125
|
+
if @_blockenspiel_active
|
126
|
+
dsl_method(symbol_)
|
127
|
+
elsif @_blockenspiel_active.nil?
|
128
|
+
if symbol_ != :initialize && symbol_.to_s !~ /^_/ && symbol_.to_s !~ /=$/
|
129
|
+
dsl_method(symbol_)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
super
|
133
|
+
end
|
134
|
+
|
135
|
+
|
136
|
+
# Get this class's corresponding DSL module
|
137
|
+
|
138
|
+
def _get_blockenspiel_module # :nodoc:
|
139
|
+
@_blockenspiel_module
|
140
|
+
end
|
141
|
+
|
142
|
+
|
143
|
+
# Get information on the given DSL method name.
|
144
|
+
# Possible values are the name of the delegate method, false for method disabled,
|
145
|
+
# or nil for method never defined.
|
146
|
+
|
147
|
+
def _get_blockenspiel_delegate(name_) # :nodoc:
|
148
|
+
delegate_ = @_blockenspiel_methods[name_]
|
149
|
+
if delegate_.nil? && @_blockenspiel_superclass
|
150
|
+
@_blockenspiel_superclass._get_blockenspiel_delegate(name_)
|
151
|
+
else
|
152
|
+
delegate_
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
|
157
|
+
# Make a particular method available to parameterless DSL blocks.
|
158
|
+
#
|
159
|
+
# To explicitly make a method available to parameterless blocks:
|
160
|
+
# dsl_method :my_method
|
161
|
+
#
|
162
|
+
# To explicitly exclude a method from parameterless blocks:
|
163
|
+
# dsl_method :my_method, false
|
164
|
+
#
|
165
|
+
# To explicitly make a method available to parameterless blocks, but
|
166
|
+
# point it to a method of a different name on the target class:
|
167
|
+
# dsl_method :my_method, :target_class_method
|
168
|
+
|
169
|
+
def dsl_method(name_, delegate_=nil)
|
170
|
+
name_ = name_.to_sym
|
171
|
+
if delegate_
|
172
|
+
delegate_ = delegate_.to_sym
|
173
|
+
elsif delegate_.nil?
|
174
|
+
delegate_ = name_
|
175
|
+
end
|
176
|
+
@_blockenspiel_methods[name_] = delegate_
|
177
|
+
unless @_blockenspiel_module.public_method_defined?(name_)
|
178
|
+
@_blockenspiel_module.module_eval("
|
179
|
+
def #{name_}(*params_, &block_)
|
180
|
+
val_ = Blockenspiel._target_dispatch(self, :#{name_}, params_, block_)
|
181
|
+
val_ == Blockenspiel::TARGET_MISMATCH ? super(*params_, &block_) : val_
|
182
|
+
end
|
183
|
+
")
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
|
188
|
+
# Control the behavior of methods with respect to parameterless blocks,
|
189
|
+
# or make a list of methods available to parameterless blocks in bulk.
|
190
|
+
#
|
191
|
+
# To enable automatic exporting of methods to parameterless blocks.
|
192
|
+
# After executing this command, all public methods defined in the class
|
193
|
+
# will be available on parameterless blocks, until
|
194
|
+
# <tt>dsl_methods false</tt> is called:
|
195
|
+
# dsl_methods true
|
196
|
+
#
|
197
|
+
# To disable automatic exporting of methods to parameterless blocks.
|
198
|
+
# After executing this command, methods defined in this class will be
|
199
|
+
# excluded from parameterless blocks, until <tt>dsl_methods true</tt>
|
200
|
+
# is called:
|
201
|
+
# dsl_methods false
|
202
|
+
#
|
203
|
+
# To make a list of methods available to parameterless blocks in bulk:
|
204
|
+
# dsl_methods :my_method1, :my_method2, ...
|
205
|
+
#
|
206
|
+
# You can also point dsl methods to a method of a different name on the
|
207
|
+
# target class, by using a hash syntax, as follows:
|
208
|
+
# dsl_methods :my_method1 => :target_class_method1,
|
209
|
+
# :my_method2 => :target_class_method2
|
210
|
+
#
|
211
|
+
# You can mix non-renamed and renamed method declarations as long as
|
212
|
+
# the renamed (hash) methods are at the end. e.g.:
|
213
|
+
# dsl_methods :my_method1, :my_method2 => :target_class_method2
|
214
|
+
|
215
|
+
def dsl_methods(*names_)
|
216
|
+
if names_.size == 0 || names_ == [true]
|
217
|
+
@_blockenspiel_active = true
|
218
|
+
elsif names_ == [false]
|
219
|
+
@_blockenspiel_active = false
|
220
|
+
else
|
221
|
+
if names_.last.kind_of?(Hash)
|
222
|
+
names_.pop.each do |name_, delegate_|
|
223
|
+
dsl_method(name_, delegate_)
|
224
|
+
end
|
225
|
+
end
|
226
|
+
names_.each do |name_|
|
227
|
+
dsl_method(name_, name_)
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
end
|
233
|
+
|
234
|
+
|
235
|
+
# === DSL activation module
|
236
|
+
#
|
237
|
+
# Include this module in a class to mark this class as a DSL class and
|
238
|
+
# make it possible for its methods to be called from a block that does not
|
239
|
+
# take a parameter.
|
240
|
+
#
|
241
|
+
# After you include this module, you can use the directives defined in
|
242
|
+
# DSLSetupMethods to control what methods are available to DSL blocks
|
243
|
+
# that do not take parameters.
|
244
|
+
|
245
|
+
module DSL
|
246
|
+
|
247
|
+
def self.included(klass_) # :nodoc:
|
248
|
+
klass_.extend(Blockenspiel::DSLSetupMethods)
|
249
|
+
end
|
250
|
+
|
251
|
+
end
|
252
|
+
|
253
|
+
|
254
|
+
# === DSL activation base class
|
255
|
+
#
|
256
|
+
# Subclasses of this base class are considered DSL classes.
|
257
|
+
# Methods of the class can be made available to be called from a block that
|
258
|
+
# doesn't take an explicit block parameter.
|
259
|
+
# You may use the directives defined in DSLSetupMethods to control how
|
260
|
+
# methods of the class are handled in such blocks.
|
261
|
+
#
|
262
|
+
# Subclassing this base class is functionally equivalent to simply
|
263
|
+
# including Blockenspiel::DSL in the class.
|
264
|
+
|
265
|
+
class Base
|
266
|
+
|
267
|
+
include Blockenspiel::DSL
|
268
|
+
|
269
|
+
end
|
270
|
+
|
271
|
+
|
272
|
+
# Class for proxy delegators.
|
273
|
+
# The proxy behavior creates one of these delegators, mixes in the dsl
|
274
|
+
# methods, and uses instance_eval to invoke the block. This class delegates
|
275
|
+
# non-handled methods to the context object.
|
276
|
+
|
277
|
+
class ProxyDelegator # :nodoc:
|
278
|
+
|
279
|
+
def method_missing(symbol_, *params_, &block_)
|
280
|
+
Blockenspiel._proxy_dispatch(self, symbol_, params_, block_)
|
281
|
+
end
|
282
|
+
|
283
|
+
end
|
284
|
+
|
285
|
+
|
286
|
+
# === Dynamically construct a target
|
287
|
+
#
|
288
|
+
# These methods are available in a block passed to Blockenspiel#invoke and
|
289
|
+
# can be used to dynamically define what methods are available from a block.
|
290
|
+
# See Blockenspiel#invoke for more information.
|
291
|
+
|
292
|
+
class Builder
|
293
|
+
|
294
|
+
include Blockenspiel::DSL
|
295
|
+
|
296
|
+
|
297
|
+
# This is a base class for dynamically constructed targets.
|
298
|
+
# The actual target class is an anonymous subclass of this base class.
|
299
|
+
|
300
|
+
class Target # :nodoc:
|
301
|
+
|
302
|
+
include Blockenspiel::DSL
|
303
|
+
|
304
|
+
|
305
|
+
# Add a method specification to the subclass.
|
306
|
+
|
307
|
+
def self._add_methodinfo(name_, block_, yields_)
|
308
|
+
(@_blockenspiel_methodinfo ||= Hash.new)[name_] = [block_, yields_]
|
309
|
+
module_eval("
|
310
|
+
def #{name_}(*params_, &block_)
|
311
|
+
self.class._invoke_methodinfo(:#{name_}, params_, block_)
|
312
|
+
end
|
313
|
+
")
|
314
|
+
end
|
315
|
+
|
316
|
+
|
317
|
+
# Attempt to invoke the given method on the subclass.
|
318
|
+
|
319
|
+
def self._invoke_methodinfo(name_, params_, block_)
|
320
|
+
info_ = @_blockenspiel_methodinfo[name_]
|
321
|
+
case info_[1]
|
322
|
+
when :first
|
323
|
+
params_.unshift(block_)
|
324
|
+
when :last
|
325
|
+
params_.push(block_)
|
326
|
+
end
|
327
|
+
info_[0].call(*params_)
|
328
|
+
end
|
329
|
+
|
330
|
+
end
|
331
|
+
|
332
|
+
|
333
|
+
# Sets up the dynamic target class.
|
334
|
+
|
335
|
+
def initialize # :nodoc:
|
336
|
+
@target_class = Class.new(Blockenspiel::Builder::Target)
|
337
|
+
@target_class.dsl_methods(false)
|
338
|
+
end
|
339
|
+
|
340
|
+
|
341
|
+
# Creates a new instance of the dynamic target class
|
342
|
+
|
343
|
+
def _create_target # :nodoc:
|
344
|
+
@target_class.new
|
345
|
+
end
|
346
|
+
|
347
|
+
|
348
|
+
# === Declare a DSL method.
|
349
|
+
#
|
350
|
+
# This call creates a method that can be called from the DSL block.
|
351
|
+
# Provide a name for the method, and a block defining the method's
|
352
|
+
# implementation. You may also provided a list of options as follows:
|
353
|
+
#
|
354
|
+
# The <tt>:block</tt> option controls how the generated method reports
|
355
|
+
# any block provided by the caller. If set to +false+ or not given, any
|
356
|
+
# caller-provided block is ignored. If set to <tt>:first</tt>, the
|
357
|
+
# block is _prepended_ (as a +Proc+ object) to the parameters passed to
|
358
|
+
# the method's implementing block. If set to <tt>:last</tt>, the block
|
359
|
+
# is _appended_. In either case, if the caller does not provide a block,
|
360
|
+
# a value of +nil+ is pre- or appended to the parameter list. A value of
|
361
|
+
# +true+ is equivalent to <tt>:first</tt>.
|
362
|
+
# (This is a workaround for the fact that blocks cannot themselves take
|
363
|
+
# block parameters in Ruby 1.8.)
|
364
|
+
#
|
365
|
+
# For example, to create a method named "foo" that takes one parameter
|
366
|
+
# and a block, do this:
|
367
|
+
#
|
368
|
+
# add_method(:foo, :block => :first) do |block, param|
|
369
|
+
# puts "foo called with parameter "+param.inspect
|
370
|
+
# puts "the block returned "+block.call.inspect
|
371
|
+
# end
|
372
|
+
#
|
373
|
+
# In your DSL, you can then call:
|
374
|
+
#
|
375
|
+
# foo("hello"){ "a value" }
|
376
|
+
#
|
377
|
+
# By default, a method of the same name is also made available to
|
378
|
+
# parameterless blocks. To change the name of the parameterless method,
|
379
|
+
# provide its name as the value of the <tt>:dsl_method</tt> option.
|
380
|
+
# To disable this method for parameterless blocks, set the
|
381
|
+
# <tt>:dsl_method</tt> option to +false+.
|
382
|
+
#
|
383
|
+
# For historical reasons, the <tt>:mixin</tt> option is an alias for
|
384
|
+
# <tt>:dsl_method</tt>. However, its use is deprecated.
|
385
|
+
#
|
386
|
+
# The <tt>:receive_block</tt> option is also supported for historical
|
387
|
+
# reasons, but its use is deprecated. Setting <tt>:receive_block</tt> to
|
388
|
+
# +true+ is equivalent to setting <tt>:block</tt> to <tt>:last</tt>.
|
389
|
+
|
390
|
+
def add_method(name_, opts_={}, &block_)
|
391
|
+
receive_block_ = opts_[:receive_block] ? :last : opts_[:block]
|
392
|
+
receive_block_ = :first if receive_block_ && receive_block_ != :last
|
393
|
+
@target_class._add_methodinfo(name_, block_, receive_block_)
|
394
|
+
dsl_method_name_ = opts_[:dsl_method] || opts_[:mixin]
|
395
|
+
if dsl_method_name_ != false
|
396
|
+
dsl_method_name_ = name_ if dsl_method_name_.nil? || dsl_method_name_ == true
|
397
|
+
@target_class.dsl_method(dsl_method_name_, name_)
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
401
|
+
end
|
402
|
+
|
403
|
+
|
404
|
+
# :stopdoc:
|
405
|
+
TARGET_MISMATCH = Object.new
|
406
|
+
# :startdoc:
|
407
|
+
|
408
|
+
@_target_stacks = Hash.new
|
409
|
+
@_mixin_counts = Hash.new
|
410
|
+
@_proxy_delegators = Hash.new
|
411
|
+
@_mutex = Mutex.new
|
412
|
+
|
413
|
+
|
414
|
+
# === Invoke a given block.
|
415
|
+
#
|
416
|
+
# This is the meat of Blockenspiel. Call this function to invoke a block
|
417
|
+
# provided by the user of your API.
|
418
|
+
#
|
419
|
+
# Normally, this method will check the block's arity to see whether it
|
420
|
+
# takes a parameter. If so, it will pass the given target to the block.
|
421
|
+
# If the block takes no parameter, and the given target is an instance of
|
422
|
+
# a class with DSL capability, the DSL methods are made available on the
|
423
|
+
# caller's self object so they may be called without a block parameter.
|
424
|
+
#
|
425
|
+
# Recognized options include:
|
426
|
+
#
|
427
|
+
# <tt>:parameterless</tt>::
|
428
|
+
# If set to false, disables parameterless blocks and always attempts to
|
429
|
+
# pass a parameter to the block. Otherwise, you may set it to one of
|
430
|
+
# three behaviors for parameterless blocks: <tt>:mixin</tt> (the
|
431
|
+
# default), <tt>:instance</tt>, and <tt>:proxy</tt>. See below for
|
432
|
+
# detailed descriptions of these behaviors.
|
433
|
+
# <tt>:parameter</tt>::
|
434
|
+
# If set to false, disables blocks with parameters, and always attempts
|
435
|
+
# to use parameterless blocks. Default is true, enabling parameter mode.
|
436
|
+
#
|
437
|
+
# The following values control the precise behavior of parameterless
|
438
|
+
# blocks. These are values for the <tt>:parameterless</tt> option.
|
439
|
+
#
|
440
|
+
# <tt>:mixin</tt>::
|
441
|
+
# This is the default behavior. DSL methods from the target are
|
442
|
+
# temporarily overlayed on the caller's +self+ object, but +self+ still
|
443
|
+
# points to the same object, so the helper methods and instance
|
444
|
+
# variables from the caller's closure remain available. The DSL methods
|
445
|
+
# are removed when the block completes.
|
446
|
+
# <tt>:instance</tt>::
|
447
|
+
# This behavior actually changes +self+ to the target object using
|
448
|
+
# <tt>instance_eval</tt>. Thus, the caller loses access to its own
|
449
|
+
# helper methods and instance variables, and instead gains access to the
|
450
|
+
# target object's instance variables. The target object's methods are
|
451
|
+
# not modified: this behavior does not apply any DSL method changes
|
452
|
+
# specified using <tt>dsl_method</tt> directives.
|
453
|
+
# <tt>:proxy</tt>::
|
454
|
+
# This behavior changes +self+ to a proxy object created by applying the
|
455
|
+
# DSL methods to an empty object, whose <tt>method_missing</tt> points
|
456
|
+
# back at the block's context. This behavior is a compromise between
|
457
|
+
# instance and mixin. As with instance, +self+ is changed, so the caller
|
458
|
+
# loses access to its own instance variables. However, the caller's own
|
459
|
+
# methods should still be available since any methods not handled by the
|
460
|
+
# DSL are delegated back to the caller. Also, as with mixin, the target
|
461
|
+
# object's instance variables are not available (and thus cannot be
|
462
|
+
# clobbered) in the block, and the transformations specified by
|
463
|
+
# <tt>dsl_method</tt> directives are honored.
|
464
|
+
#
|
465
|
+
# === Dynamic target generation
|
466
|
+
#
|
467
|
+
# It is also possible to dynamically generate a target object by passing
|
468
|
+
# a block to this method. This is probably best illustrated by example:
|
469
|
+
#
|
470
|
+
# Blockenspiel.invoke(block) do
|
471
|
+
# add_method(:set_foo) do |value|
|
472
|
+
# my_foo = value
|
473
|
+
# end
|
474
|
+
# add_method(:set_things_from_block, :receive_block => true) do |value,blk|
|
475
|
+
# my_foo = value
|
476
|
+
# my_bar = blk.call
|
477
|
+
# end
|
478
|
+
# end
|
479
|
+
#
|
480
|
+
# The above is roughly equivalent to invoking Blockenspiel with an
|
481
|
+
# instance of this target class:
|
482
|
+
#
|
483
|
+
# class MyFooTarget
|
484
|
+
# include Blockenspiel::DSL
|
485
|
+
# def set_foo(value)
|
486
|
+
# set_my_foo_from(value)
|
487
|
+
# end
|
488
|
+
# def set_things_from_block(value)
|
489
|
+
# set_my_foo_from(value)
|
490
|
+
# set_my_bar_from(yield)
|
491
|
+
# end
|
492
|
+
# end
|
493
|
+
#
|
494
|
+
# Blockenspiel.invoke(block, MyFooTarget.new)
|
495
|
+
#
|
496
|
+
# The obvious advantage of using dynamic object generation is that you are
|
497
|
+
# creating methods using closures, which provides the opportunity to, for
|
498
|
+
# example, modify closure variables such as my_foo. This is more difficult
|
499
|
+
# to do when you create a target class since its methods do not have access
|
500
|
+
# to outside data. Hence, in the above example, we hand-waved, assuming the
|
501
|
+
# existence of some method called "set_my_foo_from".
|
502
|
+
#
|
503
|
+
# The disadvantage is performance. If you dynamically generate a target
|
504
|
+
# object, it involves parsing and creating a new class whenever it is
|
505
|
+
# invoked. Thus, it is recommended that you use this technique for calls
|
506
|
+
# that are not used repeatedly, such as one-time configuration.
|
507
|
+
#
|
508
|
+
# See the Blockenspiel::Builder class for more details on add_method.
|
509
|
+
#
|
510
|
+
# (And yes, you guessed it: this API is a DSL block, and is itself
|
511
|
+
# implemented using Blockenspiel.)
|
512
|
+
|
513
|
+
def self.invoke(block_, target_=nil, opts_={}, &builder_block_)
|
514
|
+
|
515
|
+
unless block_
|
516
|
+
raise ArgumentError, "Block expected"
|
517
|
+
end
|
518
|
+
parameter_ = opts_[:parameter]
|
519
|
+
parameterless_ = opts_[:parameterless]
|
520
|
+
|
521
|
+
# Handle no-target behavior
|
522
|
+
if parameter_ == false && parameterless_ == false
|
523
|
+
if block_.arity != 0 && block_.arity != -1
|
524
|
+
raise Blockenspiel::BlockParameterError, "Block should not take parameters"
|
525
|
+
end
|
526
|
+
return block_.call
|
527
|
+
end
|
528
|
+
|
529
|
+
# Perform dynamic target generation if requested
|
530
|
+
if builder_block_
|
531
|
+
opts_ = target_ || opts_
|
532
|
+
builder_ = Blockenspiel::Builder.new
|
533
|
+
invoke(builder_block_, builder_)
|
534
|
+
target_ = builder_._create_target
|
535
|
+
end
|
536
|
+
|
537
|
+
# Handle parametered block case
|
538
|
+
if parameter_ != false && block_.arity == 1 || parameterless_ == false
|
539
|
+
if block_.arity != 1
|
540
|
+
raise Blockenspiel::BlockParameterError, "Block should take exactly one parameter"
|
541
|
+
end
|
542
|
+
return block_.call(target_)
|
543
|
+
end
|
544
|
+
|
545
|
+
# Check arity for parameterless case
|
546
|
+
if block_.arity != 0 && block_.arity != -1
|
547
|
+
raise Blockenspiel::BlockParameterError, "Block should not take parameters"
|
548
|
+
end
|
549
|
+
|
550
|
+
# Handle instance-eval behavior
|
551
|
+
if parameterless_ == :instance
|
552
|
+
return target_.instance_eval(&block_)
|
553
|
+
end
|
554
|
+
|
555
|
+
# Get the module of dsl methods
|
556
|
+
mod_ = target_.class._get_blockenspiel_module rescue nil
|
557
|
+
unless mod_
|
558
|
+
raise Blockenspiel::DSLMissingError
|
559
|
+
end
|
560
|
+
|
561
|
+
# Get the block's calling context object
|
562
|
+
object_ = Kernel.eval('self', block_.binding)
|
563
|
+
|
564
|
+
# Handle proxy behavior
|
565
|
+
if parameterless_ == :proxy
|
566
|
+
|
567
|
+
# Create proxy object
|
568
|
+
proxy_ = Blockenspiel::ProxyDelegator.new
|
569
|
+
proxy_.extend(mod_)
|
570
|
+
|
571
|
+
# Store the target and proxy object so dispatchers can get them
|
572
|
+
proxy_delegator_key_ = proxy_.object_id
|
573
|
+
target_stack_key_ = [Thread.current.object_id, proxy_.object_id]
|
574
|
+
@_proxy_delegators[proxy_delegator_key_] = object_
|
575
|
+
@_target_stacks[target_stack_key_] = [target_]
|
576
|
+
|
577
|
+
begin
|
578
|
+
|
579
|
+
# Call the block with the proxy as self
|
580
|
+
return proxy_.instance_eval(&block_)
|
581
|
+
|
582
|
+
ensure
|
583
|
+
|
584
|
+
# Clean up the dispatcher information
|
585
|
+
@_proxy_delegators.delete(proxy_delegator_key_)
|
586
|
+
@_target_stacks.delete(target_stack_key_)
|
587
|
+
|
588
|
+
end
|
589
|
+
|
590
|
+
end
|
591
|
+
|
592
|
+
# Handle mixin behavior (default)
|
593
|
+
|
594
|
+
# Create hash keys
|
595
|
+
mixin_count_key_ = [object_.object_id, mod_.object_id]
|
596
|
+
target_stack_key_ = [Thread.current.object_id, object_.object_id]
|
597
|
+
|
598
|
+
# Store the target for inheriting.
|
599
|
+
# We maintain a target call stack per thread.
|
600
|
+
target_stack_ = @_target_stacks[target_stack_key_] ||= Array.new
|
601
|
+
target_stack_.push(target_)
|
602
|
+
|
603
|
+
# Mix this module into the object, if required.
|
604
|
+
# This ensures that we keep track of the number of requests to
|
605
|
+
# mix this module in, from nested blocks and possibly multiple threads.
|
606
|
+
@_mutex.synchronize do
|
607
|
+
count_ = @_mixin_counts[mixin_count_key_]
|
608
|
+
if count_
|
609
|
+
@_mixin_counts[mixin_count_key_] = count_ + 1
|
610
|
+
else
|
611
|
+
@_mixin_counts[mixin_count_key_] = 1
|
612
|
+
object_.extend(mod_)
|
613
|
+
end
|
614
|
+
end
|
615
|
+
|
616
|
+
begin
|
617
|
+
|
618
|
+
# Now call the block
|
619
|
+
return block_.call
|
620
|
+
|
621
|
+
ensure
|
622
|
+
|
623
|
+
# Clean up the target stack
|
624
|
+
target_stack_.pop
|
625
|
+
@_target_stacks.delete(target_stack_key_) if target_stack_.size == 0
|
626
|
+
|
627
|
+
# Remove the mixin from the object, if required.
|
628
|
+
@_mutex.synchronize do
|
629
|
+
count_ = @_mixin_counts[mixin_count_key_]
|
630
|
+
if count_ == 1
|
631
|
+
@_mixin_counts.delete(mixin_count_key_)
|
632
|
+
Blockenspiel::Unmixer.unmix(object_, mod_)
|
633
|
+
else
|
634
|
+
@_mixin_counts[mixin_count_key_] = count_ - 1
|
635
|
+
end
|
636
|
+
end
|
637
|
+
|
638
|
+
end
|
639
|
+
|
640
|
+
end
|
641
|
+
|
642
|
+
|
643
|
+
# This implements the mapping between DSL module methods and target object methods.
|
644
|
+
# We look up the current target object based on the current thread.
|
645
|
+
# Then we attempt to call the given method on that object.
|
646
|
+
# If we can't find an appropriate method to call, return the special value TARGET_MISMATCH.
|
647
|
+
|
648
|
+
def self._target_dispatch(object_, name_, params_, block_) # :nodoc:
|
649
|
+
target_stack_ = @_target_stacks[[Thread.current.object_id, object_.object_id]]
|
650
|
+
return Blockenspiel::TARGET_MISMATCH unless target_stack_
|
651
|
+
target_stack_.reverse_each do |target_|
|
652
|
+
target_class_ = target_.class
|
653
|
+
delegate_ = target_class_._get_blockenspiel_delegate(name_)
|
654
|
+
if delegate_ && target_class_.public_method_defined?(delegate_)
|
655
|
+
return target_.send(delegate_, *params_, &block_)
|
656
|
+
end
|
657
|
+
end
|
658
|
+
return Blockenspiel::TARGET_MISMATCH
|
659
|
+
end
|
660
|
+
|
661
|
+
|
662
|
+
# This implements the proxy fall-back behavior.
|
663
|
+
# We look up the context object, and call the given method on that object.
|
664
|
+
|
665
|
+
def self._proxy_dispatch(proxy_, name_, params_, block_) # :nodoc:
|
666
|
+
@_proxy_delegators[proxy_.object_id].send(name_, *params_, &block_)
|
667
|
+
end
|
668
|
+
|
669
|
+
|
670
|
+
end
|