Class: MonkeyShield
- Inherits:
-
Object
- Object
- MonkeyShield
- Defined in:
- lib/monkey_shield.rb
Defined Under Namespace
Classes: MethodDefinedInModuleCallsSuper, NoContextError
Constant Summary collapse
- VERSION =
"0.1.0"
- UNIQUE_METHOD_NAMES =
{}
- CONTEXT_WRAPPED_METHODS =
Hash.new{|h,k| h[k] = [] }
Class Method Summary collapse
- .alias_method_added_hooks ⇒ Object
- .context_stack ⇒ Object
- .context_switch_for(klass, method_name) ⇒ Object
- .current_context ⇒ Object
- .hook_method_added(hook = true, &blk) ⇒ Object
- .hook_method_added? ⇒ Boolean
- .hook_module_function(&blk) ⇒ Object
- .ignore_method_added(&blk) ⇒ Object
- .in_context(context, &blk) ⇒ Object
- .pop_context ⇒ Object
- .prefix_with_context(method_name, context) ⇒ Object
- .push_context(context) ⇒ Object
- .reset! ⇒ Object
- .set_default_context_for(klass, method_name, context) ⇒ Object
- .temp_method_name ⇒ Object
- .unique ⇒ Object
- .unique_method_name(method_name) ⇒ Object
- .warnings ⇒ Object
- .wrap_method_with_context(klass, method_name, context) ⇒ Object
- .wrap_with_context(context, exceptions = [], &blk) ⇒ Object
Class Method Details
.alias_method_added_hooks ⇒ Object
148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 |
# File 'lib/monkey_shield.rb', line 148 def alias_method_added_hooks return if @method_added_hooks_aliased # TODO; catch method_added being added and automatically wrap it # these list must contain all classes any other library might override method_added on (eg. BlankSlate classes) klasses = [Module, (class << Object; self; end), (class << Kernel; self; end)] klasses.each do |klass| klass.class_eval do if method_defined? :method_added old_method_added = MonkeyShield.unique_method_name(:method_added) alias_method old_method_added, :method_added end if method_defined? :singleton_method_added old_singleton_method_added = MonkeyShield.unique_method_name(:singleton_method_added) alias_method old_singleton_method_added, :singleton_method_added end class_eval <<-EOF, __FILE__, __LINE__ def __MONKEY__method_added__proxy method_name __MONKEY__method_added(self, method_name) #{old_method_added && "#{old_method_added} method_name"} end def __MONKEY__singleton_method_added__proxy method_name __MONKEY__method_added((class<<self;self;end), method_name) #{old_singleton_method_added && "#{old_singleton_method_added} method_name"} end EOF alias_method :method_added, :__MONKEY__method_added__proxy alias_method :singleton_method_added, :__MONKEY__singleton_method_added__proxy end end @method_added_hooks_aliased = true end |
.context_stack ⇒ Object
206 207 208 |
# File 'lib/monkey_shield.rb', line 206 def context_stack s = Thread.current[:__MONKEY__method_context] and s.dup end |
.context_switch_for(klass, method_name) ⇒ Object
114 115 116 117 118 119 120 121 122 123 124 125 |
# File 'lib/monkey_shield.rb', line 114 def context_switch_for klass, method_name klass.class_eval do class_eval <<-EOF, __FILE__, __LINE__ def #{tmp_name = MonkeyShield.temp_method_name} *args, &blk raise NoContextError if ! MonkeyShield.current_context __send__(MonkeyShield.prefix_with_context(#{method_name.inspect}, MonkeyShield.current_context), *args, &blk) end EOF alias_method method_name, tmp_name remove_method tmp_name end end |
.current_context ⇒ Object
210 211 212 |
# File 'lib/monkey_shield.rb', line 210 def current_context s = Thread.current[:__MONKEY__method_context] and s.last end |
.hook_method_added(hook = true, &blk) ⇒ Object
239 240 241 242 243 244 |
# File 'lib/monkey_shield.rb', line 239 def hook_method_added(hook = true, &blk) orig, @hook_method_added = @hook_method_added, hook yield ensure @hook_method_added = orig end |
.hook_method_added? ⇒ Boolean
250 251 252 |
# File 'lib/monkey_shield.rb', line 250 def hook_method_added? @hook_method_added end |
.hook_module_function(&blk) ⇒ Object
187 188 189 190 191 192 193 194 195 196 197 |
# File 'lib/monkey_shield.rb', line 187 def hook_module_function(&blk) old_module_function = MonkeyShield.unique_method_name(:module_function) Module.class_eval do alias_method old_module_function, :module_function alias_method :module_function, :__MONKEY__module_function end yield ensure Module.class_eval { alias_method :module_function, old_module_function } end |
.ignore_method_added(&blk) ⇒ Object
246 247 248 |
# File 'lib/monkey_shield.rb', line 246 def ignore_method_added(&blk) hook_method_added false, &blk end |
.in_context(context, &blk) ⇒ Object
199 200 201 202 203 204 |
# File 'lib/monkey_shield.rb', line 199 def in_context(context, &blk) push_context context yield ensure pop_context end |
.pop_context ⇒ Object
218 219 220 |
# File 'lib/monkey_shield.rb', line 218 def pop_context s = Thread.current[:__MONKEY__method_context] and s.pop end |
.prefix_with_context(method_name, context) ⇒ Object
227 228 229 |
# File 'lib/monkey_shield.rb', line 227 def prefix_with_context(method_name, context) "__MONKEY__context__#{context}__#{method_name}" end |
.push_context(context) ⇒ Object
214 215 216 |
# File 'lib/monkey_shield.rb', line 214 def push_context(context) (Thread.current[:__MONKEY__method_context] ||= []).push context end |
.reset! ⇒ Object
109 110 111 112 |
# File 'lib/monkey_shield.rb', line 109 def reset! CONTEXT_WRAPPED_METHODS.clear UNIQUE_METHOD_NAMES.clear end |
.set_default_context_for(klass, method_name, context) ⇒ Object
127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 |
# File 'lib/monkey_shield.rb', line 127 def set_default_context_for klass, method_name, context context_switched_name = "__context_switch__#{MonkeyShield.unique}__#{method_name}" klass.class_eval do alias_method context_switched_name, method_name class_eval <<-EOF, __FILE__, __LINE__ def #{tmp_name = MonkeyShield.temp_method_name} *args, &blk if ! MonkeyShield.current_context need_pop = true MonkeyShield.push_context #{context.inspect} end __send__(#{context_switched_name.inspect}, *args, &blk) ensure MonkeyShield.pop_context if need_pop end EOF alias_method method_name, tmp_name remove_method tmp_name end end |
.temp_method_name ⇒ Object
235 236 237 |
# File 'lib/monkey_shield.rb', line 235 def temp_method_name "__MONKEY__temp_method__#{unique}" end |
.unique ⇒ Object
222 223 224 225 |
# File 'lib/monkey_shield.rb', line 222 def unique $unique_counter ||= 0 $unique_counter += 1 end |
.unique_method_name(method_name) ⇒ Object
231 232 233 |
# File 'lib/monkey_shield.rb', line 231 def unique_method_name(method_name) "__MONKEY__unique_method__#{unique}__#{method_name}" end |
.warnings ⇒ Object
254 255 256 257 258 |
# File 'lib/monkey_shield.rb', line 254 def warnings if Object.const_defined? :BasicObject and ! $LOADED_FEATURES.grep(/facets.+basic.?object/).empty? raise "BasicObject on Facets <= 2.4.1 will BREAK this library, use alternative blankslate/basicobject class" end end |
.wrap_method_with_context(klass, method_name, context) ⇒ Object
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 |
# File 'lib/monkey_shield.rb', line 71 def wrap_method_with_context(klass, method_name, context) klass.class_eval do visibility = instance_method_visibility method_name return if ! visibility # something else removed the method already, wth! method_name_with_context = MonkeyShield.prefix_with_context(method_name, context) unique_method_name = MonkeyShield.unique_method_name(method_name) alias_method method_name_with_context, method_name alias_method unique_method_name, method_name private method_name_with_context, unique_method_name UNIQUE_METHOD_NAMES[ [self, method_name] ] = unique_method_name CONTEXT_WRAPPED_METHODS[ [self, method_name] ] << context class_eval <<-EOF, __FILE__, __LINE__ def #{tmp_name = MonkeyShield.temp_method_name} *args, &blk MonkeyShield.in_context #{context.inspect} do __send__(#{unique_method_name.inspect}, *args, &blk) end rescue NoMethodError if $!.message =~ /super: no superclass method `(.+?)'/ raise MonkeyShield::MethodDefinedInModuleCallsSuper, "Please add #{self.name}##{method_name} to the exceptions list!" end raise end EOF alias_method method_name, tmp_name remove_method tmp_name send visibility, method_name end rescue puts "failed to wrap #{klass.name}##{method_name}: #{$!}" puts $!.backtrace.join("\n") end |
.wrap_with_context(context, exceptions = [], &blk) ⇒ Object
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
# File 'lib/monkey_shield.rb', line 49 def wrap_with_context(context, exceptions = [], &blk) Module.class_eval do define_method :__MONKEY__method_added do |klass, method_name| return unless MonkeyShield.hook_method_added? return if exceptions.include? method_name or exceptions.include? "#{klass.name}##{method_name}" or exceptions.any? {|ex| ex.is_a? Regexp and ex =~ method_name.to_s } MonkeyShield.ignore_method_added { MonkeyShield.wrap_method_with_context(klass, method_name, context) } end end MonkeyShield.alias_method_added_hooks MonkeyShield.hook_module_function do MonkeyShield.hook_method_added do yield end end MonkeyShield.warnings end |