chubas-peeping 1.0.0 → 1.1.0
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/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
|