Class: MonkeyShield

Inherits:
Object
  • Object
show all
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

Class Method Details

.alias_method_added_hooksObject



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_stackObject



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_contextObject



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

Returns:

  • (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_contextObject



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_nameObject



235
236
237
# File 'lib/monkey_shield.rb', line 235

def temp_method_name
  "__MONKEY__temp_method__#{unique}"
end

.uniqueObject



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

.warningsObject



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