blockenspiel 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,10 @@
1
+ === 0.2.0 / 2009-04-15
2
+
3
+ * Now compatible with Ruby 1.9.
4
+ * Now compatible with JRuby 1.2.
5
+ * No longer requires the mixology gem.
6
+ * Building no longer requires hoe.
7
+
1
8
  === 0.1.1 / 2008-11-06
2
9
 
3
10
  * Added ability to pass the block as the first parameter in
@@ -669,9 +669,9 @@ What we would really like is a way to add methods to just one object temporarily
669
669
  s1.foo # prints "foo called"
670
670
  s2.foo # NameError: s2 is unchanged
671
671
 
672
- Unfortunately, there is no way to remove the module from the object. Ruby has no "unextend" capability. This omission led Why to implement it himself as a Ruby language extension, lovingly entitled {mixico}[http://github.com/why/mixico/tree/master]. The name comes from the library's ability to add and remove "mixins" at will. A similar library exists as a gem called {mixology}[http://www.somethingnimble.com/bliki/mixology]. The two libraries use different APIs but perform the same basic function. For the discussion below, I will assume mixico is installed. However, the library I describe in the next section uses mixology because it is available as a gem.
672
+ Unfortunately, there is no way to remove the module from the object. Ruby has no "unextend" capability. This omission led Why to implement it himself as a Ruby language extension called {Mixico}[http://github.com/why/mixico/tree/master]. The name comes from the library's ability to add and remove "mixins" at will. A similar library exists as a gem called {Mixology}[http://www.somethingnimble.com/bliki/mixology]. The two libraries use different APIs but perform the same basic function. For the discussion below, I will assume Mixico is installed. However, the library I describe in the next section uses a custom implementation that is compatible with MRI 1.9 and JRuby.
673
673
 
674
- Using mixico, we can now write the +draw+ method like this:
674
+ Using Mixico, we can now write the +draw+ method like this:
675
675
 
676
676
  def draw(&block)
677
677
  clear!
@@ -758,7 +758,7 @@ As we have seen, the mixin idea seems like it may be a compelling solution, part
758
758
 
759
759
  *Implementation*:
760
760
 
761
- * Install a mixin library such as mixico or mixology.
761
+ * Install a mixin library such as mixico or mixology (or roll your own if necessary).
762
762
  * Define the DSL methods in a module.
763
763
  * Mix the module into the block's context before invoking the block, and remove it afterwards.
764
764
  * Carefully handle any issues involving nested blocks and multithreading while remaining unobtrusive.
@@ -873,7 +873,7 @@ This requires dynamic generation of the proxy class. We could implement it using
873
873
  def add_route(path, options = {})
874
874
  # ...
875
875
 
876
- You can install blockenspiel as a gem for MRI 1.8.x
876
+ You can install blockenspiel as a gem. It is compatible with MRI 1.8.7 or later, MRI 1.9.1 or later, and JRuby 1.2 or later.
877
877
 
878
878
  gem install blockenspiel
879
879
 
@@ -256,7 +256,7 @@ Here's an example:
256
256
  That API is in itself a DSL block, and yes, Blockenspiel uses itself to
257
257
  implement this feature.
258
258
 
259
- By default Blockenspiel uses mixins, which usually exhibit safe and
259
+ By default Blockenspiel uses mixins, which usually exhibit fairly safe and
260
260
  non-surprising behavior. However, there are a few cases when you might
261
261
  want the <tt>instance_eval</tt> behavior anyway. RSpec is a good example of
262
262
  such a case, since the DSL is being used to construct objects, so it makes
@@ -284,9 +284,8 @@ concurrently.
284
284
 
285
285
  === Requirements
286
286
 
287
- * Ruby 1.8.6 or later.
287
+ * Ruby 1.8.7 or later, or JRuby 1.2 or later. Ruby 1.9 compatible.
288
288
  * Rubygems
289
- * mixology gem.
290
289
 
291
290
  === Installation
292
291
 
@@ -296,8 +295,6 @@ concurrently.
296
295
 
297
296
  * Implementing wildcard DSL methods using <tt>method_missing</tt> doesn't
298
297
  work. I haven't yet figured out the right semantics for this case.
299
- * Doesn't yet work in Ruby 1.9 because the mixology gem is not compatible at this point.
300
- * JRuby status not yet known.
301
298
 
302
299
  === Development and support
303
300
 
@@ -311,16 +308,20 @@ Contact the author at dazuma at gmail dot com.
311
308
 
312
309
  === Author / Credits
313
310
 
314
- Blockenspiel is written by Daniel Azuma (http://www.daniel-azuma.com).
311
+ Blockenspiel is written by Daniel Azuma (http://www.daniel-azuma.com/).
315
312
 
316
313
  The mixin implementation is based on a concept by Why The Lucky Stiff.
317
314
  See his 6 October 2008 blog posting,
318
315
  <em>{Mixing Our Way Out Of Instance Eval?}[http://hackety.org/2008/10/06/mixingOurWayOutOfInstanceEval.html]</em>
319
316
  for further discussion.
320
317
 
318
+ The unmixer code is based on {Mixology}[http://rubyforge.org/projects/mixology],
319
+ by Patrick Farley, anonymous z, Dan Manges, and Clint Bishop.
320
+ The code has been stripped down and modified to support MRI 1.9 and JRuby 1.2.
321
+
321
322
  === License
322
323
 
323
- Copyright 2008 Daniel Azuma.
324
+ Copyright 2008-2009 Daniel Azuma.
324
325
 
325
326
  All rights reserved.
326
327
 
data/Rakefile CHANGED
@@ -3,7 +3,7 @@
3
3
  # Blockenspiel Rakefile
4
4
  #
5
5
  # -----------------------------------------------------------------------------
6
- # Copyright 2008 Daniel Azuma
6
+ # Copyright 2008-2009 Daniel Azuma
7
7
  #
8
8
  # All rights reserved.
9
9
  #
@@ -32,21 +32,150 @@
32
32
  # POSSIBILITY OF SUCH DAMAGE.
33
33
  # -----------------------------------------------------------------------------
34
34
 
35
+ require 'rake'
36
+ require 'rake/clean'
37
+ require 'rake/gempackagetask'
38
+ require 'rake/testtask'
39
+ require 'rake/rdoctask'
40
+ require 'rdoc/generator/darkfish'
35
41
 
36
- require 'rubygems'
37
- require 'hoe'
38
- require File.expand_path("#{File.dirname(__FILE__)}/lib/blockenspiel.rb")
39
-
40
- Hoe.new('blockenspiel', Blockenspiel::VERSION_STRING) do |p_|
41
- p_.rubyforge_name = 'virtuoso'
42
- p_.developer('Daniel Azuma', 'dazuma@gmail.com')
43
- p_.author = ['Daniel Azuma']
44
- p_.email = ['dazuma@gmail.com']
45
- p_.test_globs = ['tests/tc_*.rb']
46
- p_.extra_deps = [['mixology', '>= 0.1.0']]
47
- p_.description_sections = ['blockenspiel']
48
- p_.url = 'http://virtuoso.rubyforge.org/blockenspiel'
49
- p_.clean_globs << 'idslb_markdown.txt'
42
+ require File.expand_path("#{File.dirname(__FILE__)}/lib/blockenspiel/version")
43
+
44
+
45
+ # Configuration
46
+ extra_rdoc_files_ = ['README.rdoc', 'History.rdoc', 'ImplementingDSLblocks.rdoc']
47
+
48
+
49
+ # Default task
50
+ task :default => [:clean, :compile, :rdoc, :test]
51
+
52
+
53
+ # Clean task
54
+ CLEAN.include(['ext/blockenspiel/Makefile', '**/unmixer.bundle', 'ext/blockenspiel/unmixer.o', '**/blockenspiel_unmixer.jar', 'ext/blockenspiel/BlockenspielUnmixerService.class', 'idslb_markdown.txt', 'doc', 'pkg'])
55
+
56
+
57
+ # Test task
58
+ Rake::TestTask.new('test') do |task_|
59
+ task_.pattern = 'tests/tc_*.rb'
60
+ end
61
+
62
+
63
+ # RDoc task
64
+ Rake::RDocTask.new do |task_|
65
+ task_.main = 'README.rdoc'
66
+ task_.rdoc_files.include(*extra_rdoc_files_)
67
+ task_.rdoc_files.include('lib/blockenspiel/*.rb')
68
+ task_.rdoc_dir = 'doc'
69
+ task_.title = "Blockenspiel #{Blockenspiel::VERSION_STRING} documentation"
70
+ task_.options << '-f' << 'darkfish'
71
+ end
72
+
73
+
74
+ # Gem task
75
+ gemspec_ = Gem::Specification.new do |s_|
76
+ s_.name = 'blockenspiel'
77
+ s_.summary = 'Blockenspiel is a helper library designed to make it easy to implement DSL blocks.'
78
+ s_.version = Blockenspiel::VERSION_STRING
79
+ s_.author = 'Daniel Azuma'
80
+ s_.email = 'dazuma@gmail.com'
81
+ s_.description = '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.'
82
+ s_.homepage = 'http://virtuoso.rubyforge.org/blockenspiel'
83
+ s_.rubyforge_project = 'virtuoso'
84
+ s_.required_ruby_version = '>= 1.8.6'
85
+ s_.files = FileList['ext/**/*.{c,rb,java}', 'lib/**/*.{rb,jar}', 'tests/**/*.rb', '*.rdoc', 'Rakefile'].to_a
86
+ s_.extra_rdoc_files = extra_rdoc_files_
87
+ s_.has_rdoc = true
88
+ s_.test_files = FileList['tests/tc_*.rb']
89
+ if RUBY_PLATFORM =~ /java/
90
+ s_.platform = 'jruby'
91
+ s_.files += ['lib/blockenspiel_unmixer.jar']
92
+ else
93
+ s_.platform = Gem::Platform::RUBY
94
+ s_.extensions = ['ext/blockenspiel/extconf.rb']
95
+ end
96
+ end
97
+ task :package => [:compile]
98
+ Rake::GemPackageTask.new(gemspec_) do |task_|
99
+ task_.need_zip = false
100
+ task_.need_tar = false
101
+ end
102
+
103
+
104
+ # General build task
105
+ task :compile => RUBY_PLATFORM =~ /java/ ? [:compile_java] : [:compile_c]
106
+
107
+
108
+ # Build tasks for MRI
109
+ dlext_ = Config::CONFIG['DLEXT']
110
+
111
+ desc 'Builds the extension'
112
+ task :compile_c => ["lib/blockenspiel/unmixer.#{dlext_}"]
113
+
114
+ file 'ext/blockenspiel/Makefile' => ['ext/blockenspiel/extconf.rb'] do
115
+ Dir.chdir('ext/blockenspiel') do
116
+ ruby 'extconf.rb'
117
+ end
118
+ end
119
+
120
+ file "ext/blockenspiel/unmixer.#{dlext_}" => ['ext/blockenspiel/Makefile', 'ext/blockenspiel/unmixer.c'] do
121
+ Dir.chdir('ext/blockenspiel') do
122
+ sh 'make'
123
+ end
124
+ end
125
+
126
+ file "lib/blockenspiel/unmixer.#{dlext_}" => ["ext/blockenspiel/unmixer.#{dlext_}"] do
127
+ cp "ext/blockenspiel/unmixer.#{dlext_}", 'lib/blockenspiel'
128
+ end
129
+
130
+
131
+ # Build tasks for JRuby
132
+ desc "Compiles the JRuby extension"
133
+ task :compile_java do
134
+ Dir.chdir('ext/blockenspiel') do
135
+ sh 'javac -source 1.5 -target 1.5 -classpath $JRUBY_HOME/lib/jruby.jar BlockenspielUnmixerService.java'
136
+ sh 'jar cf blockenspiel_unmixer.jar BlockenspielUnmixerService.class'
137
+ cp 'blockenspiel_unmixer.jar', '../../lib/blockenspiel_unmixer.jar'
138
+ end
139
+ end
140
+
141
+
142
+ # Publish RDocs
143
+ desc 'Publishes RDocs to RubyForge'
144
+ task :publish_rdoc => [:rerdoc] do
145
+ config_ = YAML.load(File.read(File.expand_path("~/.rubyforge/user-config.yml")))
146
+ username_ = config_['username']
147
+ sh "rsync -av --delete doc/ #{username_}@rubyforge.org:/var/www/gforge-projects/virtuoso/blockenspiel"
148
+ end
149
+
150
+
151
+ # Publish gem
152
+ task :publish_gem do |t_|
153
+ v_ = ENV["VERSION"]
154
+ abort "Must supply VERSION=x.y.z" unless v_
155
+ if v_ != Blockenspiel::VERSION_STRING
156
+ abort "Versions don't match: #{v_} vs #{Blockenspiel::VERSION_STRING}"
157
+ end
158
+ mri_pkg_ = "pkg/blockenspiel-#{v_}.gem"
159
+ jruby_pkg_ = "pkg/blockenspiel-#{v_}-java.gem"
160
+ if !File.file?(mri_pkg_) || !File.readable?(mri_pkg_)
161
+ abort "You haven't built #{mri_pkg_} yet. Try rake package"
162
+ end
163
+ if !File.file?(jruby_pkg_) || !File.readable?(jruby_pkg_)
164
+ abort "You haven't built #{jruby_pkg_} yet. Try jrake package"
165
+ end
166
+ release_notes_ = File.read("README.rdoc").split(/^(==.*)/)[2].strip
167
+ release_changes_ = File.read("History.rdoc").split(/^(===.*)/)[1..2].join.strip
168
+
169
+ require 'rubyforge'
170
+ rf_ = RubyForge.new.configure
171
+ puts "Logging in to RubyForge"
172
+ rf_.login
173
+ config_ = rf_.userconfig
174
+ config_["release_notes"] = release_notes_
175
+ config_["release_changes"] = release_changes_
176
+ config_["preformatted"] = true
177
+ puts "Releasing blockenspiel #{v_}"
178
+ rf.add_release('virtuoso', 'blockenspiel', v_, mri_pkg_, jruby_pkg_)
50
179
  end
51
180
 
52
181
 
@@ -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,2 @@
1
+ require 'mkmf'
2
+ create_makefile 'blockenspiel/unmixer'
@@ -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
+ }