tiny_hooks 1.0.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c5d6e7ef3a23f548af13f1f6497d5ac5eedcf833d8090380b085a9c85bddf37a
4
- data.tar.gz: 6992a9ce9f85531803f35f009817b0b7c0b140088e64b9f38c67f4b0225e7d0d
3
+ metadata.gz: '094f25167ea774a8c2019e04a891a1056a4fcfaa6036ebf04712deb38a4de83f'
4
+ data.tar.gz: 624dc86d41d19ef506b1867e5ccacf197107baeb09f43fb2376814c33f42af50
5
5
  SHA512:
6
- metadata.gz: 9dfd1ec6b94c48b2309bf4f713dccdd7d8ae572b6ea42615ebb891238306c84224b15657f9fe77d1f3f6ae8a7f45b26c6ac347183d7bde7750e0a6d6a0879a38
7
- data.tar.gz: 109d45de44c9309ad233b8a819550306016e6dacb33a7f2a8980de4fddda7562839e6f7463c9c2b0a730544322941d21acfba1b77ea50bb6e7dbc18cdf62dffb
6
+ metadata.gz: e745e65d785c7a7a52e68be47b9a5f0d8b43103adddf39dd152a9d23b4b175f3c7edf7540f2da3ff2d383c3b817d2b34ebf6a7b9671c29abee799add67e1122e
7
+ data.tar.gz: 3268e19a06ee31caab8971785d72a112e8adfb68acd898e8845e887ce1fb84769d668e8b7b235508c0a25af965ecd89405d3089f26470e63250901aba12028e8
data/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [2.0.0] - 2021-07-12
4
+
5
+ - Add ability to specify hook method with name
6
+ - TinyHooks now works on class methods
7
+
3
8
  ## [1.0.0] - 2021-05-09
4
9
 
5
10
  - Support halting
data/README.md CHANGED
@@ -1,3 +1,5 @@
1
+ [![Ruby](https://github.com/okuramasafumi/tiny_hooks/actions/workflows/main.yml/badge.svg)](https://github.com/okuramasafumi/tiny_hooks/actions/workflows/main.yml)
2
+
1
3
  # TinyHooks
2
4
 
3
5
  A tiny gem to define hooks.
@@ -39,6 +41,46 @@ MyClass.new.my_method
39
41
  # => "my before hook\nmy method\n"
40
42
  ```
41
43
 
44
+ You can also call `define_hook` with method name as a third argument.
45
+
46
+ ```ruby
47
+ class MyClass
48
+ include TinyHooks
49
+
50
+ def my_method
51
+ puts 'my method'
52
+ end
53
+
54
+ def my_before_hook
55
+ puts 'my before hook'
56
+ end
57
+
58
+ define_hook :before, :my_method, :my_before_hook
59
+ end
60
+
61
+ MyClass.new.my_method
62
+ # => "my before hook\nmy method\n"
63
+ ```
64
+
65
+ You can define hooks for class methods as well.
66
+
67
+ ```ruby
68
+ class MyClass
69
+ include TinyHooks
70
+
71
+ def self.my_method
72
+ puts 'my method'
73
+ end
74
+
75
+ define_hook :before, :my_method, class_method: true do
76
+ puts 'my before hook'
77
+ end
78
+ end
79
+
80
+ MyClass.my_method
81
+ # => "my before hook\nmy method\n"
82
+ ```
83
+
42
84
  TinyHooks shines when the class/module is the base class/module of your library and your users will inherit/include it. In these cases, end users can define hooks to the methods you provide. The only thing you have to do is to provide the list of methods.
43
85
 
44
86
  ### Halting
data/lib/tiny_hooks.rb CHANGED
@@ -21,6 +21,7 @@ module TinyHooks
21
21
  def self.included(base)
22
22
  base.class_eval do
23
23
  @_originals = {}
24
+ @_class_originals = {}
24
25
  @_targets = UNDEFINED_TARGETS
25
26
  @_public_only = false
26
27
  end
@@ -46,37 +47,70 @@ module TinyHooks
46
47
  #
47
48
  # @param [Symbol, String] kind the kind of the hook, possible values are: :before, :after and :around
48
49
  # @param [Symbol, String] target the name of the targeted method
50
+ # @param hook_method_name [Symbol, String] the name of a method which should be called as a hook
49
51
  # @param [Symbol] terminator choice for terminating execution, default is throwing abort symbol
50
52
  # @param [Symbol] if condition to determine if it should define callback. Block is evaluated in context of self
51
- def define_hook(kind, target, terminator: :abort, if: nil, &block) # rubocop:disable Naming/MethodParameterName
52
- raise ArgumentError, 'You must provide a block' unless block
53
+ # @param class_method [Boolean] treat target as class method
54
+ def define_hook(kind, target, hook_method_name = nil, terminator: :abort, if: nil, class_method: false, &block) # rubocop:disable Naming/MethodParameterName
55
+ raise ArgumentError, 'You must provide a block or hook_method_name' unless block || hook_method_name
53
56
  raise ArgumentError, 'terminator must be one of the following: :abort or :return_false' unless %i[abort return_false].include? terminator.to_sym
54
57
  raise TinyHooks::TargetError, "Hook for #{target} is not allowed" if @_targets != UNDEFINED_TARGETS && !@_targets.include?(target)
55
58
 
56
- is_private = private_instance_methods.include?(target.to_sym)
59
+ if class_method
60
+ is_private = private_methods.include?(target.to_sym)
57
61
 
58
- begin
59
- original_method = @_public_only ? public_instance_method(target) : instance_method(target)
60
- rescue NameError => e
61
- raise unless e.message.include?('private')
62
+ begin
63
+ original_method = @_public_only ? public_method(target) : method(target)
64
+ rescue NameError => e
65
+ raise unless e.message.include?('private')
62
66
 
63
- raise TinyHooks::PrivateError, "Public only mode is on and hooks for private methods (#{target} for this time) are not available."
64
- end
65
- @_originals[target.to_sym] = original_method unless @_originals[target.to_sym]
67
+ raise TinyHooks::PrivateError, "Public only mode is on and hooks for private methods (#{target} for this time) are not available."
68
+ end
69
+ @_class_originals[target.to_sym] = original_method unless @_class_originals[target.to_sym]
70
+
71
+ block ||= -> { __send__(hook_method_name) }
72
+ body = method_body(kind, original_method, terminator, binding.local_variable_get(:if), &block)
73
+ singleton_class.class_eval do
74
+ undef_method(target)
75
+ define_method(target, &body)
76
+ private target if is_private
77
+ end
78
+ else # instance method
79
+ is_private = private_instance_methods.include?(target.to_sym)
80
+
81
+ begin
82
+ original_method = @_public_only ? public_instance_method(target) : instance_method(target)
83
+ rescue NameError => e
84
+ raise unless e.message.include?('private')
85
+
86
+ raise TinyHooks::PrivateError, "Public only mode is on and hooks for private methods (#{target} for this time) are not available."
87
+ end
88
+ @_originals[target.to_sym] = original_method unless @_originals[target.to_sym]
89
+
90
+ block ||= -> { __send__(hook_method_name) }
66
91
 
67
- undef_method(target)
68
- define_method(target, &method_body(kind, original_method, terminator, binding.local_variable_get(:if), &block))
69
- private target if is_private
92
+ undef_method(target)
93
+ define_method(target, &method_body(kind, original_method, terminator, binding.local_variable_get(:if), &block))
94
+ private target if is_private
95
+ end
70
96
  end
71
97
 
72
98
  # Restore original method
73
99
  #
74
100
  # @param [Symbol, String] target
75
- def restore_original(target)
76
- original_method = @_originals[target.to_sym] || instance_method(target)
77
-
78
- undef_method(target)
79
- define_method(target, original_method)
101
+ # @param class_method [Boolean] treat target as class method
102
+ def restore_original(target, class_method: false)
103
+ if class_method
104
+ original_method = @_class_originals[target.to_sym] || method(target)
105
+ singleton_class.class_eval do
106
+ undef_method(target)
107
+ define_method(target, original_method)
108
+ end
109
+ else
110
+ original_method = @_originals[target.to_sym] || instance_method(target)
111
+ undef_method(target)
112
+ define_method(target, original_method)
113
+ end
80
114
  end
81
115
 
82
116
  # Defines target for hooks
@@ -86,6 +120,7 @@ module TinyHooks
86
120
  raise ArgumentError if include_pattern.nil? && exclude_pattern.nil?
87
121
 
88
122
  candidates = @_public_only ? instance_methods : instance_methods + private_instance_methods
123
+ candidates += @public_only ? methods : methods + private_methods
89
124
  @_targets = if include_pattern && exclude_pattern
90
125
  targets = candidates.grep(include_pattern)
91
126
  targets.grep_v(exclude_pattern)
@@ -131,7 +166,7 @@ module TinyHooks
131
166
  return if hook_result == false && terminator == :return_false
132
167
  end
133
168
 
134
- original_method.bind_call(self, *args, **kwargs, &blk)
169
+ original_method.is_a?(UnboundMethod) ? original_method.bind_call(self, *args, **kwargs, &blk) : original_method.call(*args, **kwargs, &blk)
135
170
  end
136
171
  else
137
172
  proc do |*args, &blk|
@@ -145,7 +180,8 @@ module TinyHooks
145
180
  return if hook_result == false && terminator == :return_false
146
181
  end
147
182
 
148
- original_method.bind(self).call(*args, &blk)
183
+ original_method = original_method.bind(self) if original_method.is_a?(UnboundMethod)
184
+ original_method.call(*args, &blk)
149
185
  end
150
186
  end
151
187
  end
@@ -153,12 +189,13 @@ module TinyHooks
153
189
  def _after(original_method, if_proc:, &block)
154
190
  if RUBY_VERSION >= '2.7'
155
191
  proc do |*args, **kwargs, &blk|
156
- original_method.bind_call(self, *args, **kwargs, &blk)
192
+ original_method.is_a?(UnboundMethod) ? original_method.bind_call(self, *args, **kwargs, &blk) : original_method.call(*args, **kwargs, &blk)
157
193
  instance_exec(*args, **kwargs, &block) if if_proc.nil? || instance_exec(&if_proc) != false
158
194
  end
159
195
  else
160
196
  proc do |*args, &blk|
161
- original_method.bind(self).call(*args, &blk)
197
+ original_method = original_method.bind(self) if original_method.is_a?(UnboundMethod)
198
+ original_method.call(*args, &blk)
162
199
  instance_exec(*args, &block) if if_proc.nil? || instance_exec(&if_proc) != false
163
200
  end
164
201
  end
@@ -167,12 +204,17 @@ module TinyHooks
167
204
  def _around(original_method, if_proc:, &block)
168
205
  if RUBY_VERSION >= '2.7'
169
206
  proc do |*args, **kwargs, &blk|
170
- wrapper = -> { original_method.bind_call(self, *args, **kwargs, &blk) }
207
+ wrapper = lambda do
208
+ original_method.is_a?(UnboundMethod) ? original_method.bind_call(self, *args, **kwargs, &blk) : original_method.call(*args, **kwargs, &blk)
209
+ end
171
210
  instance_exec(wrapper, *args, **kwargs, &block) if if_proc.nil? || instance_exec(&if_proc) != false
172
211
  end
173
212
  else
174
213
  proc do |*args, &blk|
175
- wrapper = -> { original_method.bind(self).call(*args, &blk) }
214
+ wrapper = lambda do
215
+ original_method = original_method.bind(self) if original_method.is_a?(UnboundMethod)
216
+ original_method.call(*args, &blk)
217
+ end
176
218
  instance_exec(wrapper, *args, &block) if if_proc.nil? || instance_exec(&if_proc) != false
177
219
  end
178
220
  end
@@ -181,6 +223,7 @@ module TinyHooks
181
223
  def inherited(subclass)
182
224
  super
183
225
  subclass.instance_variable_set(:@_originals, instance_variable_get(:@_originals).clone)
226
+ subclass.instance_variable_set(:@_class_originals, instance_variable_get(:@_class_originals).clone)
184
227
  subclass.instance_variable_set(:@_targets, instance_variable_get(:@_targets).clone)
185
228
  subclass.instance_variable_set(:@_public_only, instance_variable_get(:@_public_only).clone)
186
229
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TinyHooks
4
- VERSION = '1.0.0'
4
+ VERSION = '2.0.0'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tiny_hooks
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - OKURA Masafumi
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-05-09 00:00:00.000000000 Z
11
+ date: 2021-07-12 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Simple, tiny and general hooks control.
14
14
  email: