kinda-hookable 0.0.2 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/lib/hookable.rb +145 -0
- data/lib/kinda-hookable.rb +1 -0
- metadata +3 -1
data/lib/hookable.rb
ADDED
@@ -0,0 +1,145 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'kinda-core'
|
3
|
+
|
4
|
+
module Kinda
|
5
|
+
module Hookable
|
6
|
+
include Core
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
extend container.extensions
|
10
|
+
|
11
|
+
def add_hook(kind, method_name, &block)
|
12
|
+
hooked_methods[method_name.to_sym].hooks[kind] << block
|
13
|
+
patch_method_if_necessary(method_name.to_sym)
|
14
|
+
end
|
15
|
+
|
16
|
+
[:before, :after, :around].each do |kind|
|
17
|
+
define_method(kind) do |method_name, &block|
|
18
|
+
add_hook(kind, method_name, &block)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
alias_method :on, :after
|
23
|
+
|
24
|
+
def exec_method(method_name, original_method, original_self, *args, &block)
|
25
|
+
result = nil
|
26
|
+
|
27
|
+
chained_hooks = [] << lambda do |*args, &block|
|
28
|
+
find_hooks(method_name, :before).each do |hook|
|
29
|
+
original_self.call_with_this(hook, *args, &block)
|
30
|
+
end
|
31
|
+
result = original_method.bind(original_self).call(*args, &block) if original_method
|
32
|
+
find_hooks(method_name, :after).each do |hook|
|
33
|
+
original_self.call_with_this(hook, *args, &block)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
hooks = find_hooks(method_name, :around)
|
38
|
+
hooks.each_index do |index|
|
39
|
+
chained_hooks << lambda do |*args, &block|
|
40
|
+
original_self.call_with_this(hooks[index], chained_hooks[index], *args, &block)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
chained_hooks.last.call(*args, &block)
|
44
|
+
|
45
|
+
result
|
46
|
+
end
|
47
|
+
|
48
|
+
def hooked_methods
|
49
|
+
@hooked_methods ||= Hash.new do |hash, method_name|
|
50
|
+
hash[method_name] = HookedMethod.new(self, method_name)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def find_hooks(method_name, kind)
|
55
|
+
hooks = []
|
56
|
+
self_and_ancestors.each do |ancestor|
|
57
|
+
ancestor.is_a?(Module) ? next : break unless ancestor.respond_to?(:hooked_methods)
|
58
|
+
next unless ancestor.hooked_methods.include?(method_name)
|
59
|
+
next unless ancestor.hooked_methods[method_name].hooks.include?(kind)
|
60
|
+
hooks.concat(ancestor.hooked_methods[method_name].hooks[kind])
|
61
|
+
end
|
62
|
+
hooks
|
63
|
+
end
|
64
|
+
|
65
|
+
def hooked_method_defined?(method_name)
|
66
|
+
self_and_ancestors.each do |ancestor|
|
67
|
+
ancestor.is_a?(Module) ? next : break unless ancestor.respond_to?(:hooked_methods)
|
68
|
+
return true if ancestor.hooked_methods.include?(method_name)
|
69
|
+
end
|
70
|
+
false
|
71
|
+
end
|
72
|
+
|
73
|
+
def method_added(method_name)
|
74
|
+
super
|
75
|
+
if hooked_method_defined?(method_name)
|
76
|
+
patch_method(method_name)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def patch_method(method_name)
|
81
|
+
return if Thread.current[:__adding_method__]
|
82
|
+
# puts "Patching method ##{method_name} in #{self.inspect}"
|
83
|
+
original_method = instance_method(method_name)
|
84
|
+
Thread.current[:__adding_method__] = true
|
85
|
+
define_method(method_name) do |*args, &block|
|
86
|
+
singleton_class.exec_method(method_name, original_method, self, *args, &block)
|
87
|
+
end
|
88
|
+
Thread.current[:__adding_method__] = false
|
89
|
+
hooked_methods[method_name].method_patched = true
|
90
|
+
end
|
91
|
+
|
92
|
+
def patch_method_if_necessary(method_name)
|
93
|
+
self_and_ancestors.each do |ancestor|
|
94
|
+
ancestor.is_a?(Module) ? next : break unless ancestor.respond_to?(:method_defined_in_this_class?)
|
95
|
+
if ancestor.method_defined_in_this_class?(method_name)
|
96
|
+
ancestor.patch_method(method_name) unless ancestor.hooked_methods[method_name].method_patched
|
97
|
+
break
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def method_defined_in_this_class?(name) # Works for singleton class too
|
103
|
+
instance_method(name).owner == self rescue false
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
inheritable_extension ClassMethods
|
108
|
+
|
109
|
+
###
|
110
|
+
|
111
|
+
delegate_to_class :before
|
112
|
+
delegate_to_class :after
|
113
|
+
alias_method :on, :after
|
114
|
+
delegate_to_class :around
|
115
|
+
delegate_to_class :singleton_method_added, :method_added
|
116
|
+
|
117
|
+
def method_missing(method_name, *args, &block)
|
118
|
+
if singleton_class.hooked_method_defined?(method_name)
|
119
|
+
singleton_class.exec_method(method_name, nil, self, *args, &block)
|
120
|
+
else
|
121
|
+
super
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def respond_to?(method_name, include_private=false)
|
126
|
+
super || singleton_class.hooked_method_defined?(method_name)
|
127
|
+
end
|
128
|
+
|
129
|
+
###
|
130
|
+
|
131
|
+
class HookedMethod
|
132
|
+
attr_reader :klass, :name
|
133
|
+
attr_accessor :method_patched
|
134
|
+
|
135
|
+
def initialize(klass, name)
|
136
|
+
@klass = klass
|
137
|
+
@name = name
|
138
|
+
end
|
139
|
+
|
140
|
+
def hooks
|
141
|
+
@hooks ||= Hash.new { |hash, kind| hash[kind] = [] }
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'hookable')
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: kinda-hookable
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Manuel Vila
|
@@ -31,6 +31,8 @@ extensions: []
|
|
31
31
|
extra_rdoc_files:
|
32
32
|
- README.rdoc
|
33
33
|
files:
|
34
|
+
- lib/hookable.rb
|
35
|
+
- lib/kinda-hookable.rb
|
34
36
|
- README.rdoc
|
35
37
|
has_rdoc: true
|
36
38
|
homepage: http://github.com/kinda/hookable
|