├── .yardopts ├── test ├── .bacon ├── .rspec ├── test_with_object2module.rb └── test.rb ├── ext └── remix │ ├── extconf.rb │ ├── test.rb │ ├── compat.h │ └── remix.c ├── lib ├── remix │ ├── version.rb │ └── c_docs.rb └── remix.rb ├── CHANGELOG ├── Rakefile └── README.markdown /.yardopts: -------------------------------------------------------------------------------- 1 | -m markdown 2 | -------------------------------------------------------------------------------- /test/.bacon: -------------------------------------------------------------------------------- 1 | --colour 2 | --format documentation 3 | -------------------------------------------------------------------------------- /test/.rspec: -------------------------------------------------------------------------------- 1 | --colour 2 | --format documentation 3 | -------------------------------------------------------------------------------- /ext/remix/extconf.rb: -------------------------------------------------------------------------------- 1 | require 'mkmf' 2 | 3 | create_makefile('remix') 4 | -------------------------------------------------------------------------------- /lib/remix/version.rb: -------------------------------------------------------------------------------- 1 | module Remix 2 | VERSION = "0.4.9" 3 | end 4 | 5 | -------------------------------------------------------------------------------- /ext/remix/test.rb: -------------------------------------------------------------------------------- 1 | require 'mult' 2 | require './remix' 3 | 4 | class A 5 | end 6 | 7 | class B < A 8 | end 9 | 10 | class C < B 11 | end 12 | 13 | module M 14 | end 15 | 16 | module N 17 | end 18 | 19 | class C 20 | include M 21 | include N 22 | end 23 | 24 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | 16/11/2010 version 0.4.9 2 | * added __attached__ method 3 | 15/11/2010 version 0.4.8 4 | * all extend-based methods now return self 5 | * added documentation on blocks (@yield) 6 | * threadsafe temp_* methods now return value of block 7 | 12/11/2010 version 0.4.6 8 | * fixed segfault in replace_module when trying to replace root module 9 | 12/11/2010 version 0.4.5 10 | * added tests to swap_module and replace_module to prevent 11 | mod1 and mod2 being the same module 12 | * fixed bug in swap_module when ancestor chains were huge 13 | * added :before and :after hooks for temp_include and friends 14 | * added tests for hooks and for swap_module crashing bug 15 | 11/11/2010 version 0.4.0 16 | * added temp_include and temp_extend, and threadsafe variants 17 | temp_include_safe etc 18 | 27/10/2010 version 0.2.5 19 | * added tests 20 | * reversed order of arguments for #include_at 21 | * made it so first index of #include_at is 1 (not 0, since 0 is host module) 22 | * added extend-based methods 23 | * reordered modules (Remix::ObjectExtenstions and Remix::ModuleExtensions) 24 | * added recursive uninclude 25 | 26/10/2010 version 0.2.0 26 | * added include_at_top, replace_module, module_move_up, etc 27 | 25/10/2010 version 0.1.0 28 | * release! 29 | -------------------------------------------------------------------------------- /ext/remix/compat.h: -------------------------------------------------------------------------------- 1 | /* contains basic macros to facilitate ruby 1.8 and ruby 1.9 compatibility */ 2 | 3 | #ifndef GUARD_COMPAT_H 4 | #define GUARD_COMPAT_H 5 | 6 | #include 7 | 8 | /* test for 1.9 */ 9 | #if !defined(RUBY_19) && defined(ROBJECT_EMBED_LEN_MAX) 10 | # define RUBY_19 11 | #endif 12 | 13 | /* macros for backwards compatibility with 1.8 */ 14 | #ifndef RUBY_19 15 | # define RCLASS_M_TBL(c) (RCLASS(c)->m_tbl) 16 | # define RCLASS_SUPER(c) (RCLASS(c)->super) 17 | # define RCLASS_IV_TBL(c) (RCLASS(c)->iv_tbl) 18 | # define OBJ_UNTRUSTED OBJ_TAINTED 19 | # include "st.h" 20 | #endif 21 | 22 | #ifdef RUBY_19 23 | inline static VALUE 24 | class_alloc(VALUE flags, VALUE klass) 25 | { 26 | rb_classext_t *ext = ALLOC(rb_classext_t); 27 | NEWOBJ(obj, struct RClass); 28 | OBJSETUP(obj, klass, flags); 29 | obj->ptr = ext; 30 | RCLASS_IV_TBL(obj) = 0; 31 | RCLASS_M_TBL(obj) = 0; 32 | RCLASS_SUPER(obj) = 0; 33 | RCLASS_IV_INDEX_TBL(obj) = 0; 34 | return (VALUE)obj; 35 | } 36 | #endif 37 | 38 | inline static VALUE 39 | create_class(VALUE flags, VALUE klass) 40 | { 41 | #ifdef RUBY_19 42 | VALUE new_klass = class_alloc(flags, klass); 43 | #else 44 | NEWOBJ(new_klass, struct RClass); 45 | OBJSETUP(new_klass, klass, flags); 46 | #endif 47 | 48 | return (VALUE)new_klass; 49 | } 50 | 51 | # define FALSE 0 52 | # define TRUE 1 53 | 54 | /* a useful macro. cannot use ordinary CLASS_OF as it does not return an lvalue */ 55 | #define KLASS_OF(c) (RBASIC(c)->klass) 56 | 57 | #endif 58 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | dlext = Config::CONFIG['DLEXT'] 2 | direc = File.dirname(__FILE__) 3 | 4 | require 'rake/clean' 5 | require 'rake/gempackagetask' 6 | require "#{direc}/lib/remix/version" 7 | 8 | CLOBBER.include("**/*.#{dlext}", "**/*~", "**/*#*", "**/*.log", "**/*.o") 9 | CLEAN.include("ext/**/*.#{dlext}", "ext/**/*.log", "ext/**/*.o", "ext/**/*~", 10 | "ext/**/*#*", "ext/**/*.obj", "ext/**/*.def", "ext/**/*.pdb") 11 | 12 | def apply_spec_defaults(s) 13 | s.name = "remix" 14 | s.summary = "Ruby modules re-mixed and remastered" 15 | s.version = Remix::VERSION 16 | s.date = Time.now.strftime '%Y-%m-%d' 17 | s.author = "John Mair (banisterfiend)" 18 | s.email = 'jrmair@gmail.com' 19 | s.description = s.summary 20 | s.require_path = 'lib' 21 | s.homepage = "http://banisterfiend.wordpress.com" 22 | s.has_rdoc = 'yard' 23 | s.files = Dir["ext/**/extconf.rb", "ext/**/*.h", "ext/**/*.c", "lib/**/*.rb", 24 | "test/*.rb", "CHANGELOG", "README.markdown", "Rakefile"] 25 | end 26 | 27 | desc "run tests" 28 | task :test do 29 | sh "bacon -k #{direc}/test/test.rb" 30 | end 31 | 32 | [:mingw32, :mswin32].each do |v| 33 | namespace v do 34 | spec = Gem::Specification.new do |s| 35 | apply_spec_defaults(s) 36 | s.platform = "i386-#{v}" 37 | s.files += FileList["lib/**/*.#{dlext}"].to_a 38 | end 39 | 40 | Rake::GemPackageTask.new(spec) do |pkg| 41 | pkg.need_zip = false 42 | pkg.need_tar = false 43 | end 44 | end 45 | end 46 | 47 | namespace :ruby do 48 | spec = Gem::Specification.new do |s| 49 | apply_spec_defaults(s) 50 | s.platform = Gem::Platform::RUBY 51 | s.extensions = ["ext/remix/extconf.rb"] 52 | end 53 | 54 | Rake::GemPackageTask.new(spec) do |pkg| 55 | pkg.need_zip = false 56 | pkg.need_tar = false 57 | end 58 | end 59 | 60 | desc "build the 1.8 and 1.9 binaries from source and copy to lib/" 61 | task :compile do 62 | build_for = proc do |pik_ver, ver| 63 | sh %{ \ 64 | c:\\devkit\\devkitvars.bat && \ 65 | pik #{pik_ver} && \ 66 | ruby extconf.rb && \ 67 | make clean && \ 68 | make && \ 69 | cp *.so #{direc}/lib/#{ver} \ 70 | } 71 | end 72 | 73 | chdir("#{direc}/ext/remix") do 74 | build_for.call("187", "1.8") 75 | build_for.call("192", "1.9") 76 | end 77 | end 78 | 79 | desc "build all platform gems at once" 80 | task :gems => [:rmgems, "mingw32:gem", "mswin32:gem", "ruby:gem"] 81 | 82 | desc "remove all platform gems" 83 | task :rmgems => ["ruby:clobber_package"] 84 | 85 | desc "build and push latest gems" 86 | task :pushgems => :gems do 87 | chdir("#{direc}/pkg") do 88 | Dir["*.gem"].each do |gemfile| 89 | sh "gem push #{gemfile}" 90 | end 91 | end 92 | end 93 | -------------------------------------------------------------------------------- /test/test_with_object2module.rb: -------------------------------------------------------------------------------- 1 | direc = File.dirname(__FILE__) 2 | require 'rubygems' 3 | require 'bacon' 4 | require 'object2module' 5 | require "#{direc}/../lib/remix" 6 | 7 | class Module 8 | public :include, :remove_const 9 | end 10 | 11 | puts "testing Remix version #{Remix::VERSION} with Object2module version #{Object2module::VERSION}..." 12 | puts "Ruby version: #{RUBY_VERSION}" 13 | 14 | describe Remix do 15 | before do 16 | class A 17 | def hello 18 | :a 19 | end 20 | end 21 | 22 | class B 23 | def hello 24 | :b 25 | end 26 | end 27 | 28 | module M 29 | def m 30 | :m 31 | end 32 | end 33 | 34 | O = Object.new 35 | class << O 36 | def o 37 | :o 38 | end 39 | end 40 | 41 | C = Class.new 42 | end 43 | 44 | after do 45 | Object.remove_const(:A) 46 | Object.remove_const(:B) 47 | Object.remove_const(:C) 48 | Object.remove_const(:M) 49 | Object.remove_const(:O) 50 | end 51 | 52 | describe 'gen_include' do 53 | it 'includes two classes and swaps them' do 54 | C.gen_include A 55 | C.gen_include B 56 | C.new.hello.should == :b 57 | C.swap_modules A, B 58 | C.new.hello.should == :a 59 | end 60 | 61 | it 'includes a class into a class and swaps them' do 62 | A.gen_include B 63 | C.gen_include A 64 | C.new.hello.should == :a 65 | C.swap_modules A, B 66 | C.new.hello.should == :b 67 | end 68 | 69 | it 'unincludes a gen_included class' do 70 | C.gen_include A 71 | C.new.hello.should == :a 72 | C.uninclude A 73 | lambda { C.new.hello }.should.raise NameError 74 | end 75 | 76 | it 'recursively unincludes a gen_included class' do 77 | A.gen_include B 78 | C.gen_include A 79 | C.new.hello.should == :a 80 | C.ancestors.should[0..2] == [C, A, B] 81 | C.uninclude A, true 82 | C.ancestors.should[0..1] == [C, Object] 83 | end 84 | 85 | it 'unincludes a singleton class' do 86 | o = Object.new 87 | class << o 88 | def hello 89 | :o 90 | end 91 | end 92 | 93 | C.gen_include o 94 | C.new.hello.should == :o 95 | C.uninclude C.ancestors[1] 96 | lambda { C.new.hello }.should.raise NameError 97 | C.ancestors[1].should == Object 98 | end 99 | end 100 | 101 | describe 'gen_extend' do 102 | it 'extends two classes into an object and swaps them' do 103 | o = Object.new 104 | o.gen_extend A, B 105 | o.hello.should == :a 106 | end 107 | 108 | it 'unextends a class from an object' do 109 | o = Object.new 110 | o.gen_extend A 111 | o.hello.should == :a 112 | o.singleton_class.ancestors[0].should == A 113 | o.unextend A 114 | lambda { o.hello }.should.raise NameError 115 | o.singleton_class.ancestors[0].should == Object 116 | end 117 | 118 | it 'recursively unextends a class from an object' do 119 | o = Object.new 120 | A.gen_include B 121 | o.gen_extend A 122 | o.singleton_class.ancestors[0..2].should == [A, B, Object] 123 | o.unextend A, true 124 | o.singleton_class.ancestors.first.should == Object 125 | end 126 | 127 | it 'unextends an object by object (not by singleton)' do 128 | o = Object.new 129 | def o.hello 130 | :o 131 | end 132 | 133 | n = Object.new 134 | n.gen_extend o 135 | n.hello.should == :o 136 | n.unextend o 137 | lambda { n.hello }.should.raise NameError 138 | end 139 | 140 | 141 | it 'recursively unextends a singleton class gen_extended into another singleton class' do 142 | o = Object.new 143 | def o.hello 144 | :o 145 | end 146 | 147 | n = Object.new 148 | def n.hello 149 | :n 150 | end 151 | 152 | n.gen_extend o 153 | 154 | v = Object.new 155 | v.gen_extend n 156 | 157 | v.hello.should == :n 158 | v.unextend n.singleton_class, true 159 | lambda { v.hello }.should.raise NameError 160 | v.singleton_class.ancestors.first.should == Object 161 | end 162 | end 163 | end 164 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | Remix 2 | ======= 3 | 4 | (C) John Mair (banisterfiend) 2010 5 | 6 | _Ruby modules remixed and remastered_ 7 | 8 | Remix is a library to give you total control over class and module ancestor 9 | chains. 10 | 11 | Using Remix you can add a module at any point in the chain, 12 | remove modules, replace modules, move modules around and otherwise 13 | 'remix' your modules. 14 | 15 | * Install the [gem](https://rubygems.org/gems/remix): `gem install remix` 16 | * Read the [documentation](http://rdoc.info/github/banister/remix/master/file/README.markdown) 17 | * See the [source code](http://github.com/banister/remix) 18 | 19 | example: temp_include(): 20 | ------------------------ 21 | 22 | Using `temp_include` we can temporaliy mix in a module for the 23 | duration of a block: 24 | 25 | module M def hello() :hello end end 26 | 27 | String.temp_include(M) do 28 | puts "test".hello #=> "hello" 29 | end 30 | 31 | "test".hello #=> NoMethodError 32 | 33 | example: unextend() 34 | -------------------- 35 | 36 | Like the Mixico library Remix allows you to unextend 37 | (or uninclude) modules from inheritance chains; but also extends this 38 | functionality by (optionally) removing nested modules too: 39 | 40 | C.ancestors #=> [C, A, B] 41 | 42 | o = Object.new 43 | o.extend C 44 | o.singleton_class.ancestors #=> [C, A, B, Object, ...] 45 | 46 | # remove entire nested module C by passing true as second parameter 47 | o.unextend C, true 48 | 49 | o.singleton_class.ancestors #=> [Object, ...] 50 | 51 | Special features 52 | ------------------ 53 | 54 | Remix is intelligent enough to manipulate classes as well as 55 | modules: 56 | 57 | class D < C 58 | include M 59 | end 60 | 61 | D.ancestors #=> [D, M, C] 62 | 63 | D.swap_modules C, M 64 | 65 | D.ancestors #=> [D, C, M] 66 | 67 | It does this by first converting all superclasses to Included Modules 68 | before remixing takes place. 69 | 70 | How it works 71 | -------------- 72 | 73 | Remix is a C-based extension that directly manipulates the superclass 74 | pointers of Included Modules. 75 | 76 | Companion Libraries 77 | -------------------- 78 | 79 | Remix is one of a series of experimental libraries that mess with 80 | the internals of Ruby to bring new and interesting functionality to 81 | the language, see also: 82 | 83 | * [Include Complete](http://github.com/banister/include_complete) - Brings in 84 | module singleton classes during an include. No more ugly ClassMethods and included() hook hacks. 85 | * [Object2module](http://github.com/banister/object2module) - Convert Classes and Objects to Modules so they can be extended/included 86 | * [Prepend](http://github.com/banister/prepend) - Prepends modules in front of a class; so method lookup starts with the module 87 | * [GenEval](http://github.com/banister/gen_eval) - A strange new breed of instance_eval 88 | 89 | Full list of functions 90 | ---------------------- 91 | 92 | **include-based functions:** 93 | 94 | * temp_include(mod) 95 | * include_at(index, mod) 96 | * include_at_top(mod) 97 | * include_before(before_mod, mod) 98 | * include_after(after_mod, mod) 99 | * swap_modules(mod1, mod2) 100 | * uninclude(mod, recurse=fale) 101 | * module_move_up(mod) 102 | * module_move_down(mod) 103 | * replace_module(mod1, mod2) 104 | 105 | **extend-based functions:** 106 | 107 | * temp_extend(mod) 108 | * extend_at(index, mod) 109 | * extend_at_top(mod) 110 | * extend_before(before_mod, mod) 111 | * extend_after(after_mod, mod) 112 | * swap_extended_modules(mod1, mod2) 113 | * unextend(mod, recurse=false) 114 | * extended_module_move_up(mod) 115 | * extended_module_move_down(mod) 116 | * replace_extended_module(mod1, mod2) 117 | 118 | Limitations 119 | ------------ 120 | 121 | Remix does not currently reorder the singleton classes of superclasses 122 | to reflect the new position of the class. This functionality is coming 123 | soon. 124 | 125 | Special Thanks 126 | --------------- 127 | 128 | [Asher](http://github.com/asher-) 129 | 130 | Contact 131 | ------- 132 | 133 | Problems or questions contact me at [github](http://github.com/banister) 134 | 135 | Dedication 136 | ---------- 137 | 138 | For Rue (1977-) 139 | 140 | 141 | -------------------------------------------------------------------------------- /lib/remix/c_docs.rb: -------------------------------------------------------------------------------- 1 | module Remix 2 | module ModuleExtensions 3 | 4 | # Utility method to return object associated with a singleton class 5 | # @return Object associated with singleton 6 | # @example 7 | # class C; end 8 | # C.singleton_class.__attached__ #=> C 9 | def __attached__() end 10 | 11 | # Includes a module at a particular index in the ancestor 12 | # chain. 13 | # 14 | # @param [Fixnum] index The index where the module will be included 15 | # (must be > 1.) 16 | # @param [Module] mod The module to include 17 | # @return [Module] The receiver 18 | # @example 19 | # P.ancestors #=> [P, M, N] 20 | # P.include_at 2, O 21 | # P.ancestors #=> [P, M, O, N] 22 | def include_at(index, mod) end 23 | 24 | # Includes a module below a specific module in the ancestor chain. 25 | # @param [Module] mod1 Module with position 26 | # @param [Module] mod2 Module that will be included 27 | # @return [Module] The receiver 28 | # @example 29 | # M.ancestors #=> [M, A, B] 30 | # M.include_below B, J 31 | # M.ancestors #=> [M, A, J, B] 32 | def include_below(mod1, mod2) end 33 | 34 | # Includes a module above a specific module in the ancestor chain. 35 | # @param [Module] mod1 Module with position 36 | # @param [Module] mod2 Module that will be included 37 | # @return [Module] The receiver 38 | # @example 39 | # M.ancestors #=> [M, A, B] 40 | # M.include_above B, J 41 | # M.ancestors #=> [M, A, B, J] 42 | def include_above(mod1, mod) end 43 | 44 | # Includes a module at top of ancestor chain 45 | # @param [Module] mod Module that will be included 46 | # @return [Module] The receiver 47 | # @example 48 | # M.ancestors #=> [M, A, B] 49 | # M.include_at_top J 50 | # M.ancestors #=> [M, A, B, J] 51 | def include_at_top(mod) end 52 | 53 | # Moves a module up one position in the ancestor chain. 54 | # Module must already be in ancestor chain. 55 | # @param [Module] mod The module to move up 56 | # @return [Module] The receiver 57 | # @example 58 | # M.ancestors #=> [M, A, B] 59 | # M.module_move_up A 60 | # M.ancestors #=> [M, B, A] 61 | def module_move_up(mod) end 62 | 63 | # Moves a module down one position in the ancestor chain. 64 | # Module must already be in ancestor chain. 65 | # @param [Module] mod The module to move down 66 | # @return [Module] The receiver 67 | # @example 68 | # M.ancestors #=> [M, A, B] 69 | # M.module_move_down B 70 | # M.ancestors #=> [M, B, A] 71 | def module_move_down(mod) end 72 | 73 | # Unincludes a module from an ancestor chain with optional recursion 74 | # for nested modules. 75 | # @param [Module] mod The module to uninclude 76 | # @param [Boolean] recurse Set to true to remove nested modules 77 | # @return [Module] The receiver 78 | # @example Without recursion 79 | # module C 80 | # include A, B 81 | # end 82 | # M.ancestors #=> [M, C, A, B] 83 | # M.uninclude C 84 | # M.ancestors #=> [M, A, B] 85 | # @example With recursion 86 | # module C 87 | # include A, B 88 | # end 89 | # M.ancestors #=> [M, C, A, B] 90 | # M.uninclude C, true 91 | # M.ancestors #=> [M] 92 | def uninclude(mod, recurse = false) end 93 | 94 | # Swaps the position of two modules that already exist in an 95 | # ancestor chain. 96 | # @param [Module] mod1 Module to swap 97 | # @param [Module] mod2 Module to swap 98 | # @return [Module] The receiver 99 | # @example 100 | # M.ancestors #=> [M, A, B, C, D] 101 | # M.swap_modules A, D 102 | # M.ancestors #=> [M, D, B, C, A] 103 | def swap_modules(mod1, mod2) end 104 | 105 | # Replaces a module with another module that is not already in the 106 | # ancestor chain. 107 | # @param [Module] mod1 The module to be replaced 108 | # @param [Module] mod2 The module that will replace 109 | # @return [Module] The receiver 110 | # @example 111 | # J = Module.new 112 | # M.ancestors #=> [M, A, B] 113 | # M.replace_module B, J 114 | # M.ancestors #=> [M, A, J] 115 | def replace_module(mod1, mod2) end 116 | 117 | # Prepares the receiver's ancestor chain for remixing. This method 118 | # is called automatically by all remixing methods and should 119 | # never need to be invoked by the user. 120 | # @return [Object] The receiver 121 | def ready_remix() end 122 | end 123 | end 124 | -------------------------------------------------------------------------------- /lib/remix.rb: -------------------------------------------------------------------------------- 1 | # remix.rb 2 | # (C) John Mair (banisterfiend); MIT license 3 | 4 | direc = File.dirname(__FILE__) 5 | 6 | require 'rbconfig' 7 | require "#{direc}/remix/version" 8 | require "#{direc}/remix/c_docs" 9 | 10 | dlext = Config::CONFIG['DLEXT'] 11 | 12 | begin 13 | if RUBY_VERSION && RUBY_VERSION =~ /1.9/ 14 | require "#{direc}/1.9/remix.#{dlext}" 15 | else 16 | require "#{direc}/1.8/remix.#{dlext}" 17 | end 18 | rescue LoadError => e 19 | require "#{direc}/remix.#{dlext}" 20 | end 21 | 22 | module Kernel 23 | 24 | # Define a `singleton_class` method for the 1.8 kids 25 | # @return [Class] The singleton class of the receiver 26 | def singleton_class 27 | class << self; self; end 28 | end if !respond_to?(:singleton_class) 29 | end 30 | 31 | module Remix 32 | 33 | # Wraps a block of code so that `before` and `after` lambdas are invoked 34 | # prior to and after the block. 35 | # @return [Object] The return value of the block 36 | # @yield the block to wrap 37 | def self.wrap_with_hooks(before, after, &block) 38 | before.call if before 39 | yield 40 | ensure 41 | after.call if after 42 | end 43 | 44 | module ObjectExtensions 45 | 46 | # Temporarily extends a module for the duration of a block. 47 | # Module will be unextended at end of block. 48 | # @param [Module] mod Module to be temporarily extended 49 | # @return [Object] The value of the block 50 | # @yield The block that will acquire the functionality. 51 | # @example 52 | # module M 53 | # def hello 54 | # :hello 55 | # end 56 | # end 57 | # 58 | # o = Object.new 59 | # o.temp_extend(M) do 60 | # puts o.hello #=> "hello" 61 | # end 62 | # 63 | # o.hello #=> NoMethodError 64 | def temp_extend(mod, options={}, &block) 65 | Remix.wrap_with_hooks(options[:before], options[:after]) do 66 | begin 67 | extend(mod) 68 | yield 69 | ensure 70 | unextend(mod, true) 71 | end 72 | end 73 | end 74 | 75 | # Temporarily extends a module for the duration of a block in a 76 | # thread-safe manner. 77 | # Module will be unextended at end of block. 78 | # **DO NOT** wait on other threads in this block as it will result 79 | # in deadlock. `Thread.exclusive` is used. 80 | # @param [Module] mod Module to be temporarily extended 81 | # @return [Object] The value of the block 82 | # @yield The block that will acquire the functionality. 83 | def temp_extend_safe(mod, options={}, &block) 84 | safe_code = proc do 85 | Remix.wrap_with_hooks(options[:before], options[:after]) do 86 | begin 87 | extend(mod) 88 | yield 89 | ensure 90 | unextend(mod, true) 91 | end 92 | end 93 | end 94 | 95 | if !Thread.current[:__exclusive__] 96 | value = nil 97 | 98 | Thread.exclusive do 99 | Thread.current[:__exclusive__] = true 100 | value = safe_code.call 101 | Thread.current[:__exclusive__] = false 102 | end 103 | 104 | value 105 | else 106 | safe_code.call 107 | end 108 | end 109 | 110 | # Like `include_at()` but for the singleton class 111 | # @return [Object] The receiver 112 | # @see Remix::ModuleExtensions#include_at 113 | def extend_at(index, mod) 114 | singleton_class.include_at(index, mod) 115 | self 116 | end 117 | 118 | # Like `include_below()` but for the singleton class 119 | # @return [Object] The receiver 120 | # @see Remix::ModuleExtensions#include_below 121 | def extend_below(mod1, mod2) 122 | singleton_class.include_below(mod1, mod2) 123 | self 124 | end 125 | alias_method :extend_before, :extend_below 126 | 127 | # Like `include_above()` but for the singleton class 128 | # @return [Object] The receiver 129 | # @see Remix::ModuleExtensions#include_above 130 | def extend_above(mod1, mod2) 131 | singleton_class.include_above(mod1, mod2) 132 | self 133 | end 134 | alias_method :extend_after, :extend_above 135 | 136 | # Like `uninclude()` but for the singleton class 137 | # @return [Object] The receiver 138 | # @see Remix::ModuleExtensions#uninclude 139 | def unextend(mod, recurse = false) 140 | singleton_class.uninclude(mod, recurse) 141 | self 142 | end 143 | alias_method :remove_extended_module, :unextend 144 | 145 | # Like `include_at_top()` but for the singleton class 146 | # @return [Object] The receiver 147 | # @see Remix::ModuleExtensions#include_at_top 148 | def extend_at_top(mod) 149 | singleton_class.include_at_top(mod) 150 | self 151 | end 152 | 153 | # Like `swap_modules()` but for the singleton class 154 | # @return [Object] The receiver 155 | # @see Remix::ModuleExtensions#swap_modules 156 | def swap_extended_modules(mod1, mod2) 157 | singleton_class.swap_modules(mod1, mod2) 158 | self 159 | end 160 | 161 | # Like `module_move_up()` but for the singleton class 162 | # @return [Object] The receiver 163 | # @see Remix::ModuleExtensions#module_move_up 164 | def extended_module_move_up(mod) 165 | singleton_class.module_move_up(mod) 166 | self 167 | end 168 | 169 | # Like `module_move_down()` but for the singleton class 170 | # @return [Object] The receiver 171 | # @see Remix::ModuleExtensions#module_move_down 172 | def extended_module_move_down(mod) 173 | singleton_class.module_move_down(mod) 174 | self 175 | end 176 | 177 | # Like `replace_module()` but for the singleton class 178 | # @return [Object] The receiver 179 | # @see Remix::ModuleExtensions#replace_module 180 | def replace_extended_module(mod1, mod2) 181 | singleton_class.replace_module(mod1, mod2) 182 | self 183 | end 184 | 185 | # Like `ready_remix()` on `Module` but for the singleton class 186 | # @return [Object] The receiver 187 | # @see Remix::ModuleExtensions#ready_remix 188 | def ready_remix() 189 | singleton_class.ready_remix 190 | self 191 | end 192 | end 193 | 194 | module Remix::ModuleExtensions 195 | 196 | # Temporarily includes a module for the duration of a block. 197 | # Module will be unincluded at end of block. 198 | # @param [Module] mod Module to be temporarily included 199 | # @return [Object] The value of the block 200 | # @yield The block that will acquire the functionality. 201 | # @example 202 | # module M 203 | # def hello 204 | # :hello 205 | # end 206 | # end 207 | # 208 | # String.temp_include(M) do 209 | # puts "friendo".hello #=> "hello" 210 | # end 211 | # 212 | # "friendo".hello #=> NoMethodError 213 | def temp_include(mod, options={}, &block) 214 | Remix.wrap_with_hooks(options[:before], options[:after]) do 215 | begin 216 | include(mod) 217 | yield 218 | ensure 219 | uninclude(mod, true) 220 | end 221 | end 222 | end 223 | 224 | # Temporarily includes a module for the duration of a block in a 225 | # thread-safe manner. 226 | # Module will be unincluded at end of block. 227 | # *DO NOT* wait on other threads in this block as it will result 228 | # in deadlock. `Thread.exclusive` is used. 229 | # @param [Module] mod Module to be temporarily included 230 | # @return [Object] The value of the block 231 | # @yield The block that will acquire the functionality. 232 | def temp_include_safe(mod, options={}, &block) 233 | safe_code = proc do 234 | Remix.wrap_with_hooks(options[:before], options[:after]) do 235 | begin 236 | include(mod) 237 | yield 238 | ensure 239 | uninclude(mod, true) 240 | end 241 | end 242 | end 243 | 244 | if !Thread.current[:__exclusive__] 245 | value = nil 246 | 247 | Thread.exclusive do 248 | Thread.current[:__exclusive__] = true 249 | value = safe_code.call 250 | Thread.current[:__exclusive__] = false 251 | end 252 | 253 | value 254 | else 255 | safe_code.call 256 | end 257 | end 258 | end 259 | end 260 | 261 | # bring extend-based methods into Object 262 | class Object 263 | include Remix::ObjectExtensions 264 | end 265 | 266 | # bring include-based methods into Module 267 | class Module 268 | include Remix::ModuleExtensions 269 | end 270 | -------------------------------------------------------------------------------- /test/test.rb: -------------------------------------------------------------------------------- 1 | direc = File.dirname(__FILE__) 2 | require 'rubygems' 3 | require "#{direc}/../lib/remix" 4 | require 'bacon' 5 | 6 | class Module 7 | public :include, :remove_const 8 | end 9 | 10 | puts "testing Remix version #{Remix::VERSION}..." 11 | puts "Ruby version: #{RUBY_VERSION}" 12 | 13 | describe 'Test basic remix functionality' do 14 | before do 15 | A = Module.new { def hello; :hello; end } 16 | B = Module.new 17 | C = Module.new 18 | J = Module.new 19 | 20 | M = Module.new 21 | 22 | M.include A, B 23 | M.extend A, B 24 | 25 | C1 = Class.new 26 | C2 = Class.new(C1) 27 | C2.include A 28 | end 29 | 30 | after do 31 | Object.remove_const(:A) 32 | Object.remove_const(:B) 33 | Object.remove_const(:C) 34 | Object.remove_const(:M) 35 | Object.remove_const(:J) 36 | Object.remove_const(:C1) 37 | Object.remove_const(:C2) 38 | end 39 | 40 | describe 'extend-based methods' do 41 | describe 'extend_after' do 42 | it 'should insert module into correct position in singleton class' do 43 | M.extend_above B, J 44 | M.singleton_class.ancestors 45 | M.singleton_class.ancestors[0..2].should == [A, B, J] 46 | end 47 | end 48 | 49 | describe 'temp_extend' do 50 | it 'should temporarily extend the module for the duration of a block' do 51 | lambda { B.hello }.should.raise NoMethodError 52 | B.temp_extend(A) do 53 | B.hello.should == :hello 54 | end 55 | lambda { B.hello }.should.raise NoMethodError 56 | end 57 | 58 | it 'should execute before and after hooks prior to and after running the temp_extend block' do 59 | lambda { B.hello }.should.raise NoMethodError 60 | B.instance_variable_defined?(:@before).should == false 61 | B.instance_variable_defined?(:@after).should == false 62 | B.temp_extend(A, 63 | :before => proc { B.instance_variable_set(:@before, true) }, 64 | :after => proc { B.instance_variable_set(:@after, true) } ) do 65 | B.instance_variable_get(:@before).should == true 66 | B.instance_variable_defined?(:@after).should == false 67 | B.hello.should == :hello 68 | end 69 | B.instance_variable_get(:@after).should == true 70 | B.instance_variable_get(:@before).should == true 71 | lambda { B.hello }.should.raise NoMethodError 72 | end 73 | 74 | describe 'temp_extend_safe' do 75 | it 'should temporarily extend the module for the duration of a block in a threadsafe manner' do 76 | lambda { B.hello }.should.raise NoMethodError 77 | B.temp_extend_safe(A) do 78 | B.hello.should == :hello 79 | end 80 | lambda { B.hello }.should.raise NoMethodError 81 | end 82 | end 83 | 84 | describe 'unextend' do 85 | it 'should unextend the module' do 86 | C.include A, B 87 | M.extend C 88 | M.singleton_class.ancestors[0..2].should == [C, A, B] 89 | M.unextend C 90 | M.singleton_class.ancestors[0..1].should == [A, B] 91 | end 92 | 93 | it 'should unextend the nested module' do 94 | C.include A, B 95 | M.extend C 96 | M.extend J 97 | M.singleton_class.ancestors[0..3].should == [J, C, A, B] 98 | M.unextend C, true 99 | M.singleton_class.ancestors[0..1].should == [J, Module] 100 | end 101 | 102 | it 'should unextend the class of the object' do 103 | o = C2.new 104 | o.singleton_class.ancestors.first.should == C2 105 | o.unextend(C2) 106 | o.singleton_class.ancestors.first.should == A 107 | end 108 | end 109 | 110 | describe 'replace_extended_module' do 111 | it 'should replace the class of the object with a module' do 112 | o = C2.new 113 | o.singleton_class.ancestors.first.should == C2 114 | o.replace_extended_module C2, J 115 | o.singleton_class.ancestors.first.should == J 116 | end 117 | end 118 | end 119 | 120 | describe 'include-based methods' do 121 | describe 'include_after' do 122 | it 'should insert module into correct position' do 123 | M.include_after A, C 124 | M.ancestors[2].should == C 125 | end 126 | end 127 | 128 | describe 'temp_include' do 129 | it 'should temporarily include the module for the duration of a block' do 130 | lambda { "john".hello }.should.raise NoMethodError 131 | String.temp_include(A) do 132 | "john".hello.should == :hello 133 | end 134 | lambda { "john".hello }.should.raise NoMethodError 135 | end 136 | 137 | it 'should execute before and after hooks prior to and after running the temp_include block' do 138 | lambda { B.hello }.should.raise NoMethodError 139 | B.instance_variable_defined?(:@before).should == false 140 | B.instance_variable_defined?(:@after).should == false 141 | B.temp_include(A, 142 | :before => proc { B.instance_variable_set(:@before, true) }, 143 | :after => proc { B.instance_variable_set(:@after, true) } ) do 144 | B.instance_variable_get(:@before).should == true 145 | B.instance_variable_defined?(:@after).should == false 146 | end 147 | B.instance_variable_get(:@after).should == true 148 | B.instance_variable_get(:@before).should == true 149 | lambda { B.hello }.should.raise NoMethodError 150 | end 151 | 152 | end 153 | 154 | describe 'temp_include_safe' do 155 | it 'should temporarily include the module for the duration of a block in a threadsafe manner' do 156 | lambda { "john".hello }.should.raise NoMethodError 157 | String.temp_include_safe(A) do 158 | "john".hello.should == :hello 159 | end 160 | lambda { "john".hello }.should.raise NoMethodError 161 | end 162 | end 163 | 164 | describe 'include_before' do 165 | it 'should insert module into correct position' do 166 | M.include_before B, C 167 | M.ancestors[2].should == C 168 | end 169 | end 170 | 171 | describe 'include_at_top' do 172 | it 'should insert module at top of chain' do 173 | M.include_at_top C 174 | M.ancestors.last.should == C 175 | end 176 | end 177 | 178 | describe 'swap_modules' do 179 | it 'should interchange modules' do 180 | M.ancestors[1..2].should == [A, B] 181 | M.swap_modules A, B 182 | M.ancestors[1..2].should == [B, A] 183 | end 184 | 185 | it 'should do a no-op when source/dest modules are the same' do 186 | M.ancestors[1..2].should == [A, B] 187 | M.swap_modules A, A 188 | M.ancestors[1..2].should == [A, B] 189 | M.swap_modules B, B 190 | M.ancestors[1..2].should == [A, B] 191 | end 192 | 193 | it 'should handle huge ancestor chains without crashing or returning the wrong result' do 194 | size = 100 195 | m = Module.new 196 | size.times do 197 | m.include Module.new 198 | end 199 | 200 | m.ancestors.size.should == size + 1 201 | size.times do 202 | m.swap_modules(m.ancestors[rand(size - 1) + 1], 203 | m.ancestors[rand(size - 1) + 1]) 204 | end 205 | m.ancestors.size.should == size + 1 206 | end 207 | end 208 | 209 | describe '__attached__' do 210 | it 'should return the correct attached object' do 211 | o = Object.new 212 | o.singleton_class.__attached__.should == o 213 | end 214 | end 215 | 216 | describe 'module_move_up' do 217 | it 'should move module up the chain' do 218 | M.ancestors[1..2].should == [A, B] 219 | M.module_move_up A 220 | M.ancestors[1..2].should == [B, A] 221 | end 222 | end 223 | 224 | describe 'module_move_down' do 225 | it 'should move module down the chain' do 226 | M.ancestors[1..2].should == [A, B] 227 | M.module_move_down B 228 | M.ancestors[1..2].should == [B, A] 229 | end 230 | end 231 | 232 | describe 'include_at' do 233 | it 'should include module at specified index' do 234 | M.include_at(1, C) 235 | M.ancestors[1].should == C 236 | end 237 | end 238 | 239 | describe 'remove_module' do 240 | it 'should remove the module' do 241 | M.ancestors[1..2].should == [A, B] 242 | M.remove_module A 243 | M.ancestors[1..2].should == [B] 244 | end 245 | 246 | it 'should remove recursively if second parameter is true' do 247 | klass = Module.new 248 | klass.include J, M, C 249 | klass.ancestors[1..-1].should == [J, M, A, B, C] 250 | klass.remove_module M, true 251 | klass.ancestors[1..-1].should == [J, C] 252 | end 253 | end 254 | 255 | describe 'replace_module' do 256 | it 'should replace the module with another' do 257 | M.ancestors[1..2].should == [A, B] 258 | M.replace_module B, C 259 | M.ancestors[1..2].should == [A, C] 260 | end 261 | 262 | 263 | it 'should replace a class with a module' do 264 | C2.ancestors[0..2].should == [C2, A, C1] 265 | C2.replace_module C1, B 266 | C2.ancestors[0..2].should == [C2, A, B] 267 | end 268 | 269 | it 'should raise when replace_module target is the root module of the chain' do 270 | M.ancestors[0..2].should == [M, A, B] 271 | lambda { M.replace_module M, J }.should.raise RuntimeError 272 | end 273 | end 274 | end 275 | end 276 | end 277 | -------------------------------------------------------------------------------- /ext/remix/remix.c: -------------------------------------------------------------------------------- 1 | /* remix.c */ 2 | /* (C) John Mair 2010 3 | * This program is distributed under the terms of the MIT License 4 | * */ 5 | 6 | #include 7 | #include "compat.h" 8 | 9 | VALUE rb_swap_modules(VALUE self, VALUE mod1, VALUE mod2); 10 | 11 | #define Enforce_Classmod(klass) \ 12 | do { \ 13 | if (!RTEST(rb_obj_is_kind_of(klass, rb_cModule))) \ 14 | rb_raise(rb_eTypeError, "Must be a Module or Class type."); \ 15 | } while(0) 16 | 17 | #define Validate_Type(klass) \ 18 | do { \ 19 | if (TYPE(klass) != T_OBJECT && TYPE(klass) != T_CLASS && TYPE(klass) != T_MODULE && TYPE(klass) != T_ICLASS && \ 20 | TYPE(klass) != T_FALSE) \ 21 | rb_raise(rb_eTypeError, "Must be a T_MODULE, T_CLASS, T_ICLASS, T_OBJECT, or T_FALSE type."); \ 22 | } while(0) 23 | 24 | /* Tiny utility method to return an object attached to a singleton */ 25 | static VALUE 26 | rb_singleton_attached(VALUE self) 27 | { 28 | if(FL_TEST(self, FL_SINGLETON)) 29 | return rb_iv_get(self, "__attached__"); 30 | else 31 | return Qnil; 32 | } 33 | 34 | /* a modified version of include_class_new from class.c */ 35 | static VALUE 36 | j_class_new(VALUE module, VALUE sup) 37 | { 38 | VALUE klass = create_class(T_ICLASS, rb_cClass); 39 | 40 | if (!RCLASS_IV_TBL(module)) { 41 | RCLASS_IV_TBL(module) = (struct st_table *)st_init_numtable(); 42 | } 43 | 44 | /* assign iv_tbl, m_tbl and super */ 45 | RCLASS_IV_TBL(klass) = RCLASS_IV_TBL(module); 46 | 47 | if (TYPE(module) == T_ICLASS) { 48 | if (!RTEST(rb_iv_get(module, "__module__"))) 49 | rb_iv_set(klass, "__module__", KLASS_OF(module)); 50 | } 51 | else if (TYPE(module) == T_MODULE || TYPE(module) == T_CLASS) 52 | rb_iv_set(klass, "__module__", module); 53 | 54 | RCLASS_SUPER(klass) = sup; 55 | if(TYPE(module) != T_OBJECT) { 56 | RCLASS_M_TBL(klass) = RCLASS_M_TBL(module); 57 | } 58 | else { 59 | RCLASS_M_TBL(klass) = RCLASS_M_TBL(CLASS_OF(module)); 60 | } 61 | 62 | if (TYPE(module) == T_ICLASS) { 63 | KLASS_OF(klass) = rb_iv_get(klass, "__module__"); 64 | } 65 | else { 66 | KLASS_OF(klass) = module; 67 | } 68 | 69 | if(TYPE(module) != T_OBJECT) { 70 | OBJ_INFECT(klass, module); 71 | OBJ_INFECT(klass, sup); 72 | } 73 | 74 | return (VALUE)klass; 75 | } 76 | 77 | static VALUE 78 | set_supers(VALUE c) 79 | { 80 | if (RCLASS_SUPER(c) == rb_cObject || RCLASS_SUPER(c) == 0) { 81 | return RCLASS_SUPER(c); 82 | } 83 | else { 84 | return j_class_new(RCLASS_SUPER(c), set_supers(RCLASS_SUPER(c))); 85 | } 86 | } 87 | 88 | VALUE 89 | rb_prepare_for_remix(VALUE klass) 90 | { 91 | Enforce_Classmod(klass); 92 | 93 | /* class chain is already prepared for remixing */ 94 | if (RTEST(rb_iv_get(klass, "__remix_ready__"))) 95 | return klass; 96 | 97 | RCLASS_SUPER(klass) = set_supers(klass); 98 | 99 | rb_iv_set(klass, "__remix_ready__", Qtrue); 100 | 101 | rb_clear_cache(); 102 | return klass; 103 | } 104 | 105 | inline static VALUE 106 | get_source_module(VALUE mod) 107 | { 108 | switch (TYPE(mod)) { 109 | case T_FALSE: 110 | return Qfalse; 111 | break; 112 | case T_ICLASS: 113 | if (RTEST(rb_iv_get(mod, "__module__"))) 114 | return rb_iv_get(mod, "__module__"); 115 | else 116 | return KLASS_OF(mod); 117 | break; 118 | case T_CLASS: 119 | case T_MODULE: 120 | return mod; 121 | break; 122 | case T_OBJECT: 123 | return rb_singleton_class(mod); 124 | break; 125 | default: 126 | Validate_Type(mod); 127 | } 128 | 129 | /* never reached */ 130 | return Qnil; 131 | } 132 | 133 | static VALUE 134 | retrieve_imod_before_mod(VALUE m, VALUE before) 135 | { 136 | Validate_Type(before); 137 | 138 | VALUE k = get_source_module(RCLASS_SUPER(m)); 139 | while(k != before && m != 0 && m != rb_cObject) { 140 | m = RCLASS_SUPER(m); 141 | k = get_source_module(RCLASS_SUPER(m)); 142 | } 143 | if (k != before) 144 | rb_raise(rb_eRuntimeError, "'before' module not found"); 145 | 146 | return m; 147 | } 148 | 149 | static VALUE 150 | retrieve_imod_for_mod(VALUE m, VALUE after) 151 | { 152 | Validate_Type(after); 153 | 154 | VALUE k = get_source_module(m); 155 | while(k != after && m != 0 && m != rb_cObject) { 156 | m = RCLASS_SUPER(m); 157 | k = get_source_module(m); 158 | } 159 | 160 | if (k != after) 161 | rb_raise(rb_eRuntimeError, "'after' module not found"); 162 | 163 | return m; 164 | } 165 | 166 | VALUE 167 | rb_module_move_up(VALUE self, VALUE mod) 168 | { 169 | rb_prepare_for_remix(self); 170 | 171 | if (self == mod) 172 | return self; 173 | 174 | VALUE included_mod = retrieve_imod_for_mod(self, mod); 175 | if (RCLASS_SUPER(included_mod) == rb_cObject || RCLASS_SUPER(included_mod) == 0) 176 | return self; 177 | 178 | rb_swap_modules(self, mod, get_source_module(RCLASS_SUPER(included_mod))); 179 | 180 | return self; 181 | } 182 | 183 | VALUE 184 | rb_module_move_down(VALUE self, VALUE mod) 185 | { 186 | rb_prepare_for_remix(self); 187 | 188 | if (self == mod) 189 | return self; 190 | 191 | VALUE before_included_mod = retrieve_imod_before_mod(self, mod); 192 | if (before_included_mod == self) 193 | return self; 194 | 195 | rb_swap_modules(self, mod, get_source_module(before_included_mod)); 196 | 197 | return self; 198 | } 199 | 200 | VALUE 201 | rb_include_at_top(VALUE self, VALUE mod) 202 | { 203 | rb_prepare_for_remix(self); 204 | 205 | if (TYPE(self) == T_MODULE) 206 | rb_include_module(retrieve_imod_before_mod(self, Qfalse), mod); 207 | else 208 | rb_include_module(retrieve_imod_before_mod(self, rb_cObject), mod); 209 | 210 | return self; 211 | } 212 | 213 | VALUE 214 | rb_include_after(VALUE self, VALUE after, VALUE mod) 215 | { 216 | rb_prepare_for_remix(self); 217 | rb_include_module(retrieve_imod_for_mod(self, after), mod); 218 | return self; 219 | } 220 | 221 | VALUE 222 | rb_include_before(VALUE self, VALUE before, VALUE mod) 223 | { 224 | rb_prepare_for_remix(self); 225 | 226 | if (before == self) 227 | rb_raise(rb_eRuntimeError, "Prepend not supported yet!"); 228 | 229 | rb_include_module(retrieve_imod_before_mod(self, before), mod); 230 | return self; 231 | } 232 | 233 | VALUE 234 | rb_include_at(VALUE self, VALUE rb_index, VALUE mod) 235 | { 236 | rb_prepare_for_remix(self); 237 | 238 | Check_Type(rb_index, T_FIXNUM); 239 | 240 | int index = FIX2INT(rb_index); 241 | VALUE m = self; 242 | 243 | int i = 1; 244 | while(i++ < index && RCLASS_SUPER(m) != 0 && RCLASS_SUPER(m) != rb_cObject) 245 | m = RCLASS_SUPER(m); 246 | 247 | rb_include_module(m, mod); 248 | return self; 249 | } 250 | 251 | #define SWAP(X, Y) {(X) ^= (Y); (Y) ^= (X); (X) ^= (Y);} 252 | 253 | VALUE 254 | rb_swap_modules(VALUE self, VALUE mod1, VALUE mod2) 255 | { 256 | rb_prepare_for_remix(self); 257 | 258 | VALUE before_mod1, before_mod2; 259 | VALUE included_mod1, included_mod2; 260 | 261 | if (mod1 == rb_cObject || mod2 == rb_cObject) rb_raise(rb_eRuntimeError, "can't swap Object"); 262 | if (mod1 == mod2) return self; 263 | 264 | included_mod1 = retrieve_imod_for_mod(self, mod1); 265 | included_mod2 = retrieve_imod_for_mod(self, mod2); 266 | before_mod1 = retrieve_imod_before_mod(self, mod1); 267 | before_mod2 = retrieve_imod_before_mod(self, mod2); 268 | 269 | SWAP(RCLASS_SUPER(before_mod1), RCLASS_SUPER(before_mod2)); 270 | SWAP(RCLASS_SUPER(included_mod1), RCLASS_SUPER(included_mod2)); 271 | 272 | rb_clear_cache(); 273 | 274 | return self; 275 | } 276 | 277 | static void 278 | remove_nested_module(VALUE included_mod, VALUE module) 279 | { 280 | VALUE source_mod = get_source_module(RCLASS_SUPER(included_mod)); 281 | 282 | if (source_mod == rb_cObject || source_mod == Qfalse) 283 | return; 284 | else if (source_mod == get_source_module(RCLASS_SUPER(module))) { 285 | remove_nested_module(RCLASS_SUPER(included_mod), RCLASS_SUPER(module)); 286 | RCLASS_SUPER(included_mod) = RCLASS_SUPER(RCLASS_SUPER(included_mod)); 287 | } 288 | } 289 | 290 | static VALUE 291 | rb_classmod_include_p(VALUE mod, VALUE mod2) 292 | { 293 | VALUE p; 294 | 295 | Enforce_Classmod(mod); 296 | 297 | for (p = RCLASS_SUPER(mod); p; p = RCLASS_SUPER(p)) { 298 | if (BUILTIN_TYPE(p) == T_ICLASS) { 299 | if (RTEST(rb_iv_get(p, "__module__"))) { 300 | if (rb_iv_get(p, "__module__") == mod2) return Qtrue; 301 | } 302 | 303 | if (RBASIC(p)->klass == mod2) return Qtrue; 304 | } 305 | } 306 | return Qfalse; 307 | } 308 | 309 | 310 | VALUE 311 | rb_uninclude(int argc, VALUE * argv, VALUE self) 312 | { 313 | rb_prepare_for_remix(self); 314 | 315 | VALUE mod1, recurse = Qfalse; 316 | rb_scan_args(argc, argv, "11", &mod1, &recurse); 317 | 318 | if (TYPE(mod1) == T_OBJECT) 319 | mod1 = rb_singleton_class(mod1); 320 | 321 | if (!RTEST(rb_classmod_include_p(self, mod1))) 322 | rb_raise(rb_eArgError, "Module not found"); 323 | 324 | VALUE before = retrieve_imod_before_mod(self, mod1); 325 | VALUE included_mod = retrieve_imod_for_mod(self, mod1); 326 | 327 | if (mod1 == rb_cObject) rb_raise(rb_eRuntimeError, "can't delete Object"); 328 | 329 | if (RTEST(recurse)) 330 | remove_nested_module(included_mod, mod1); 331 | 332 | RCLASS_SUPER(before) = RCLASS_SUPER(included_mod); 333 | 334 | rb_clear_cache(); 335 | 336 | return self; 337 | } 338 | 339 | VALUE 340 | rb_replace_module(VALUE self, VALUE mod1, VALUE mod2) 341 | { 342 | rb_prepare_for_remix(self); 343 | 344 | if (mod1 == self) rb_raise(rb_eRuntimeError, "can't replace root module."); 345 | 346 | if (rb_classmod_include_p(self, mod2)) 347 | return rb_swap_modules(self, mod1, mod2); 348 | 349 | if (mod1 == mod2) return; 350 | 351 | VALUE before = retrieve_imod_before_mod(self, mod1); 352 | rb_uninclude(1, &mod1, self); 353 | rb_include_module(before, mod2); 354 | return self; 355 | } 356 | 357 | void 358 | Init_remix() 359 | { 360 | VALUE mRemix = rb_define_module("Remix"); 361 | VALUE mModuleExtensions = rb_define_module_under(mRemix, "ModuleExtensions"); 362 | 363 | rb_define_method(mModuleExtensions, "ready_remix", rb_prepare_for_remix, 0); 364 | rb_define_method(mModuleExtensions, "module_move_up", rb_module_move_up, 1); 365 | rb_define_method(mModuleExtensions, "module_move_down", rb_module_move_down, 1); 366 | 367 | rb_define_method(mModuleExtensions, "include_at", rb_include_at, 2); 368 | rb_define_method(mModuleExtensions, "include_below", rb_include_before, 2); 369 | rb_define_alias(mModuleExtensions, "include_before", "include_below"); 370 | rb_define_method(mModuleExtensions, "include_above", rb_include_after, 2); 371 | rb_define_alias(mModuleExtensions, "include_after", "include_above"); 372 | rb_define_method(mModuleExtensions, "include_at_top", rb_include_at_top, 1); 373 | 374 | rb_define_method(mModuleExtensions, "swap_modules", rb_swap_modules, 2); 375 | rb_define_method(mModuleExtensions, "uninclude", rb_uninclude, -1); 376 | rb_define_alias(mModuleExtensions, "remove_module", "uninclude"); 377 | rb_define_method(mModuleExtensions, "replace_module", rb_replace_module, 2); 378 | 379 | rb_define_method(mModuleExtensions, "__attached__", rb_singleton_attached, 0); 380 | 381 | } 382 | 383 | --------------------------------------------------------------------------------