chubas-peeping 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +7 -6
- data/VERSION +1 -1
- data/lib/peeping/exceptions.rb +1 -0
- data/lib/peeping/hooks/class_hooks.rb +150 -0
- data/lib/peeping/hooks/hooking.rb +42 -0
- data/lib/peeping/hooks/instance_hooks.rb +145 -0
- data/lib/peeping/hooks/singleton_hooks.rb +165 -0
- data/lib/peeping/peeping.rb +10 -283
- data/lib/peeping.rb +1 -2
- data/peeping.gemspec +62 -0
- data/spec/hooked_behavior_spec.rb +5 -4
- data/spec/selective_hook_management_spec.rb +190 -0
- metadata +9 -3
- data/lib/peeping/hook.rb +0 -8
data/README.markdown
CHANGED
@@ -59,16 +59,17 @@ produces output:
|
|
59
59
|
|
60
60
|
## Tests
|
61
61
|
|
62
|
-
The library comes with its rspec test suite, located in folder
|
62
|
+
The library comes with its rspec test suite, located in folder _spec_
|
63
63
|
|
64
64
|
## Updates
|
65
65
|
|
66
|
+
15 Jul 09 - Finished hook behavior specification. Version 1.1.0 released
|
67
|
+
|
66
68
|
## TODO
|
67
69
|
|
68
|
-
|
69
|
-
|
70
|
-
-
|
71
|
-
-
|
72
|
-
- Gem packaging
|
70
|
+
( Several previously marked TODO's are not anymore. Peeping it's not aimed at being a full framework )
|
71
|
+
|
72
|
+
- Refactor classes (lot of ugly things there)
|
73
|
+
- Add option to override ot keep instance hooks when defining singleton hooks
|
73
74
|
|
74
75
|
[1]: http://rdoc.info/projects/chubas/peeping
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.
|
1
|
+
1.1.0
|
data/lib/peeping/exceptions.rb
CHANGED
@@ -0,0 +1,150 @@
|
|
1
|
+
require 'peeping/hooks/hooking'
|
2
|
+
|
3
|
+
module Peeping
|
4
|
+
module ClassMethodHooks
|
5
|
+
|
6
|
+
#--
|
7
|
+
# Extend class and instance methods to the base class
|
8
|
+
#++
|
9
|
+
def self.included(base) #:nodoc:
|
10
|
+
base.extend Peeping::ClassMethodHooks::ClassMethods
|
11
|
+
base.class_eval do
|
12
|
+
include Peeping::ClassMethodHooks::InstanceMethods
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
module ClassMethods
|
17
|
+
|
18
|
+
include Hooking
|
19
|
+
|
20
|
+
# Holds the hooks declared for class methods
|
21
|
+
CLASS_METHOD_HOOKS = {}
|
22
|
+
|
23
|
+
# Returns true if hooks given for class methods of the class +klass+ exist
|
24
|
+
def is_class_hooked?(klass)
|
25
|
+
CLASS_METHOD_HOOKS.has_key?(klass) and not CLASS_METHOD_HOOKS[klass].empty?
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns a hash containing all hooked methods for class +klass+ as keys, or an empty hash if don't exist
|
29
|
+
def hooked_class_methods_for(klass)
|
30
|
+
CLASS_METHOD_HOOKS[klass] || {}
|
31
|
+
end
|
32
|
+
|
33
|
+
# Adds +hooks+ hooks to the given +klass+ class methods +hooked_methods+
|
34
|
+
# This parameter can be either a symbol or an array of symbols.
|
35
|
+
#
|
36
|
+
# Possible thrown exceptions:
|
37
|
+
# NotAClassException:: When the specified parameter +klass+ is not a class
|
38
|
+
# UndefinedMethodException:: When one of the symbols in +hooked_methods+ param is effectively not a defined class method
|
39
|
+
# AlreadyDefinedHookException:: When a hook for this class and specified method exists
|
40
|
+
# InvalidHooksException:: When hooks contains keys other than :before and :after calls
|
41
|
+
def hook_class!(klass, hooked_methods, hooks)
|
42
|
+
|
43
|
+
hooked_methods = [hooked_methods] if hooked_methods.is_a? Symbol
|
44
|
+
|
45
|
+
validate_hooks(hooks) # Validate hooks
|
46
|
+
validate_is_class(klass) # Validate class
|
47
|
+
|
48
|
+
hooked_methods.each do |hooked_method| # Validate methods defined
|
49
|
+
validate_has_method_defined(klass.metaclass, hooked_method)
|
50
|
+
end
|
51
|
+
|
52
|
+
hooked_methods.each do |hooked_method|
|
53
|
+
CLASS_METHOD_HOOKS[klass] ||= {}
|
54
|
+
new_hooked_method_name = "#{CLASS_HOOK_METHOD_PREFIX}#{hooked_method}"
|
55
|
+
|
56
|
+
if CLASS_METHOD_HOOKS[klass].has_key?(hooked_method)
|
57
|
+
raise AlreadyDefinedHookException.new("Hook signature present for #{hooked_method} in #{klass.metaclass}")
|
58
|
+
end
|
59
|
+
if klass.metaclass.method_defined?(new_hooked_method_name)
|
60
|
+
raise AlreadyDefinedHookException.new("Method #{new_hooked_method_name} hook already defined for #{hooked_method} in #{klass.metaclass}")
|
61
|
+
end
|
62
|
+
|
63
|
+
CLASS_METHOD_HOOKS[klass][hooked_method] = {}
|
64
|
+
|
65
|
+
hooks.each do |where, callback|
|
66
|
+
CLASS_METHOD_HOOKS[klass][hooked_method][where] = callback
|
67
|
+
end
|
68
|
+
|
69
|
+
hook_key = klass.name
|
70
|
+
before_hook_call = <<-BEFORE_HOOK_CALL
|
71
|
+
before_hook_callback = Peeping::Peep.hooked_class_methods_for(#{hook_key})[:"#{hooked_method}"][:before]
|
72
|
+
before_hook_callback.call(self, *args) if before_hook_callback
|
73
|
+
BEFORE_HOOK_CALL
|
74
|
+
|
75
|
+
after_hook_call = <<-AFTER_HOOK_CALL
|
76
|
+
after_hook_callback = Peeping::Peep.hooked_class_methods_for(#{hook_key})[:"#{hooked_method}"][:after]
|
77
|
+
after_hook_callback.call(self, proxied_result) if after_hook_callback
|
78
|
+
AFTER_HOOK_CALL
|
79
|
+
|
80
|
+
class_eval_call = Proc.new do
|
81
|
+
eval <<-REDEF
|
82
|
+
alias :"#{new_hooked_method_name}" :"#{hooked_method}"
|
83
|
+
def #{hooked_method}(*args, &block)
|
84
|
+
#{before_hook_call}
|
85
|
+
proxied_result = if block_given?
|
86
|
+
__send__("#{new_hooked_method_name}", *args, &block)
|
87
|
+
else
|
88
|
+
__send__("#{new_hooked_method_name}", *args)
|
89
|
+
end
|
90
|
+
#{after_hook_call}
|
91
|
+
proxied_result
|
92
|
+
end
|
93
|
+
REDEF
|
94
|
+
end
|
95
|
+
klass.metaclass.class_eval(&class_eval_call)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Removes hook class methods as well as returns the hooked methods to their original definition
|
100
|
+
def unhook_class!(klass, methods = :all, hooks = :all)
|
101
|
+
methods = (methods == :all) ? CLASS_METHOD_HOOKS[klass].keys : (methods.is_a?(Symbol) ? [methods] : methods)
|
102
|
+
raise ArgumentError("Valid arguments: :before, :after or :all") unless [:before, :after, :all].include?(hooks)
|
103
|
+
|
104
|
+
# Validate all methods exist before doing anything
|
105
|
+
methods.each do |method|
|
106
|
+
raise UndefinedHookException.new("No hook defined for class method #{method})") unless CLASS_METHOD_HOOKS[klass][method]
|
107
|
+
end
|
108
|
+
|
109
|
+
methods.each do |method|
|
110
|
+
if hooks == :all
|
111
|
+
klass.metaclass.class_eval <<-REDEF_OLD_METHOD
|
112
|
+
alias :"#{method}" :"#{CLASS_HOOK_METHOD_PREFIX}#{method}"
|
113
|
+
undef :"#{CLASS_HOOK_METHOD_PREFIX}#{method}"
|
114
|
+
REDEF_OLD_METHOD
|
115
|
+
CLASS_METHOD_HOOKS[klass].delete(method)
|
116
|
+
else
|
117
|
+
unless CLASS_METHOD_HOOKS[klass][method][hooks]
|
118
|
+
raise UndefinedHookException.new("No hook defined for class method #{method}) at #{hooks.inspect}")
|
119
|
+
end
|
120
|
+
CLASS_METHOD_HOOKS[klass][method].delete(hooks)
|
121
|
+
CLASS_METHOD_HOOKS[klass].delete(method) if CLASS_METHOD_HOOKS[klass][method].empty?
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
CLASS_METHOD_HOOKS.delete(klass) if methods == :all or (CLASS_METHOD_HOOKS[klass] || {}).empty?
|
126
|
+
|
127
|
+
end
|
128
|
+
|
129
|
+
def clear_all_class_hooks!
|
130
|
+
CLASS_METHOD_HOOKS.each do |klass, hooked_methods|
|
131
|
+
hooked_methods.each do |hooked_method, callbacks|
|
132
|
+
redefined_method = "#{CLASS_HOOK_METHOD_PREFIX}#{hooked_method}"
|
133
|
+
klass.metaclass.class_eval <<-UNDEF_EVAL
|
134
|
+
if method_defined?(:"#{redefined_method}")
|
135
|
+
alias :"#{hooked_method}" :"#{redefined_method}"
|
136
|
+
undef :"#{redefined_method}"
|
137
|
+
end
|
138
|
+
UNDEF_EVAL
|
139
|
+
end
|
140
|
+
end
|
141
|
+
CLASS_METHOD_HOOKS.clear
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
145
|
+
|
146
|
+
module InstanceMethods
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
150
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'peeping/exceptions'
|
2
|
+
|
3
|
+
module Peeping
|
4
|
+
module Hooking
|
5
|
+
|
6
|
+
HOOKED_METHOD_PREFIX = '__peeping__hooked'
|
7
|
+
|
8
|
+
# Prefix for hooked class methods
|
9
|
+
CLASS_HOOK_METHOD_PREFIX = "#{HOOKED_METHOD_PREFIX}__class__method__"
|
10
|
+
|
11
|
+
# Prefix for hooked class methods
|
12
|
+
INSTANCE_HOOK_METHOD_PREFIX = "#{HOOKED_METHOD_PREFIX}__instance__method__"
|
13
|
+
|
14
|
+
# Prefix for hooked class methods
|
15
|
+
SINGLETON_HOOK_METHOD_PREFIX = "#{HOOKED_METHOD_PREFIX}__singleton__method__"
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
# Validates that the array consists only in the keys :before or/and :after
|
20
|
+
#--
|
21
|
+
# TODO: Add extensibility for possible keys. Ideas: warn_on_override, :dont_override, :execute_in_place
|
22
|
+
#++
|
23
|
+
def validate_hooks(hooks)
|
24
|
+
raise InvalidHooksException.new("At least an :after or a :before hook are expected") if hooks.empty?
|
25
|
+
unknown_keys = hooks.keys - [:before, :after]
|
26
|
+
raise InvalidHooksException.new("Unknown keys #{unknown_keys.join(', ')}") unless unknown_keys.empty?
|
27
|
+
end
|
28
|
+
|
29
|
+
# Validates that the passed object is a class
|
30
|
+
def validate_is_class(klass)
|
31
|
+
raise NotAClassException.new("#{klass} is not a Class") unless klass.is_a? Class
|
32
|
+
end
|
33
|
+
|
34
|
+
# Validates that the the method that will be hooked for the given object is defined.
|
35
|
+
def validate_has_method_defined(holding_class, method)
|
36
|
+
unless holding_class.method_defined?(method)
|
37
|
+
raise UndefinedMethodException.new("Undefined method #{method.inspect} for #{holding_class}")
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
require 'peeping/hooks/hooking'
|
2
|
+
|
3
|
+
module Peeping
|
4
|
+
module InstanceMethodHooks
|
5
|
+
|
6
|
+
def self.included(base)
|
7
|
+
base.extend Peeping::InstanceMethodHooks::ClassMethods
|
8
|
+
base.class_eval do
|
9
|
+
include Peeping::InstanceMethodHooks::InstanceMethods
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
|
15
|
+
include Hooking
|
16
|
+
|
17
|
+
# Holds the hooks defined for instance methods
|
18
|
+
INSTANCE_METHOD_HOOKS = {}
|
19
|
+
|
20
|
+
# Returns true if hooks given for instance methods of the class +klass+ exist
|
21
|
+
def are_instances_hooked?(klass)
|
22
|
+
INSTANCE_METHOD_HOOKS.has_key?(klass)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Returns a hash containing all hooked methods for class +klass+ as keys, or an empty hash if don't exist
|
26
|
+
def hooked_instance_methods_for(klass)
|
27
|
+
INSTANCE_METHOD_HOOKS[klass] || {}
|
28
|
+
end
|
29
|
+
|
30
|
+
# Adds +hooks+ hooks to the given +klass+ instance methods +hooked_methods+
|
31
|
+
# This parameter can be either a symbol or an array of symbols.
|
32
|
+
#
|
33
|
+
# Possible thrown exceptions:
|
34
|
+
# NotAClassException:: When the specified parameter +klass+ is not a class
|
35
|
+
# UndefinedMethodException:: When one of the symbols in +hooked_methods+ param is effectively not a defined instance method
|
36
|
+
# AlreadyDefinedHookException:: When a hook for this class and specified instance method exists
|
37
|
+
# InvalidHooksException:: When hooks contains keys other than :before and :after calls
|
38
|
+
def hook_instances!(klass, hooked_methods, hooks)
|
39
|
+
hooked_methods = [hooked_methods] if hooked_methods.is_a? Symbol
|
40
|
+
|
41
|
+
#=== Validations ===
|
42
|
+
validate_hooks(hooks)
|
43
|
+
validate_is_class(klass)
|
44
|
+
hooked_methods.each { |hooked_method| validate_has_method_defined(klass, hooked_method) }
|
45
|
+
|
46
|
+
hooked_methods.each do |hooked_method|
|
47
|
+
INSTANCE_METHOD_HOOKS[klass] ||= {}
|
48
|
+
hooked_method_name = "#{INSTANCE_HOOK_METHOD_PREFIX}#{hooked_method}"
|
49
|
+
|
50
|
+
if INSTANCE_METHOD_HOOKS[klass].has_key?(hooked_method)
|
51
|
+
raise AlreadyDefinedHookException.new("Hook signature present for #{hooked_method} in #{klass}")
|
52
|
+
end
|
53
|
+
if klass.method_defined?(hooked_method_name)
|
54
|
+
raise AlreadyDefinedHookException.new("Method #{hooked_method_name} hook already defined for #{hooked_method} in #{klass}")
|
55
|
+
end
|
56
|
+
|
57
|
+
INSTANCE_METHOD_HOOKS[klass][hooked_method] = {}
|
58
|
+
|
59
|
+
hooks.each do |where, callback|
|
60
|
+
INSTANCE_METHOD_HOOKS[klass][hooked_method][where] = callback
|
61
|
+
end
|
62
|
+
|
63
|
+
hook_key = klass.name
|
64
|
+
before_hook_call = <<-BEFORE_HOOK_CALL
|
65
|
+
before_hook_callback = Peeping::Peep.hooked_instance_methods_for(#{hook_key})[:"#{hooked_method}"][:before]
|
66
|
+
before_hook_callback.call(self, *args) if before_hook_callback
|
67
|
+
BEFORE_HOOK_CALL
|
68
|
+
|
69
|
+
after_hook_call = <<-AFTER_HOOK_CALL
|
70
|
+
after_hook_callback = Peeping::Peep.hooked_instance_methods_for(#{hook_key})[:"#{hooked_method}"][:after]
|
71
|
+
after_hook_callback.call(self, proxied_result) if after_hook_callback
|
72
|
+
AFTER_HOOK_CALL
|
73
|
+
|
74
|
+
class_eval_call = Proc.new do
|
75
|
+
eval <<-REDEF
|
76
|
+
alias :"#{hooked_method_name}" :"#{hooked_method}"
|
77
|
+
def #{hooked_method}(*args, &block)
|
78
|
+
#{before_hook_call}
|
79
|
+
proxied_result = if block_given?
|
80
|
+
__send__("#{hooked_method_name}", *args, &block)
|
81
|
+
else
|
82
|
+
__send__("#{hooked_method_name}", *args)
|
83
|
+
end
|
84
|
+
#{after_hook_call}
|
85
|
+
proxied_result
|
86
|
+
end
|
87
|
+
REDEF
|
88
|
+
end
|
89
|
+
klass.class_eval(&class_eval_call)
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
|
94
|
+
# Removes hook instance methods as well as returns the hooked methods to their original definition
|
95
|
+
def unhook_instances!(klass, methods = :all, hooks = :all)
|
96
|
+
methods = (methods == :all) ? INSTANCE_METHOD_HOOKS[klass].keys : (methods.is_a?(Symbol) ? [methods] : methods)
|
97
|
+
raise ArgumentError("Valid arguments: :before, :after or :all") unless [:before, :after, :all].include?(hooks)
|
98
|
+
|
99
|
+
# Validate all methods exist before doing anything
|
100
|
+
methods.each do |method|
|
101
|
+
raise UndefinedHookException.new("No hook defined for instance method #{method})") unless INSTANCE_METHOD_HOOKS[klass][method]
|
102
|
+
end
|
103
|
+
|
104
|
+
methods.each do |method|
|
105
|
+
if hooks == :all
|
106
|
+
klass.class_eval <<-REDEF_OLD_METHOD
|
107
|
+
alias :"#{method}" :"#{INSTANCE_HOOK_METHOD_PREFIX}#{method}"
|
108
|
+
undef :"#{INSTANCE_HOOK_METHOD_PREFIX}#{method}"
|
109
|
+
REDEF_OLD_METHOD
|
110
|
+
INSTANCE_METHOD_HOOKS[klass].delete(method)
|
111
|
+
else
|
112
|
+
unless INSTANCE_METHOD_HOOKS[klass][method][hooks]
|
113
|
+
raise UndefinedHookException.new("No hook defined for instance method #{method}) at #{hooks.inspect}")
|
114
|
+
end
|
115
|
+
INSTANCE_METHOD_HOOKS[klass][method].delete(hooks)
|
116
|
+
INSTANCE_METHOD_HOOKS[klass].delete(method) if INSTANCE_METHOD_HOOKS[klass][method].empty?
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
INSTANCE_METHOD_HOOKS.delete(klass) if methods == :all or (INSTANCE_METHOD_HOOKS[klass] || {}).empty?
|
121
|
+
|
122
|
+
end
|
123
|
+
|
124
|
+
def clear_all_instance_hooks!
|
125
|
+
INSTANCE_METHOD_HOOKS.each do |klass, hooked_methods|
|
126
|
+
hooked_methods.each do |hooked_method, callbacks|
|
127
|
+
redefined_method = "#{INSTANCE_HOOK_METHOD_PREFIX}#{hooked_method}"
|
128
|
+
klass.class_eval <<-UNDEF_EVAL
|
129
|
+
if method_defined?(:"#{redefined_method}")
|
130
|
+
alias :"#{hooked_method}" :"#{redefined_method}"
|
131
|
+
undef :"#{redefined_method}"
|
132
|
+
end
|
133
|
+
UNDEF_EVAL
|
134
|
+
end
|
135
|
+
end
|
136
|
+
INSTANCE_METHOD_HOOKS.clear
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
140
|
+
|
141
|
+
module InstanceMethods
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
145
|
+
end
|
@@ -0,0 +1,165 @@
|
|
1
|
+
require 'peeping/hooks/hooking'
|
2
|
+
|
3
|
+
module Peeping
|
4
|
+
module SingletonMethodHooks
|
5
|
+
|
6
|
+
#--
|
7
|
+
# Extend class and instance methods to the base class
|
8
|
+
#++
|
9
|
+
def self.included(base) #:nodoc:
|
10
|
+
base.extend Peeping::SingletonMethodHooks::ClassMethods
|
11
|
+
base.class_eval do
|
12
|
+
include Peeping::SingletonMethodHooks::InstanceMethods
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
module ClassMethods
|
17
|
+
|
18
|
+
include Hooking
|
19
|
+
|
20
|
+
# Holds the hooks defined for instance methods
|
21
|
+
SINGLETON_METHOD_HOOKS = {}
|
22
|
+
|
23
|
+
# Returns true if hooks given for singleton methods of the class +klass+ exist
|
24
|
+
def is_hooked?(object)
|
25
|
+
SINGLETON_METHOD_HOOKS.has_key?(object)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns a hash containing all hooked methods for class +klass+ as keys, or an empty hash if don't exist
|
29
|
+
def hooked_singleton_methods_for(object)
|
30
|
+
SINGLETON_METHOD_HOOKS[object] || {}
|
31
|
+
end
|
32
|
+
|
33
|
+
# Adds +hooks+ hooks to the given +object+ singleton methods +hooked_methods+
|
34
|
+
# This parameter can be either a symbol or an array of symbols.
|
35
|
+
#
|
36
|
+
# Possible thrown exceptions:
|
37
|
+
# UndefinedMethodException:: When one of the symbols in +hooked_methods+ param is not a defined method for object +object+
|
38
|
+
# AlreadyDefinedHookException:: When a hook for this object and specified instance method exists.
|
39
|
+
# Note that this error *will not* be raised if an instance method hook exists for this
|
40
|
+
# method in the object's class. It will override it.
|
41
|
+
# InvalidHooksException:: When hooks contains keys other than :before and :after calls
|
42
|
+
def hook_object!(object, hooked_methods, hooks)
|
43
|
+
hooked_methods = [hooked_methods] if hooked_methods.is_a? Symbol
|
44
|
+
|
45
|
+
validate_hooks(hooks) # Validate hooks
|
46
|
+
|
47
|
+
hooked_methods.each do |hooked_method| # Validate methods defined
|
48
|
+
validate_has_method_defined(object.metaclass, hooked_method)
|
49
|
+
end
|
50
|
+
|
51
|
+
hooked_methods.each do |hooked_method|
|
52
|
+
SINGLETON_METHOD_HOOKS[object] ||= {}
|
53
|
+
hooked_method_name = "#{SINGLETON_HOOK_METHOD_PREFIX}#{hooked_method}"
|
54
|
+
|
55
|
+
if SINGLETON_METHOD_HOOKS[object].has_key?(hooked_method)
|
56
|
+
raise AlreadyDefinedHookException.new("Hook signature present for #{hooked_method} in #{object.metaclass}")
|
57
|
+
end
|
58
|
+
if object.metaclass.method_defined?(hooked_method_name)
|
59
|
+
raise AlreadyDefinedHookException.new("Method #{hooked_method_name} hook already defined for #{hooked_method} in #{where_to_eval}")
|
60
|
+
end
|
61
|
+
|
62
|
+
SINGLETON_METHOD_HOOKS[object][hooked_method] = {}
|
63
|
+
|
64
|
+
hooks.each do |where, callback|
|
65
|
+
SINGLETON_METHOD_HOOKS[object][hooked_method][where] = callback
|
66
|
+
end
|
67
|
+
|
68
|
+
hook_key = 'self'
|
69
|
+
before_hook_call = <<-BEFORE_HOOK_CALL
|
70
|
+
|
71
|
+
before_hook_callback = ((Peeping::Peep.hooked_singleton_methods_for(#{hook_key})||{})[:"#{hooked_method}"] || {})[:before]
|
72
|
+
instance_before_hook_callback = ((Peeping::Peep.hooked_instance_methods_for(self.class) || {})[:"#{hooked_method}"] || {})[:before]
|
73
|
+
|
74
|
+
before_hook_callback ||= instance_before_hook_callback
|
75
|
+
before_hook_callback.call(self, *args) if before_hook_callback
|
76
|
+
BEFORE_HOOK_CALL
|
77
|
+
|
78
|
+
after_hook_call = <<-AFTER_HOOK_CALL
|
79
|
+
after_hook_callback = ((Peeping::Peep.hooked_singleton_methods_for(#{hook_key})||{})[:"#{hooked_method}"] || {})[:after]
|
80
|
+
instance_after_hook_callback = ((Peeping::Peep.hooked_instance_methods_for(self.class) || {})[:"#{hooked_method}"] || {})[:after]
|
81
|
+
|
82
|
+
after_hook_callback ||= instance_after_hook_callback
|
83
|
+
after_hook_callback.call(self, proxied_result) if after_hook_callback
|
84
|
+
AFTER_HOOK_CALL
|
85
|
+
|
86
|
+
should_override_instance_call = true # TODO: Optionalize
|
87
|
+
class_eval_call = Proc.new do
|
88
|
+
eval <<-REDEF
|
89
|
+
if #{should_override_instance_call} and method_defined?(:"#{INSTANCE_HOOK_METHOD_PREFIX}#{hooked_method}")
|
90
|
+
alias :"#{hooked_method_name}" :"#{INSTANCE_HOOK_METHOD_PREFIX}#{hooked_method}"
|
91
|
+
else
|
92
|
+
alias :"#{hooked_method_name}" :"#{hooked_method}"
|
93
|
+
end
|
94
|
+
def #{hooked_method}(*args, &block)
|
95
|
+
#{before_hook_call}
|
96
|
+
proxied_result = if block_given?
|
97
|
+
__send__("#{hooked_method_name}", *args, &block)
|
98
|
+
else
|
99
|
+
__send__("#{hooked_method_name}", *args)
|
100
|
+
end
|
101
|
+
#{after_hook_call}
|
102
|
+
proxied_result
|
103
|
+
end
|
104
|
+
REDEF
|
105
|
+
end
|
106
|
+
object.metaclass.class_eval(&class_eval_call)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# Removes hook singleton methods as well as returns the hooked methods to their original definition
|
111
|
+
def unhook_object!(object, methods = :all, hooks = :all)
|
112
|
+
methods = (methods == :all) ? SINGLETON_METHOD_HOOKS[object].keys : (methods.is_a?(Symbol) ? [methods] : methods)
|
113
|
+
raise ArgumentError("Valid arguments: :before, :after or :all") unless [:before, :after, :all].include?(hooks)
|
114
|
+
|
115
|
+
# Validate all methods exist before doing anything
|
116
|
+
methods.each do |method|
|
117
|
+
raise UndefinedHookException.new("No hook defined for class method #{method})") unless SINGLETON_METHOD_HOOKS[object][method]
|
118
|
+
end
|
119
|
+
|
120
|
+
methods.each do |method|
|
121
|
+
if hooks == :all
|
122
|
+
object.metaclass.class_eval <<-REDEF_OLD_METHOD
|
123
|
+
alias :"#{method}" :"#{SINGLETON_HOOK_METHOD_PREFIX}#{method}"
|
124
|
+
undef :"#{SINGLETON_HOOK_METHOD_PREFIX}#{method}"
|
125
|
+
REDEF_OLD_METHOD
|
126
|
+
SINGLETON_METHOD_HOOKS[object].delete(method)
|
127
|
+
else
|
128
|
+
unless SINGLETON_METHOD_HOOKS[object][method][hooks]
|
129
|
+
raise UndefinedHookException.new("No hook defined for singleton method #{method}) at #{hooks.inspect}")
|
130
|
+
end
|
131
|
+
SINGLETON_METHOD_HOOKS[object][method].delete(hooks)
|
132
|
+
SINGLETON_METHOD_HOOKS[object].delete(method) if SINGLETON_METHOD_HOOKS[object][method].empty?
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
SINGLETON_METHOD_HOOKS.delete(object) if methods == :all or (SINGLETON_METHOD_HOOKS[object] || {}).empty?
|
137
|
+
end
|
138
|
+
|
139
|
+
def clear_all_singleton_hooks!
|
140
|
+
SINGLETON_METHOD_HOOKS.each do |object, hooked_methods|
|
141
|
+
hooked_methods.each do |hooked_method, callbacks|
|
142
|
+
redefined_singleton_method = "#{SINGLETON_HOOK_METHOD_PREFIX}#{hooked_method}"
|
143
|
+
redefined_instance_method = "#{INSTANCE_HOOK_METHOD_PREFIX}#{hooked_method}"
|
144
|
+
object.metaclass.class_eval <<-UNDEF_EVAL
|
145
|
+
if method_defined?(:"#{redefined_singleton_method}")
|
146
|
+
if method_defined?(:"#{redefined_instance_method}")
|
147
|
+
alias :"#{hooked_method}" :"#{redefined_instance_method}"
|
148
|
+
else
|
149
|
+
alias :"#{hooked_method}" :"#{redefined_singleton_method}"
|
150
|
+
end
|
151
|
+
undef :"#{redefined_singleton_method}"
|
152
|
+
end
|
153
|
+
UNDEF_EVAL
|
154
|
+
end
|
155
|
+
end
|
156
|
+
SINGLETON_METHOD_HOOKS.clear
|
157
|
+
end
|
158
|
+
|
159
|
+
end
|
160
|
+
|
161
|
+
module InstanceMethods
|
162
|
+
end
|
163
|
+
|
164
|
+
end
|
165
|
+
end
|
data/lib/peeping/peeping.rb
CHANGED
@@ -9,6 +9,10 @@ require 'peeping/util'
|
|
9
9
|
require 'peeping/hook'
|
10
10
|
require 'peeping/exceptions'
|
11
11
|
|
12
|
+
require 'peeping/hooks/class_hooks'
|
13
|
+
require 'peeping/hooks/instance_hooks'
|
14
|
+
require 'peeping/hooks/singleton_hooks'
|
15
|
+
|
12
16
|
# Wraps classes and modules for the +peeping+ library
|
13
17
|
module Peeping
|
14
18
|
|
@@ -16,299 +20,22 @@ module Peeping
|
|
16
20
|
# hold the hook definitions
|
17
21
|
class Peep
|
18
22
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
# Holds the hooks declared for class methods
|
24
|
-
CLASS_METHOD_HOOKS = {}
|
25
|
-
|
26
|
-
# Holds the hooks defined for instance methods
|
27
|
-
INSTANCE_METHOD_HOOKS = {}
|
28
|
-
|
29
|
-
# Holds the hooks defined for singleton methods
|
30
|
-
SINGLETON_METHOD_HOOKS = {}
|
31
|
-
|
32
|
-
#--
|
33
|
-
## ===================================================
|
34
|
-
## =============== Class hooks section ===============
|
35
|
-
## ===================================================
|
36
|
-
#++
|
37
|
-
|
38
|
-
# Returns true if hooks given for class methods of the class +klass+ exist
|
39
|
-
def self.is_class_hooked?(klass)
|
40
|
-
CLASS_METHOD_HOOKS.has_key?(klass)
|
41
|
-
end
|
42
|
-
|
43
|
-
# Returns a hash containing all hooked methods for class +klass+ as keys, or an empty hash if don't exist
|
44
|
-
def self.hooked_class_methods_for(klass)
|
45
|
-
CLASS_METHOD_HOOKS[klass] || {}
|
46
|
-
end
|
47
|
-
|
48
|
-
# Adds +hooks+ hooks to the given +klass+ class methods +hooked_methods+
|
49
|
-
# This parameter can be either a symbol or an array of symbols.
|
50
|
-
#
|
51
|
-
# Possible thrown exceptions:
|
52
|
-
# NotAClassException:: When the specified parameter +klass+ is not a class
|
53
|
-
# UndefinedMethodException:: When one of the symbols in +hooked_methods+ param is effectively not a defined class method
|
54
|
-
# AlreadyDefinedHookException:: When a hook for this class and specified method exists
|
55
|
-
# InvalidHooksException:: When hooks contains keys other than :before and :after calls
|
56
|
-
def self.hook_class!(klass, hooked_methods, hooks)
|
57
|
-
define_method_hooks!(klass, hooked_methods, hooks, :class, CLASS_METHOD_HOOKS)
|
58
|
-
end
|
59
|
-
|
60
|
-
# Removes hook class methods as well as returns the hooked methods to their original definition
|
61
|
-
def self.unhook_class!(klass)
|
62
|
-
klass.metaclass.class_eval do
|
63
|
-
instance_methods.grep(/^__proxied_class_method_/).each do |proxied_method|
|
64
|
-
proxied_method =~ (/^__proxied_class_method_(.*)$/)
|
65
|
-
original = $1
|
66
|
-
eval "alias #{original} #{proxied_method}"
|
67
|
-
eval "undef #{proxied_method}"
|
68
|
-
end
|
69
|
-
end
|
70
|
-
CLASS_METHOD_HOOKS.delete(klass)
|
71
|
-
end
|
23
|
+
include ClassMethodHooks
|
24
|
+
include InstanceMethodHooks
|
25
|
+
include SingletonMethodHooks
|
72
26
|
|
73
27
|
#--
|
74
|
-
|
75
|
-
## =============== Instance hooks section ===============
|
76
|
-
## ======================================================
|
28
|
+
#===== GLOBAL FUNCTIONS =====
|
77
29
|
#++
|
78
30
|
|
79
|
-
# Returns true if hooks given for instance methods of the class +klass+ exist
|
80
|
-
def self.are_instances_hooked?(klass)
|
81
|
-
INSTANCE_METHOD_HOOKS.has_key?(klass)
|
82
|
-
end
|
83
|
-
|
84
|
-
# Returns a hash containing all hooked methods for class +klass+ as keys, or an empty hash if don't exist
|
85
|
-
def self.hooked_instance_methods_for(klass)
|
86
|
-
INSTANCE_METHOD_HOOKS[klass] || {}
|
87
|
-
end
|
88
|
-
|
89
|
-
# Adds +hooks+ hooks to the given +klass+ instance methods +hooked_methods+
|
90
|
-
# This parameter can be either a symbol or an array of symbols.
|
91
|
-
#
|
92
|
-
# Possible thrown exceptions:
|
93
|
-
# NotAClassException:: When the specified parameter +klass+ is not a class
|
94
|
-
# UndefinedMethodException:: When one of the symbols in +hooked_methods+ param is effectively not a defined instance method
|
95
|
-
# AlreadyDefinedHookException:: When a hook for this class and specified instance method exists
|
96
|
-
# InvalidHooksException:: When hooks contains keys other than :before and :after calls
|
97
|
-
def self.hook_instances!(klass, hooked_methods, hooks)
|
98
|
-
define_method_hooks!(klass, hooked_methods, hooks, :instance, INSTANCE_METHOD_HOOKS)
|
99
|
-
end
|
100
|
-
|
101
|
-
# Removes hook instance methods as well as returns the hooked methods to their original definition
|
102
|
-
def self.unhook_instances!(klass)
|
103
|
-
klass.class_eval do
|
104
|
-
instance_methods.grep(/^__proxied_instance_method_/).each do |proxied_method|
|
105
|
-
proxied_method =~ (/^__proxied_instance_method_(.*)$/)
|
106
|
-
original = $1
|
107
|
-
eval "alias #{original} #{proxied_method}"
|
108
|
-
eval "undef #{proxied_method}"
|
109
|
-
end
|
110
|
-
end
|
111
|
-
INSTANCE_METHOD_HOOKS.delete(klass)
|
112
|
-
end
|
113
|
-
|
114
|
-
#--
|
115
|
-
## =======================================================
|
116
|
-
## =============== Singleton hooks section ===============
|
117
|
-
## =======================================================
|
118
|
-
#++
|
119
|
-
|
120
|
-
# Returns true if hooks given for singleton methods of the class +klass+ exist
|
121
|
-
def self.is_hooked?(object)
|
122
|
-
SINGLETON_METHOD_HOOKS.has_key?(object)
|
123
|
-
end
|
124
|
-
|
125
|
-
# Returns a hash containing all hooked methods for class +klass+ as keys, or an empty hash if don't exist
|
126
|
-
def self.hooked_singleton_methods_for(object)
|
127
|
-
SINGLETON_METHOD_HOOKS[object] || {}
|
128
|
-
end
|
129
|
-
|
130
|
-
# Adds +hooks+ hooks to the given +object+ singleton methods +hooked_methods+
|
131
|
-
# This parameter can be either a symbol or an array of symbols.
|
132
|
-
#
|
133
|
-
# Possible thrown exceptions:
|
134
|
-
# UndefinedMethodException:: When one of the symbols in +hooked_methods+ param is not a defined method for object +object+
|
135
|
-
# AlreadyDefinedHookException:: When a hook for this object and specified instance method exists.
|
136
|
-
# Note that this error *will not* be raised if an instance method hook exists for this
|
137
|
-
# method in the object's class. It will override it.
|
138
|
-
# InvalidHooksException:: When hooks contains keys other than :before and :after calls
|
139
|
-
def self.hook_object!(object, hooked_methods, hooks)
|
140
|
-
define_method_hooks!(object, hooked_methods, hooks, :singleton, SINGLETON_METHOD_HOOKS)
|
141
|
-
end
|
142
|
-
|
143
|
-
# Removes hook singleton methods as well as returns the hooked methods to their original definition
|
144
|
-
def self.unhook_object!(object)
|
145
|
-
object.metaclass.class_eval do
|
146
|
-
instance_methods.grep(/^__proxied_singleton_method_/).each do |proxied_method|
|
147
|
-
proxied_method =~ (/^__proxied_singleton_method_(.*)$/)
|
148
|
-
original = $1
|
149
|
-
eval "alias #{original} #{proxied_method}"
|
150
|
-
eval "undef #{proxied_method}"
|
151
|
-
end
|
152
|
-
end
|
153
|
-
SINGLETON_METHOD_HOOKS.delete(object)
|
154
|
-
end
|
155
|
-
|
156
|
-
#--
|
157
|
-
## ========== Protected auxiliar methods section ==========
|
158
|
-
class << self
|
159
|
-
protected
|
160
|
-
|
161
|
-
def define_method_hooks!(what, hooked_methods, hooks, mode, container)
|
162
|
-
hooked_methods = [hooked_methods] if hooked_methods.is_a? Symbol
|
163
|
-
|
164
|
-
validate_hooks(hooks) # Validate hooks
|
165
|
-
validate_is_class(what, mode) # Validate class
|
166
|
-
|
167
|
-
where_to_eval = (mode == :class or mode == :singleton) ? what.metaclass : what
|
168
|
-
hooked_methods.each do |hooked_method| # Validate methods defined
|
169
|
-
validate_has_method_defined(where_to_eval, hooked_method, mode)
|
170
|
-
end
|
171
|
-
|
172
|
-
hooked_methods.each do |hooked_method|
|
173
|
-
container[what] ||= {}
|
174
|
-
hooked_method_name = "__proxied_#{mode}_method_#{hooked_method}"
|
175
|
-
|
176
|
-
if container[what].has_key?(hooked_method)
|
177
|
-
raise AlreadyDefinedHookException.new("Hook signature present for #{hooked_method} in #{where_to_eval}")
|
178
|
-
end
|
179
|
-
if what.respond_to?(hooked_method_name)
|
180
|
-
raise AlreadyDefinedHookException.new("Method #{hooked_method_name} hook already defined for #{hooked_method} in #{where_to_eval}")
|
181
|
-
end
|
182
|
-
|
183
|
-
container[what][hooked_method] = {}
|
184
|
-
|
185
|
-
hooks.each do |where, callback|
|
186
|
-
container[what][hooked_method][where] = callback
|
187
|
-
end
|
188
|
-
|
189
|
-
hook_key = (mode == :class or mode == :instance) ? what.name : 'self'
|
190
|
-
before_hook_call = if hooks.include?(:before)
|
191
|
-
"Peeping::Peep.hooked_#{mode}_methods_for(#{hook_key})[:\"#{hooked_method}\"][:before].call(self, *args)"
|
192
|
-
end
|
193
|
-
after_hook_call = if hooks.include?(:after)
|
194
|
-
"Peeping::Peep.hooked_#{mode}_methods_for(#{hook_key})[:\"#{hooked_method}\"][:after].call(self, proxied_result)"
|
195
|
-
end
|
196
|
-
should_override_instance_call = (mode == :singleton).to_s
|
197
|
-
class_eval_call = Proc.new do
|
198
|
-
eval <<-REDEF
|
199
|
-
if #{should_override_instance_call} and method_defined?(:"__proxied_instance_method_#{hooked_method}")
|
200
|
-
alias :"#{hooked_method_name}" :"__proxied_instance_method_#{hooked_method}"
|
201
|
-
else
|
202
|
-
alias :"#{hooked_method_name}" :"#{hooked_method}"
|
203
|
-
end
|
204
|
-
def #{hooked_method}(*args, &block)
|
205
|
-
#{before_hook_call}
|
206
|
-
proxied_result = if block_given?
|
207
|
-
__send__("#{hooked_method_name}", *args, &block)
|
208
|
-
else
|
209
|
-
__send__("#{hooked_method_name}", *args)
|
210
|
-
end
|
211
|
-
#{after_hook_call}
|
212
|
-
proxied_result
|
213
|
-
end
|
214
|
-
REDEF
|
215
|
-
end
|
216
|
-
where_to_eval.class_eval(&class_eval_call)
|
217
|
-
end
|
218
|
-
end
|
219
|
-
|
220
|
-
# Validates that the array consists only in the keys :before or/and :after
|
221
|
-
#--
|
222
|
-
# TODO: Add extensibility for possible keys. Ideas: warn_on_override, :dont_override, :execute_in_place
|
223
|
-
#++
|
224
|
-
def validate_hooks(hooks)
|
225
|
-
raise InvalidHooksException.new("At least an :after or a :before hook are expected") if hooks.empty?
|
226
|
-
unknown_keys = hooks.keys - [:before, :after]
|
227
|
-
raise InvalidHooksException.new("Unknown keys #{unknown_keys.join(', ')}") unless unknown_keys.empty?
|
228
|
-
end
|
229
|
-
|
230
|
-
# Validates that the passed object is a class in :class and :instance modes
|
231
|
-
def validate_is_class(what, mode)
|
232
|
-
if mode == :class or mode == :instance
|
233
|
-
raise NotAClassException.new("#{what} is not a Class") unless what.is_a? Class
|
234
|
-
end
|
235
|
-
end
|
236
|
-
|
237
|
-
# Validates that the the method that will be hooked for the given object is defined.
|
238
|
-
def validate_has_method_defined(holding_class, method, mode)
|
239
|
-
unless holding_class.method_defined?(method)
|
240
|
-
raise UndefinedMethodException.new("Undefined #{mode} method #{method.inspect} for #{holding_class}")
|
241
|
-
end
|
242
|
-
end
|
243
|
-
end
|
244
|
-
#++
|
245
|
-
|
246
|
-
end
|
247
|
-
|
248
|
-
## ========== Auxiliar methods ==========
|
249
|
-
|
250
|
-
class Peep
|
251
31
|
# Completely removes all hooks for all defined classes and objects
|
252
32
|
def self.clear_all_hooks!
|
253
|
-
clear_all_class_hooks!
|
254
33
|
clear_all_singleton_hooks!
|
255
34
|
clear_all_instance_hooks!
|
35
|
+
clear_all_class_hooks!
|
256
36
|
end
|
257
37
|
|
258
|
-
|
259
|
-
class << self
|
260
|
-
protected
|
261
|
-
|
262
|
-
# TODO: Refactor lots of this
|
263
|
-
|
264
|
-
def clear_all_class_hooks!
|
265
|
-
Peep::CLASS_METHOD_HOOKS.each do |klass, hooked_methods|
|
266
|
-
hooked_methods.each do |hooked_method, callbacks|
|
267
|
-
klass.metaclass.class_eval <<-UNDEF_EVAL
|
268
|
-
if method_defined?(:"__proxied_class_method_#{hooked_method}")
|
269
|
-
alias :"#{hooked_method}" :"__proxied_class_method_#{hooked_method}"
|
270
|
-
undef :"__proxied_class_method_#{hooked_method}"
|
271
|
-
end
|
272
|
-
UNDEF_EVAL
|
273
|
-
end
|
274
|
-
end
|
275
|
-
Peep::CLASS_METHOD_HOOKS.clear
|
276
|
-
end
|
277
|
-
|
278
|
-
def clear_all_instance_hooks!
|
279
|
-
Peep::INSTANCE_METHOD_HOOKS.each do |klass, hooked_methods|
|
280
|
-
hooked_methods.each do |hooked_method, callbacks|
|
281
|
-
klass.class_eval <<-UNDEF_EVAL
|
282
|
-
if method_defined?(:"__proxied_instance_method_#{hooked_method}")
|
283
|
-
alias :"#{hooked_method}" :"__proxied_instance_method_#{hooked_method}"
|
284
|
-
undef :"__proxied_instance_method_#{hooked_method}"
|
285
|
-
end
|
286
|
-
UNDEF_EVAL
|
287
|
-
end
|
288
|
-
end
|
289
|
-
Peep::INSTANCE_METHOD_HOOKS.clear
|
290
|
-
end
|
291
|
-
|
292
|
-
def clear_all_singleton_hooks!
|
293
|
-
Peep::SINGLETON_METHOD_HOOKS.each do |object, hooked_methods|
|
294
|
-
hooked_methods.each do |hooked_method, callbacks|
|
295
|
-
object.metaclass.class_eval <<-UNDEF_EVAL
|
296
|
-
if method_defined?(:"__proxied_singleton_method_#{hooked_method}")
|
297
|
-
if method_defined?(:"__proxied_instance_method_#{hooked_method}")
|
298
|
-
alias :"#{hooked_method}" :"__proxied_instance_method_#{hooked_method}"
|
299
|
-
else
|
300
|
-
alias :"#{hooked_method}" :"__proxied_singleton_method_#{hooked_method}"
|
301
|
-
end
|
302
|
-
undef :"__proxied_singleton_method_#{hooked_method}"
|
303
|
-
end
|
304
|
-
UNDEF_EVAL
|
305
|
-
end
|
306
|
-
end
|
307
|
-
Peep::SINGLETON_METHOD_HOOKS.clear
|
308
|
-
end
|
309
|
-
end
|
310
|
-
#++
|
38
|
+
end
|
311
39
|
|
312
|
-
end
|
313
40
|
end
|
314
41
|
|
data/lib/peeping.rb
CHANGED
data/peeping.gemspec
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{peeping}
|
5
|
+
s.version = "1.1.0"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["Ruben Medellin"]
|
9
|
+
s.date = %q{2009-07-15}
|
10
|
+
s.description = %q{ Add, remove and manage hooks for class, instance and singleton method calls.
|
11
|
+
Intended to be not a full Aspect Oriented Programming framework, but a lightweight
|
12
|
+
simpler one.
|
13
|
+
}
|
14
|
+
s.email = %q{ruben.medellin.c@gmail.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE",
|
17
|
+
"README.markdown"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".document",
|
21
|
+
".gitignore",
|
22
|
+
"LICENSE",
|
23
|
+
"README.markdown",
|
24
|
+
"Rakefile",
|
25
|
+
"VERSION",
|
26
|
+
"lib/peeping.rb",
|
27
|
+
"lib/peeping/exceptions.rb",
|
28
|
+
"lib/peeping/hooks/class_hooks.rb",
|
29
|
+
"lib/peeping/hooks/hooking.rb",
|
30
|
+
"lib/peeping/hooks/instance_hooks.rb",
|
31
|
+
"lib/peeping/hooks/singleton_hooks.rb",
|
32
|
+
"lib/peeping/peeping.rb",
|
33
|
+
"lib/peeping/util.rb",
|
34
|
+
"peeping.gemspec",
|
35
|
+
"spec/helpers/test_helpers.rb",
|
36
|
+
"spec/hook_methods_spec.rb",
|
37
|
+
"spec/hooked_behavior_spec.rb",
|
38
|
+
"spec/selective_hook_management_spec.rb"
|
39
|
+
]
|
40
|
+
s.has_rdoc = true
|
41
|
+
s.homepage = %q{http://github.com/chubas/peeping}
|
42
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
43
|
+
s.require_paths = ["lib"]
|
44
|
+
s.requirements = ["none"]
|
45
|
+
s.rubygems_version = %q{1.3.2}
|
46
|
+
s.summary = %q{Lightweight AOP framework for managing method hooks}
|
47
|
+
s.test_files = [
|
48
|
+
"spec/hooked_behavior_spec.rb",
|
49
|
+
"spec/hook_methods_spec.rb",
|
50
|
+
"spec/selective_hook_management_spec.rb"
|
51
|
+
]
|
52
|
+
|
53
|
+
if s.respond_to? :specification_version then
|
54
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
55
|
+
s.specification_version = 3
|
56
|
+
|
57
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
58
|
+
else
|
59
|
+
end
|
60
|
+
else
|
61
|
+
end
|
62
|
+
end
|
@@ -79,16 +79,17 @@ describe Peep, "when calling hook methods" do
|
|
79
79
|
|
80
80
|
it "should always override instance method calls with singleton method calls" do
|
81
81
|
scooby = Dog.new('scooby')
|
82
|
+
lassie = Dog.new('lassie')
|
83
|
+
|
82
84
|
Peep.hook_object!(
|
83
85
|
scooby,
|
84
86
|
:lick_owner,
|
85
|
-
:before => Proc.new{|object, *args|
|
86
|
-
:after => Proc.new{|object, result|
|
87
|
-
lassie = Dog.new('lassie')
|
87
|
+
:before => Proc.new{|object, *args| object.owner = "Shaggy" },
|
88
|
+
:after => Proc.new{|object, result| result.should == "Shaggy is now all wet!" } )
|
88
89
|
Peep.hook_instances!(
|
89
90
|
Dog,
|
90
91
|
:lick_owner,
|
91
|
-
:after
|
92
|
+
:after => Proc.new{|objetc, result| result.should == "I have no owner to lick :(" } )
|
92
93
|
scooby.lick_owner
|
93
94
|
lassie.lick_owner
|
94
95
|
end
|
@@ -0,0 +1,190 @@
|
|
1
|
+
require 'spec'
|
2
|
+
require File.join(File.dirname(__FILE__), '..', 'lib', 'peeping')
|
3
|
+
require File.join(File.dirname(__FILE__), 'helpers', 'test_helpers')
|
4
|
+
|
5
|
+
include Peeping
|
6
|
+
|
7
|
+
describe Peep, "when adding and removing hooks" do
|
8
|
+
|
9
|
+
it_should_behave_like "a clean test"
|
10
|
+
|
11
|
+
it "should allow to remove class hook methods by method and location" do
|
12
|
+
before = Proc.new{}
|
13
|
+
after = Proc.new{}
|
14
|
+
|
15
|
+
Peep.hook_class!(Dog, [:train, :eats?], :before => before, :after => after)
|
16
|
+
|
17
|
+
hooked_class_methods = Peep.hooked_class_methods_for(Dog)
|
18
|
+
hooked_class_methods.should have(2).keys
|
19
|
+
hooked_class_methods.should include(:train)
|
20
|
+
hooked_class_methods.should include(:eats?)
|
21
|
+
|
22
|
+
hooked_class_methods[:train].should have(2).keys
|
23
|
+
hooked_class_methods[:train].should include(:before)
|
24
|
+
hooked_class_methods[:train].should include(:after)
|
25
|
+
|
26
|
+
Peep.unhook_class!(Dog, :train, :before)
|
27
|
+
|
28
|
+
hooked_class_methods = Peep.hooked_class_methods_for(Dog)
|
29
|
+
hooked_class_methods.should have(2).keys
|
30
|
+
hooked_class_methods.keys.should include(:train)
|
31
|
+
hooked_class_methods.keys.should include(:eats?)
|
32
|
+
|
33
|
+
hooked_class_methods[:train].should have(1).keys
|
34
|
+
hooked_class_methods[:train].should_not include(:before)
|
35
|
+
hooked_class_methods[:train].should include(:after)
|
36
|
+
|
37
|
+
Proc.new{ Peep.unhook_class! Dog, :no_such_hook }.should raise_error UndefinedHookException
|
38
|
+
Proc.new{ Peep.unhook_class! Dog, :train, :before }.should raise_error UndefinedHookException
|
39
|
+
|
40
|
+
Peep.unhook_class!(Dog, :train, :after)
|
41
|
+
|
42
|
+
hooked_class_methods = Peep.hooked_class_methods_for(Dog)
|
43
|
+
hooked_class_methods.should have(1).keys
|
44
|
+
hooked_class_methods.keys.should_not include(:train)
|
45
|
+
hooked_class_methods.keys.should include(:eats?)
|
46
|
+
|
47
|
+
Peep.unhook_class!(Dog)
|
48
|
+
|
49
|
+
hooked_class_methods.should have(0).keys
|
50
|
+
Peep.is_class_hooked?(Dog).should == false
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
it "should allow to remove instance hook methods by method and location" do
|
55
|
+
before = Proc.new{}
|
56
|
+
after = Proc.new{}
|
57
|
+
|
58
|
+
Peep.hook_instances!(Dog, [:bark, :lick_owner], :before => before, :after => after)
|
59
|
+
|
60
|
+
hooked_instance_methods = Peep.hooked_instance_methods_for(Dog)
|
61
|
+
hooked_instance_methods.should have(2).keys
|
62
|
+
hooked_instance_methods.should include(:bark)
|
63
|
+
hooked_instance_methods.should include(:lick_owner)
|
64
|
+
|
65
|
+
hooked_instance_methods[:bark].should have(2).keys
|
66
|
+
hooked_instance_methods[:bark].should include(:before)
|
67
|
+
hooked_instance_methods[:bark].should include(:after)
|
68
|
+
|
69
|
+
Peep.unhook_instances!(Dog, :bark, :before)
|
70
|
+
|
71
|
+
hooked_instance_methods = Peep.hooked_instance_methods_for(Dog)
|
72
|
+
hooked_instance_methods.should have(2).keys
|
73
|
+
hooked_instance_methods.keys.should include(:bark)
|
74
|
+
hooked_instance_methods.keys.should include(:lick_owner)
|
75
|
+
|
76
|
+
hooked_instance_methods[:bark].should have(1).keys
|
77
|
+
hooked_instance_methods[:bark].should_not include(:before)
|
78
|
+
hooked_instance_methods[:bark].should include(:after)
|
79
|
+
|
80
|
+
Proc.new{ Peep.unhook_instances! Dog, :no_such_hook }.should raise_error UndefinedHookException
|
81
|
+
Proc.new{ Peep.unhook_instances! Dog, :bark, :before }.should raise_error UndefinedHookException
|
82
|
+
|
83
|
+
Peep.unhook_instances!(Dog, :bark, :after)
|
84
|
+
|
85
|
+
hooked_instance_methods = Peep.hooked_instance_methods_for(Dog)
|
86
|
+
hooked_instance_methods.should have(1).keys
|
87
|
+
hooked_instance_methods.keys.should_not include(:bark)
|
88
|
+
hooked_instance_methods.keys.should include(:lick_owner)
|
89
|
+
|
90
|
+
Peep.unhook_instances!(Dog)
|
91
|
+
|
92
|
+
hooked_instance_methods.should have(0).keys
|
93
|
+
end
|
94
|
+
|
95
|
+
it "should allow to remove singleton hook methods by method and location" do
|
96
|
+
before = Proc.new{}
|
97
|
+
after = Proc.new{}
|
98
|
+
scooby = Dog.new('scooby')
|
99
|
+
|
100
|
+
Peep.hook_object!(scooby, [:bark, :lick_owner], :before => before, :after => after)
|
101
|
+
|
102
|
+
hooked_singleton_methods = Peep.hooked_singleton_methods_for(scooby)
|
103
|
+
hooked_singleton_methods.should have(2).keys
|
104
|
+
hooked_singleton_methods.should include(:bark)
|
105
|
+
hooked_singleton_methods.should include(:lick_owner)
|
106
|
+
|
107
|
+
hooked_singleton_methods[:bark].should have(2).keys
|
108
|
+
hooked_singleton_methods[:bark].should include(:before)
|
109
|
+
hooked_singleton_methods[:bark].should include(:after)
|
110
|
+
|
111
|
+
Peep.unhook_object!(scooby, :bark, :before)
|
112
|
+
|
113
|
+
hooked_singleton_methods = Peep.hooked_singleton_methods_for(scooby)
|
114
|
+
hooked_singleton_methods.should have(2).keys
|
115
|
+
hooked_singleton_methods.keys.should include(:bark)
|
116
|
+
hooked_singleton_methods.keys.should include(:lick_owner)
|
117
|
+
|
118
|
+
hooked_singleton_methods[:bark].should have(1).keys
|
119
|
+
hooked_singleton_methods[:bark].should_not include(:before)
|
120
|
+
hooked_singleton_methods[:bark].should include(:after)
|
121
|
+
|
122
|
+
Proc.new{ Peep.unhook_object! scooby, :no_such_hook }.should raise_error UndefinedHookException
|
123
|
+
Proc.new{ Peep.unhook_object! scooby, :bark, :before }.should raise_error UndefinedHookException
|
124
|
+
|
125
|
+
Peep.unhook_object!(scooby, :bark, :after)
|
126
|
+
|
127
|
+
hooked_singleton_methods = Peep.hooked_singleton_methods_for(scooby)
|
128
|
+
hooked_singleton_methods.should have(1).keys
|
129
|
+
hooked_singleton_methods.keys.should_not include(:bark)
|
130
|
+
hooked_singleton_methods.keys.should include(:lick_owner)
|
131
|
+
|
132
|
+
Peep.unhook_object!(scooby)
|
133
|
+
|
134
|
+
hooked_singleton_methods.should have(0).keys
|
135
|
+
end
|
136
|
+
|
137
|
+
it "should keep the instance hook method when removing any singleton hook method" do
|
138
|
+
scooby = Dog.new('scooby')
|
139
|
+
lassie = Dog.new('lassie')
|
140
|
+
|
141
|
+
before_instance_counter = 0
|
142
|
+
after_instance_counter = 0
|
143
|
+
before_object_counter = 0
|
144
|
+
after_object_counter = 0
|
145
|
+
|
146
|
+
counters_should_be = Proc.new do |bic, aic, boc, aoc|
|
147
|
+
before_instance_counter.should == bic
|
148
|
+
after_instance_counter.should == aic
|
149
|
+
before_object_counter.should == boc
|
150
|
+
after_object_counter.should == aoc
|
151
|
+
end
|
152
|
+
|
153
|
+
Peep.hook_instances!(Dog, :lick_owner,
|
154
|
+
:before => Proc.new{ before_instance_counter += 1},
|
155
|
+
:after => Proc.new{ after_instance_counter += 1} )
|
156
|
+
Peep.hook_object!(scooby, :lick_owner,
|
157
|
+
:before => Proc.new{ before_object_counter += 1},
|
158
|
+
:after => Proc.new{ after_object_counter += 1} )
|
159
|
+
|
160
|
+
scooby.lick_owner
|
161
|
+
counters_should_be[0, 0, 1, 1]
|
162
|
+
|
163
|
+
lassie.lick_owner
|
164
|
+
counters_should_be[1, 1, 1, 1]
|
165
|
+
|
166
|
+
Peep.unhook_object!(scooby, :lick_owner, :before)
|
167
|
+
scooby.lick_owner
|
168
|
+
counters_should_be[2, 1, 1, 2]
|
169
|
+
lassie.lick_owner
|
170
|
+
counters_should_be[3, 2, 1, 2]
|
171
|
+
|
172
|
+
Peep.unhook_instances!(Dog, :lick_owner, :after)
|
173
|
+
scooby.lick_owner
|
174
|
+
counters_should_be[4, 2, 1, 3]
|
175
|
+
lassie.lick_owner
|
176
|
+
counters_should_be[5, 2, 1, 3]
|
177
|
+
|
178
|
+
Peep.unhook_object!(scooby, :lick_owner, :after)
|
179
|
+
scooby.lick_owner
|
180
|
+
counters_should_be[6, 2, 1, 3]
|
181
|
+
lassie.lick_owner
|
182
|
+
counters_should_be[7, 2, 1, 3]
|
183
|
+
|
184
|
+
Peep.unhook_instances!(Dog, :lick_owner)
|
185
|
+
scooby.lick_owner
|
186
|
+
lassie.lick_owner
|
187
|
+
counters_should_be[7, 2, 1, 3]
|
188
|
+
end
|
189
|
+
|
190
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: chubas-peeping
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ruben Medellin
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-07-
|
12
|
+
date: 2009-07-15 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
@@ -31,12 +31,17 @@ files:
|
|
31
31
|
- VERSION
|
32
32
|
- lib/peeping.rb
|
33
33
|
- lib/peeping/exceptions.rb
|
34
|
-
- lib/peeping/
|
34
|
+
- lib/peeping/hooks/class_hooks.rb
|
35
|
+
- lib/peeping/hooks/hooking.rb
|
36
|
+
- lib/peeping/hooks/instance_hooks.rb
|
37
|
+
- lib/peeping/hooks/singleton_hooks.rb
|
35
38
|
- lib/peeping/peeping.rb
|
36
39
|
- lib/peeping/util.rb
|
40
|
+
- peeping.gemspec
|
37
41
|
- spec/helpers/test_helpers.rb
|
38
42
|
- spec/hook_methods_spec.rb
|
39
43
|
- spec/hooked_behavior_spec.rb
|
44
|
+
- spec/selective_hook_management_spec.rb
|
40
45
|
has_rdoc: true
|
41
46
|
homepage: http://github.com/chubas/peeping
|
42
47
|
post_install_message:
|
@@ -66,3 +71,4 @@ summary: Lightweight AOP framework for managing method hooks
|
|
66
71
|
test_files:
|
67
72
|
- spec/hooked_behavior_spec.rb
|
68
73
|
- spec/hook_methods_spec.rb
|
74
|
+
- spec/selective_hook_management_spec.rb
|