Permalink
Browse files

Remove dependencies on mixology and hoe; Compatibility fixes for Ruby…

… 1.9 and JRuby
  • Loading branch information...
1 parent 2ab48af commit 4a536d87df06357daa9484343629918ffeee66b1 @dazuma dazuma committed Apr 15, 2009
@@ -1,3 +1,10 @@
+=== 0.2.0 / 2009-04-15
+
+* Now compatible with Ruby 1.9.
+* Now compatible with JRuby 1.2.
+* No longer requires the mixology gem.
+* Building no longer requires hoe.
+
=== 0.1.1 / 2008-11-06
* 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
s1.foo # prints "foo called"
s2.foo # NameError: s2 is unchanged
-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.
+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.
-Using mixico, we can now write the +draw+ method like this:
+Using Mixico, we can now write the +draw+ method like this:
def draw(&block)
clear!
@@ -758,7 +758,7 @@ As we have seen, the mixin idea seems like it may be a compelling solution, part
*Implementation*:
-* Install a mixin library such as mixico or mixology.
+* Install a mixin library such as mixico or mixology (or roll your own if necessary).
* Define the DSL methods in a module.
* Mix the module into the block's context before invoking the block, and remove it afterwards.
* 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
def add_route(path, options = {})
# ...
-You can install blockenspiel as a gem for MRI 1.8.x
+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.
gem install blockenspiel
View
@@ -1,11 +0,0 @@
-History.txt
-ImplementingDSLblocks.txt
-Manifest.txt
-README.txt
-Rakefile
-lib/blockenspiel.rb
-tests/tc_basic.rb
-tests/tc_behaviors.rb
-tests/tc_dsl_methods.rb
-tests/tc_dynamic.rb
-tests/tc_mixins.rb
View
@@ -256,7 +256,7 @@ Here's an example:
That API is in itself a DSL block, and yes, Blockenspiel uses itself to
implement this feature.
-By default Blockenspiel uses mixins, which usually exhibit safe and
+By default Blockenspiel uses mixins, which usually exhibit fairly safe and
non-surprising behavior. However, there are a few cases when you might
want the <tt>instance_eval</tt> behavior anyway. RSpec is a good example of
such a case, since the DSL is being used to construct objects, so it makes
@@ -284,9 +284,8 @@ concurrently.
=== Requirements
-* Ruby 1.8.6 or later.
+* Ruby 1.8.7 or later, or JRuby 1.2 or later. Ruby 1.9 compatible.
* Rubygems
-* mixology gem.
=== Installation
@@ -296,8 +295,6 @@ concurrently.
* Implementing wildcard DSL methods using <tt>method_missing</tt> doesn't
work. I haven't yet figured out the right semantics for this case.
-* Doesn't yet work in Ruby 1.9 because the mixology gem is not compatible at this point.
-* JRuby status not yet known.
=== Development and support
@@ -311,16 +308,20 @@ Contact the author at dazuma at gmail dot com.
=== Author / Credits
-Blockenspiel is written by Daniel Azuma (http://www.daniel-azuma.com).
+Blockenspiel is written by Daniel Azuma (http://www.daniel-azuma.com/).
The mixin implementation is based on a concept by Why The Lucky Stiff.
See his 6 October 2008 blog posting,
<em>{Mixing Our Way Out Of Instance Eval?}[http://hackety.org/2008/10/06/mixingOurWayOutOfInstanceEval.html]</em>
for further discussion.
+The unmixer code is based on {Mixology}[http://rubyforge.org/projects/mixology],
+by Patrick Farley, anonymous z, Dan Manges, and Clint Bishop.
+The code has been stripped down and modified to support MRI 1.9 and JRuby 1.2.
+
=== License
-Copyright 2008 Daniel Azuma.
+Copyright 2008-2009 Daniel Azuma.
All rights reserved.
View
114 Rakefile
@@ -3,7 +3,7 @@
# Blockenspiel Rakefile
#
# -----------------------------------------------------------------------------
-# Copyright 2008 Daniel Azuma
+# Copyright 2008-2009 Daniel Azuma
#
# All rights reserved.
#
@@ -32,21 +32,105 @@
# POSSIBILITY OF SUCH DAMAGE.
# -----------------------------------------------------------------------------
+require 'rake'
+require 'rake/clean'
+require 'rake/gempackagetask'
+require 'rake/testtask'
+require 'rake/rdoctask'
-require 'rubygems'
-require 'hoe'
-require File.expand_path("#{File.dirname(__FILE__)}/lib/blockenspiel.rb")
-
-Hoe.new('blockenspiel', Blockenspiel::VERSION_STRING) do |p_|
- p_.rubyforge_name = 'virtuoso'
- p_.developer('Daniel Azuma', 'dazuma@gmail.com')
- p_.author = ['Daniel Azuma']
- p_.email = ['dazuma@gmail.com']
- p_.test_globs = ['tests/tc_*.rb']
- p_.extra_deps = [['mixology', '>= 0.1.0']]
- p_.description_sections = ['blockenspiel']
- p_.url = 'http://virtuoso.rubyforge.org/blockenspiel'
- p_.clean_globs << 'idslb_markdown.txt'
+require File.expand_path("#{File.dirname(__FILE__)}/lib/blockenspiel/version")
+
+
+# Configuration
+extra_rdoc_files_ = ['README.rdoc', 'HISTORY.rdoc', 'ImplementingDSLblocks.rdoc']
+
+
+# Default task
+if RUBY_PLATFORM =~ /java/
+ task :default => [:clean, :compile_java, :rdoc, :test]
+else
+ task :default => [:clean, :compile, :rdoc, :test]
+end
+
+
+# Clean task
+CLEAN.include(['ext/blockenspiel/Makefile', '**/unmixer.bundle', 'ext/blockenspiel/unmixer.o', '**/blockenspiel_unmixer.jar', 'ext/blockenspiel/BlockenspielUnmixerService.class', 'idslb_markdown.txt', 'doc', 'pkg'])
+
+
+# Test task
+Rake::TestTask.new('test') do |task_|
+ task_.pattern = 'tests/tc_*.rb'
+end
+
+
+# RDoc task
+Rake::RDocTask.new do |task_|
+ task_.main = 'README.rdoc'
+ task_.rdoc_files.include(*extra_rdoc_files_)
+ task_.rdoc_files.include('lib/**/*.rb')
+ task_.rdoc_dir = 'doc'
+end
+
+
+# Gem task
+gemspec_ = Gem::Specification.new do |s_|
+ s_.name = 'blockenspiel'
+ s_.summary = 'Blockenspiel is a helper library designed to make it easy to implement DSL blocks.'
+ s_.version = Blockenspiel::VERSION_STRING
+ s_.author = 'Daniel Azuma'
+ s_.email = 'dazuma@gmail.com'
+ 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.'
+ s_.homepage = 'http://virtuoso.rubyforge.org/blockenspiel'
+ s_.rubyforge_project = 'virtuoso'
+ s_.required_ruby_version = '>= 1.8.7'
+ s_.files = FileList['ext/**/*.{c,rb}', '{lib,tests}/**/*.rb', '*.rdoc', 'Rakefile'].to_a
+ s_.extra_rdoc_files = extra_rdoc_files_
+ s_.has_rdoc = true
+ if RUBY_PLATFORM =~ /java/
+ s_.platform = 'jruby'
+ s_.files += ['lib/blockenspiel_unmixer.jar']
+ else
+ s_.platform = Gem::Platform::RUBY
+ s_.extensions = ['ext/blockenspiel/extconf.rb']
+ end
+end
+Rake::GemPackageTask.new(gemspec_) do |task_|
+ task_.need_zip = false
+ task_.need_tar = false
+end
+
+
+# Build tasks for MRI
+dlext_ = Config::CONFIG['DLEXT']
+
+desc 'Builds the extension'
+task :compile => ["lib/blockenspiel/unmixer.#{dlext_}"]
+
+file 'ext/blockenspiel/Makefile' => ['ext/blockenspiel/extconf.rb'] do
+ Dir.chdir('ext/blockenspiel') do
+ ruby 'extconf.rb'
+ end
+end
+
+file "ext/blockenspiel/unmixer.#{dlext_}" => ['ext/blockenspiel/Makefile', 'ext/blockenspiel/unmixer.c'] do
+ Dir.chdir('ext/blockenspiel') do
+ sh 'make'
+ end
+end
+
+file "lib/blockenspiel/unmixer.#{dlext_}" => ["ext/blockenspiel/unmixer.#{dlext_}"] do
+ cp "ext/blockenspiel/unmixer.#{dlext_}", 'lib/blockenspiel'
+end
+
+
+# Build tasks for JRuby
+desc "Compiles the JRuby extension"
+task :compile_java do
+ Dir.chdir('ext/blockenspiel') do
+ sh 'javac -source 1.5 -target 1.5 -classpath $JRUBY_HOME/lib/jruby.jar BlockenspielUnmixerService.java'
+ sh 'jar cf blockenspiel_unmixer.jar BlockenspielUnmixerService.class'
+ cp 'blockenspiel_unmixer.jar', '../../lib/blockenspiel_unmixer.jar'
+ end
end
@@ -0,0 +1,140 @@
+/*
+ -----------------------------------------------------------------------------
+
+ Blockenspiel unmixer (JRuby implementation)
+
+ -----------------------------------------------------------------------------
+ Copyright 2008-2009 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.
+ -----------------------------------------------------------------------------
+*/
+
+
+/*
+ This implementation based on Mixology,
+ written by Patrick Farley, anonymous z, Dan Manges, and Clint Bishop.
+ http://rubyforge.org/projects/mixology
+ http://github.com/dan-manges/mixology/tree/master
+
+ It has been stripped down and modified for JRuby 1.2 compatibility.
+*/
+
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ArrayList;
+
+import org.jruby.Ruby;
+import org.jruby.RubyArray;
+import org.jruby.RubyClass;
+import org.jruby.RubyModule;
+import org.jruby.anno.JRubyMethod;
+import org.jruby.IncludedModuleWrapper;
+import org.jruby.runtime.Block;
+import org.jruby.runtime.builtin.IRubyObject;
+import org.jruby.exceptions.RaiseException;
+import org.jruby.runtime.load.BasicLibraryService;
+
+
+public class BlockenspielUnmixerService implements BasicLibraryService
+{
+
+ public boolean basicLoad(final Ruby runtime) throws IOException
+ {
+ RubyModule blockenspielModule = runtime.getOrCreateModule("Blockenspiel");
+ RubyModule unmixerModule = runtime.defineModuleUnder("Unmixer", blockenspielModule);
+ unmixerModule.getSingletonClass().defineAnnotatedMethods(BlockenspielUnmixerService.class);
+ return true;
+ }
+
+
+ @JRubyMethod(name = "unmix", required = 2)
+ public synchronized static IRubyObject unmix(IRubyObject self, IRubyObject receiver, IRubyObject module, Block block)
+ throws NoSuchMethodException, IllegalAccessException, InvocationTargetException
+ {
+ RubyModule actualModule = (RubyModule)module;
+ RubyClass klass = receiver.getMetaClass();
+ while (klass != receiver.getMetaClass().getRealClass())
+ {
+ RubyClass superClass = klass.getSuperClass();
+ if (superClass != null && superClass.getNonIncludedClass() == actualModule)
+ {
+ if (actualModule.getSuperClass() != null &&
+ actualModule.getSuperClass() instanceof IncludedModuleWrapper)
+ {
+ removeNestedModule(superClass, actualModule);
+ }
+ setSuperClass(klass, superClass.getSuperClass());
+ invalidateCacheDescendants(klass);
+ }
+ klass = superClass;
+ }
+ return receiver;
+ }
+
+
+ protected synchronized static void removeNestedModule(RubyClass klass, RubyModule module)
+ throws NoSuchMethodException, IllegalAccessException, InvocationTargetException
+ {
+ if ((klass.getSuperClass() instanceof IncludedModuleWrapper) &&
+ ((IncludedModuleWrapper)klass.getSuperClass()).getNonIncludedClass() ==
+ ((IncludedModuleWrapper)module.getSuperClass()).getNonIncludedClass())
+ {
+ if (module.getSuperClass().getSuperClass() != null &&
+ module.getSuperClass() instanceof IncludedModuleWrapper)
+ {
+ removeNestedModule(klass.getSuperClass(), module.getSuperClass());
+ }
+ setSuperClass(klass, klass.getSuperClass().getSuperClass());
+ }
+ }
+
+
+ protected synchronized static void setSuperClass(RubyModule klass, RubyModule superClass)
+ throws NoSuchMethodException, IllegalAccessException, InvocationTargetException
+ {
+ Method method = RubyModule.class.getDeclaredMethod("setSuperClass",
+ new Class[] {RubyClass.class} );
+ method.setAccessible(true);
+ Object[] superClassArg = new Object[] { superClass };
+ method.invoke(klass, superClassArg);
+ }
+
+
+ protected synchronized static void invalidateCacheDescendants(RubyModule klass)
+ throws NoSuchMethodException, IllegalAccessException, InvocationTargetException
+ {
+ Method method = RubyModule.class.getDeclaredMethod("invalidateCacheDescendants", new Class[0]);
+ method.setAccessible(true);
+ method.invoke(klass, new Object[0]);
+ }
+
+}
+
@@ -0,0 +1,2 @@
+require 'mkmf'
+create_makefile 'blockenspiel/unmixer'
Oops, something went wrong.

0 comments on commit 4a536d8

Please sign in to comment.