adornable 1.0.3 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +43 -6
- data/README.md +79 -61
- data/adornable.gemspec +3 -2
- data/lib/adornable.rb +3 -2
- data/lib/adornable/context.rb +23 -0
- data/lib/adornable/decorators.rb +18 -11
- data/lib/adornable/machinery.rb +33 -13
- data/lib/adornable/utils.rb +9 -0
- data/lib/adornable/version.rb +1 -1
- metadata +25 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ece582a2ce4c7093792eacfa04863a2c71a8a5bee12fceb0f964356b58f997c5
|
4
|
+
data.tar.gz: dabd571e4161e633d40d15bdaccee37bf8646ec7f7cdd2d4773feaeb207c5e9d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b0dbae5d19cd42fe225b054bb9245ff9946c417d75c1decdb23441be527991a6186a90abaa0ce46faa5ad61cc3dfbfa01d5d0fc063620dbc4bb69db94a44a9f7
|
7
|
+
data.tar.gz: 58416d66482d8b39ea8ee9b3ac532c6e37b6a2de811542551fe4a857927beb5117bc13ef3a460320f19eaca683bf97939a0e257548fc12a1c81aec39a6c78163
|
data/.rubocop.yml
CHANGED
@@ -3,45 +3,73 @@ require:
|
|
3
3
|
- rubocop-rspec
|
4
4
|
- rubocop-rake
|
5
5
|
|
6
|
+
# Globals
|
7
|
+
|
8
|
+
AllCops:
|
9
|
+
NewCops: enable
|
10
|
+
|
6
11
|
# Layout
|
7
12
|
|
8
13
|
Layout/LineLength:
|
9
14
|
Max: 120
|
10
15
|
Exclude:
|
11
16
|
- 'spec/**/*_spec.rb'
|
12
|
-
- 'test/**/*_spec.rb'
|
13
17
|
|
14
18
|
Layout/EndAlignment:
|
15
19
|
EnforcedStyleAlignWith: variable
|
16
20
|
|
21
|
+
Layout/FirstArrayElementIndentation:
|
22
|
+
EnforcedStyle: consistent
|
23
|
+
|
17
24
|
# Metrics
|
18
25
|
|
19
26
|
Metrics/AbcSize:
|
20
27
|
CountRepeatedAttributes: false
|
21
28
|
Exclude:
|
22
29
|
- 'spec/**/*_spec.rb'
|
23
|
-
- 'test/**/*_spec.rb'
|
24
30
|
|
25
31
|
Metrics/BlockLength:
|
26
32
|
Exclude:
|
27
33
|
- 'spec/**/*_spec.rb'
|
28
|
-
- 'test/**/*_spec.rb'
|
29
34
|
|
30
35
|
Metrics/ClassLength:
|
36
|
+
Max: 150
|
37
|
+
CountComments: false
|
38
|
+
CountAsOne:
|
39
|
+
- array
|
40
|
+
- hash
|
41
|
+
- heredoc
|
42
|
+
Exclude:
|
43
|
+
- 'spec/**/*_spec.rb'
|
44
|
+
|
45
|
+
Metrics/MethodLength:
|
46
|
+
Max: 20
|
47
|
+
CountComments: false
|
48
|
+
CountAsOne:
|
49
|
+
- array
|
50
|
+
- hash
|
51
|
+
- heredoc
|
52
|
+
|
53
|
+
Metrics/ModuleLength:
|
54
|
+
Max: 150
|
55
|
+
CountComments: false
|
56
|
+
CountAsOne:
|
57
|
+
- array
|
58
|
+
- hash
|
59
|
+
- heredoc
|
31
60
|
Exclude:
|
32
61
|
- 'spec/**/*_spec.rb'
|
33
|
-
- 'test/**/*_spec.rb'
|
34
62
|
|
35
63
|
# Rspec
|
36
64
|
|
37
65
|
RSpec/ExampleLength:
|
38
|
-
Max:
|
66
|
+
Max: 25
|
39
67
|
|
40
68
|
RSpec/MessageSpies:
|
41
69
|
Enabled: false
|
42
70
|
|
43
71
|
RSpec/MultipleExpectations:
|
44
|
-
|
72
|
+
Enabled: false
|
45
73
|
|
46
74
|
RSpec/NestedGroups:
|
47
75
|
Max: 10
|
@@ -57,3 +85,12 @@ Style/ExpandPathArguments:
|
|
57
85
|
|
58
86
|
Style/StringLiterals:
|
59
87
|
Enabled: false
|
88
|
+
|
89
|
+
Style/TrailingCommaInArguments:
|
90
|
+
EnforcedStyleForMultiline: consistent_comma
|
91
|
+
|
92
|
+
Style/TrailingCommaInArrayLiteral:
|
93
|
+
EnforcedStyleForMultiline: consistent_comma
|
94
|
+
|
95
|
+
Style/TrailingCommaInHashLiteral:
|
96
|
+
EnforcedStyleForMultiline: consistent_comma
|
data/README.md
CHANGED
@@ -90,7 +90,7 @@ class RandomValueGenerator
|
|
90
90
|
end
|
91
91
|
|
92
92
|
decorate :log
|
93
|
-
decorate :
|
93
|
+
decorate :memoize
|
94
94
|
def values(max)
|
95
95
|
(1..max).map { rand }
|
96
96
|
end
|
@@ -149,35 +149,37 @@ class Foo
|
|
149
149
|
|
150
150
|
decorate :log
|
151
151
|
def some_method
|
152
|
-
#
|
152
|
+
# the method name (Foo#some_method) and arguments will be logged
|
153
153
|
end
|
154
154
|
|
155
155
|
decorate :memoize
|
156
156
|
def some_other_method
|
157
|
-
#
|
157
|
+
# the return value will be cached
|
158
158
|
end
|
159
159
|
|
160
|
-
decorate :
|
160
|
+
decorate :memoize
|
161
161
|
def yet_another_method(some_arg, some_other_arg = true, key_word_arg:, key_word_arg_with_default: 123)
|
162
|
-
#
|
162
|
+
# the return value will be cached based on the arguments the method receives
|
163
163
|
end
|
164
164
|
|
165
165
|
decorate :log
|
166
|
-
decorate :
|
166
|
+
decorate :memoize, for_any_arguments: true
|
167
167
|
def oh_boy_another_method(some_arg, some_other_arg = true, key_word_arg:, key_word_arg_with_default: 123)
|
168
|
-
#
|
168
|
+
# the method name (Foo#oh_boy_another_method) and arguments will be logged
|
169
|
+
# the return value will be cached regardless of the arguments received
|
169
170
|
end
|
170
171
|
|
171
172
|
decorate :log
|
172
173
|
def self.yeah_it_works_on_class_methods_too
|
173
|
-
#
|
174
|
+
# the method name (Foo::yeah_it_works_on_class_methods_too) and arguments
|
175
|
+
# will be logged
|
174
176
|
end
|
175
177
|
end
|
176
178
|
```
|
177
179
|
|
178
180
|
- `decorate :log` logs the method name and any passed arguments to the console
|
179
|
-
- `decorate :memoize` caches the result of the first call and returns that initial result (and does not execute the method again) for any additional calls
|
180
|
-
- `
|
181
|
+
- `decorate :memoize` caches the result of the first call and returns that initial result (and does not execute the method again) for any additional calls. By default, it namespaces the cache by the arguments passed to the method, so it will re-compute only if the arguments change; if the arguments are the same as any previous time the method was called, it will return the cached result instead.
|
182
|
+
- pass the `for_any_arguments: true` option (e.g., `decorate :memoize, for_any_arguments: true`) to ignore the arguments in the caching process and simply memoize the result no matter what
|
181
183
|
|
182
184
|
> **Note:** in the case of multiple decorators decorating a method, each is executed from top to bottom.
|
183
185
|
|
@@ -188,17 +190,18 @@ You can reference any decorator method you write, like so:
|
|
188
190
|
```rb
|
189
191
|
class FooDecorators
|
190
192
|
# Note: this is a class method
|
191
|
-
def self.blast_it(
|
193
|
+
def self.blast_it(context)
|
192
194
|
puts "Blasting it!"
|
193
195
|
value = yield
|
194
196
|
"#{value}!"
|
195
197
|
end
|
196
198
|
|
197
199
|
# Note: this is an instance method
|
198
|
-
def wait_for_it(
|
199
|
-
|
200
|
+
def self.wait_for_it(context, dot_count: 3)
|
201
|
+
ellipsis = dot_count.times.map { '.' }.join
|
202
|
+
puts "Waiting for it#{ellipsis}"
|
200
203
|
value = yield
|
201
|
-
"#{value}
|
204
|
+
"#{value}#{ellipsis}"
|
202
205
|
end
|
203
206
|
end
|
204
207
|
|
@@ -240,49 +243,57 @@ foo.yet_another_method(123, bloop: "bleep")
|
|
240
243
|
|
241
244
|
Use the `from:` option to specify what should receive the decorator method. Keep in mind that the decorator method will be called on the thing specified by `from:`... so, if you provide a class, it better be a class method, and if you supply an instance, it better be an instance method.
|
242
245
|
|
243
|
-
Every decorator method must take
|
246
|
+
Every custom decorator method that you define must take one required argument (`context`) and any number of keyword arguments.
|
244
247
|
|
245
|
-
|
246
|
-
- `method_name`: the name of the [decorated] method being called on `method_receiver` (a symbol); e.g., `:some_method` or `:other_method`
|
247
|
-
- `arguments`: an array of arguments passed to the [decorated] method, including keyword arguments; e.g., if `:yet_another_method` was called like `Foo.new.yet_another_method(123, bar: true)` then `arguments` would be `[123, {:bar=>true}]`
|
248
|
+
The **required argument** is an instance of `Adornable::Context`, which has some useful information about the decorated method being called
|
248
249
|
|
249
|
-
|
250
|
-
|
251
|
-
|
250
|
+
- `Adornable::Context#method_receiver`: the actual object that the [decorated] method is being called on (an object/class; e.g., `Foo` or an instance of `Foo`)
|
251
|
+
- `Adornable::Context#method_name`: the name of the [decorated] method being called on `method_receiver` (a symbol; e.g., `:some_method` or `:other_method`)
|
252
|
+
- `Adornable::Context#method_arguments`: an array of arguments passed to the [decorated] method, including keyword arguments as a final hash (e.g., if `:yet_another_method` was called like `Foo.new.yet_another_method(123, bar: true)` then `arguments` would be `[123, {:bar=>true}]`)
|
252
253
|
|
253
|
-
|
254
|
+
The **optional keyword arguments** are any parameters you want to be able to pass to the decorator method when decorating a method with `::decorate`:
|
254
255
|
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
value = yield
|
259
|
-
new_value = value.strip.to_i
|
260
|
-
puts "New value: #{value.inspect} (class: #{value.class})"
|
261
|
-
new_value
|
262
|
-
end
|
263
|
-
end
|
264
|
-
|
265
|
-
class Foo
|
266
|
-
extend Adornable
|
267
|
-
|
268
|
-
decorate :coerce_to_int, from: FooDecorators
|
269
|
-
def get_number_from_user
|
270
|
-
print "Enter a number: "
|
271
|
-
value = gets
|
272
|
-
puts "Value: #{value.inspect} (class: #{value.class})"
|
273
|
-
value
|
274
|
-
end
|
275
|
-
end
|
256
|
+
- If you define a decorator like `def self.some_decorator(context)` then it takes no options when it is used: `decorate :some_decorator`
|
257
|
+
- If you define a decorator like `def self.some_decorator(context, some_option:)` then it takes one _required_ keyword argument when it is used: `decorate :some_decorator, some_option: 123` (`::some_decorator`, will receive `123` every time the method it's decorating is called)
|
258
|
+
- Similarly, if you define a decorator like `def self.some_decorator(context, some_option: 456)`, then it takes one _optional_ keyword argument when it is used: `decorate :some_decorator` is valid (and implies `some_option: 456` since it has a default), and `decorate :some_decorator, some_option: 789` is valid as well
|
276
259
|
|
277
|
-
|
260
|
+
> **Note:** Every decorator method should _probably_ `yield` at some point in the method body. I say _"should"_ because, technically, you don't have to, but if you don't then the original method will never be called. That's a valid use-case, but 99% of the time you're gonna want to `yield`.
|
278
261
|
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
262
|
+
> **Note:** the return value of your decorator **will replace the return value of the decorated method,** so _also_ you should probably return whatever value `yield` returned. Again, it is a valid use case to return something _else,_ but 99% of the time you probably want to return the value returned by the wrapped method.
|
263
|
+
>
|
264
|
+
> A contrived example of when you might want to muck around with the return value:
|
265
|
+
>
|
266
|
+
> ```rb
|
267
|
+
> class FooDecorators
|
268
|
+
> def self.coerce_to_int(context)
|
269
|
+
> value = yield
|
270
|
+
> new_value = value.strip.to_i
|
271
|
+
> puts "New value: #{value.inspect} (class: #{value.class})"
|
272
|
+
> new_value
|
273
|
+
> end
|
274
|
+
> end
|
275
|
+
>
|
276
|
+
> class Foo
|
277
|
+
> extend Adornable
|
278
|
+
>
|
279
|
+
> decorate :coerce_to_int, from: FooDecorators
|
280
|
+
> def get_number_from_user
|
281
|
+
> print "Enter a number: "
|
282
|
+
> value = gets
|
283
|
+
> puts "Value: #{value.inspect} (class: #{value.class})"
|
284
|
+
> value
|
285
|
+
> end
|
286
|
+
> end
|
287
|
+
>
|
288
|
+
> foo = Foo.new
|
289
|
+
>
|
290
|
+
> foo.get_number_from_user
|
291
|
+
> # Enter a number
|
292
|
+
> # > 123
|
293
|
+
> # Value: "123" (class: String)
|
294
|
+
> # New value: 123 (class: Integer)
|
295
|
+
> #=> 123
|
296
|
+
> ```
|
286
297
|
|
287
298
|
#### Using custom decorators implicitly
|
288
299
|
|
@@ -291,7 +302,7 @@ You can also register decorator receivers so that you don't have to reference th
|
|
291
302
|
```rb
|
292
303
|
class FooDecorators
|
293
304
|
# Note: this is a class method
|
294
|
-
def self.blast_it(
|
305
|
+
def self.blast_it(context)
|
295
306
|
puts "Blasting it!"
|
296
307
|
value = yield
|
297
308
|
"#{value}!"
|
@@ -300,10 +311,11 @@ end
|
|
300
311
|
|
301
312
|
class MoreFooDecorators
|
302
313
|
# Note: this is a class method
|
303
|
-
def self.wait_for_it(
|
304
|
-
|
314
|
+
def self.wait_for_it(context, dot_count: 3)
|
315
|
+
ellipsis = dot_count.times.map { '.' }.join
|
316
|
+
puts "Waiting for it#{ellipsis}"
|
305
317
|
value = yield
|
306
|
-
"#{value}
|
318
|
+
"#{value}#{ellipsis}"
|
307
319
|
end
|
308
320
|
end
|
309
321
|
|
@@ -314,7 +326,7 @@ class Foo
|
|
314
326
|
add_decorators_from MoreFooDecorators
|
315
327
|
|
316
328
|
decorate :blast_it
|
317
|
-
decorate :wait_for_it
|
329
|
+
decorate :wait_for_it, dot_count: 9
|
318
330
|
def some_method
|
319
331
|
"haha I'm a method"
|
320
332
|
end
|
@@ -324,12 +336,12 @@ foo = Foo.new
|
|
324
336
|
|
325
337
|
foo.some_method
|
326
338
|
# Blasting it!
|
327
|
-
# Waiting for it
|
328
|
-
#=> "haha I'm a method
|
339
|
+
# Waiting for it.........
|
340
|
+
#=> "haha I'm a method!........."
|
329
341
|
```
|
330
342
|
|
331
|
-
> **Note:** In the case of duplicate decorator methods, later receivers registered with `::add_decorators_from` will override any
|
332
|
-
|
343
|
+
> **Note:** In the case of duplicate decorator methods, later receivers registered with `::add_decorators_from` will override any decorators by the same name from earlier registered receivers.
|
344
|
+
|
333
345
|
> **Note:** in the case of multiple decorators decorating a method, each is executed from top to bottom; i.e., the top wraps the next, which wraps the next, and so on, until the method itself is wrapped.
|
334
346
|
|
335
347
|
## Development
|
@@ -340,12 +352,18 @@ foo.some_method
|
|
340
352
|
bin/setup
|
341
353
|
```
|
342
354
|
|
343
|
-
### Run
|
355
|
+
### Run the tests
|
344
356
|
|
345
357
|
```bash
|
346
358
|
rake spec
|
347
359
|
```
|
348
360
|
|
361
|
+
### Run the linter
|
362
|
+
|
363
|
+
```bash
|
364
|
+
rubocop
|
365
|
+
```
|
366
|
+
|
349
367
|
### Create release
|
350
368
|
|
351
369
|
```
|
data/adornable.gemspec
CHANGED
@@ -25,11 +25,12 @@ Gem::Specification.new do |spec|
|
|
25
25
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
26
26
|
spec.require_paths = ["lib"]
|
27
27
|
|
28
|
-
spec.add_development_dependency "bundler", "~>
|
29
|
-
spec.add_development_dependency "rake", "~>
|
28
|
+
spec.add_development_dependency "bundler", "~> 2.2"
|
29
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
30
30
|
spec.add_development_dependency "rspec", "~> 3.0"
|
31
31
|
spec.add_development_dependency "rubocop", "~> 1.10"
|
32
32
|
spec.add_development_dependency "rubocop-performance", "~> 1.9"
|
33
33
|
spec.add_development_dependency "rubocop-rake", "~> 0.5"
|
34
34
|
spec.add_development_dependency "rubocop-rspec", "~> 2.2"
|
35
|
+
spec.add_development_dependency "solargraph"
|
35
36
|
end
|
data/lib/adornable.rb
CHANGED
@@ -13,7 +13,7 @@ module Adornable
|
|
13
13
|
@adornable_machinery ||= Adornable::Machinery.new
|
14
14
|
end
|
15
15
|
|
16
|
-
def decorate(decorator_name, from: nil, defer_validation: false)
|
16
|
+
def decorate(decorator_name, from: nil, defer_validation: false, **decorator_options)
|
17
17
|
if Adornable::Utils.blank?(name)
|
18
18
|
raise Adornable::Error::InvalidDecoratorArguments, "Decorator name must be provided."
|
19
19
|
end
|
@@ -21,7 +21,8 @@ module Adornable
|
|
21
21
|
adornable_machinery.accumulate_decorator!(
|
22
22
|
name: decorator_name,
|
23
23
|
receiver: from,
|
24
|
-
defer_validation: !!defer_validation
|
24
|
+
defer_validation: !!defer_validation,
|
25
|
+
decorator_options: decorator_options,
|
25
26
|
)
|
26
27
|
end
|
27
28
|
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Adornable
|
4
|
+
# A context object is passed to the decorator method, and contains information
|
5
|
+
# about the decorated method being called.
|
6
|
+
class Context
|
7
|
+
attr_reader(*%i[
|
8
|
+
method_receiver
|
9
|
+
method_name
|
10
|
+
method_arguments
|
11
|
+
decorator_name
|
12
|
+
decorator_options
|
13
|
+
])
|
14
|
+
|
15
|
+
def initialize(method_receiver:, method_name:, method_arguments:, decorator_name:, decorator_options:)
|
16
|
+
@method_receiver = method_receiver
|
17
|
+
@method_name = method_name
|
18
|
+
@method_arguments = method_arguments
|
19
|
+
@decorator_name = decorator_name
|
20
|
+
@decorator_options = decorator_options
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/adornable/decorators.rb
CHANGED
@@ -1,35 +1,42 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'adornable/utils'
|
4
|
+
|
3
5
|
module Adornable
|
4
6
|
# `Adornable::Decorators` is used as the default namespace for decorator
|
5
7
|
# methods when a decorator method that is neither explicitly sourced (via the
|
6
8
|
# `decorate from: <receiver>` option) nor implicitly sourced (via the
|
7
9
|
# `add_decorators_from <receiver>` macro).
|
8
10
|
class Decorators
|
9
|
-
def self.log(
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
full_name = "`#{receiver_name}#{name_delimiter}#{method_name}`"
|
16
|
-
arguments_desc = arguments.empty? ? "no arguments" : "arguments `#{arguments.inspect}`"
|
11
|
+
def self.log(context)
|
12
|
+
method_receiver = context.method_receiver
|
13
|
+
method_name = context.method_name
|
14
|
+
method_args = context.method_arguments
|
15
|
+
full_name = Adornable::Utils.formal_method_name(method_receiver, method_name)
|
16
|
+
arguments_desc = method_args.empty? ? "no arguments" : "arguments `#{method_args.inspect}`"
|
17
17
|
puts "Calling method #{full_name} with #{arguments_desc}"
|
18
18
|
yield
|
19
19
|
end
|
20
20
|
|
21
|
-
def self.memoize(
|
21
|
+
def self.memoize(context, for_any_arguments: false, &block)
|
22
|
+
return memoize_for_arguments(context, &block) unless for_any_arguments
|
23
|
+
|
24
|
+
method_receiver = context.method_receiver
|
25
|
+
method_name = context.method_name
|
22
26
|
memo_var_name = :"@adornable_memoized_#{method_receiver.object_id}_#{method_name}"
|
23
27
|
existing = instance_variable_get(memo_var_name)
|
24
28
|
value = existing.nil? ? yield : existing
|
25
29
|
instance_variable_set(memo_var_name, value)
|
26
30
|
end
|
27
31
|
|
28
|
-
def self.memoize_for_arguments(
|
32
|
+
def self.memoize_for_arguments(context)
|
33
|
+
method_receiver = context.method_receiver
|
34
|
+
method_name = context.method_name
|
35
|
+
method_args = context.method_arguments
|
29
36
|
memo_var_name = :"@adornable_memoized_for_arguments_#{method_receiver.object_id}_#{method_name}"
|
30
37
|
memo = instance_variable_get(memo_var_name) || {}
|
31
38
|
instance_variable_set(memo_var_name, memo)
|
32
|
-
args_key =
|
39
|
+
args_key = method_args.inspect
|
33
40
|
memo[args_key] = yield if memo[args_key].nil?
|
34
41
|
memo[args_key]
|
35
42
|
end
|
data/lib/adornable/machinery.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'adornable/utils'
|
4
4
|
require 'adornable/error'
|
5
|
+
require 'adornable/context'
|
5
6
|
|
6
7
|
module Adornable
|
7
8
|
class Machinery # :nodoc:
|
@@ -9,12 +10,17 @@ module Adornable
|
|
9
10
|
registered_decorator_receivers.unshift(receiver)
|
10
11
|
end
|
11
12
|
|
12
|
-
def accumulate_decorator!(name:, receiver:, defer_validation:)
|
13
|
+
def accumulate_decorator!(name:, receiver:, defer_validation:, decorator_options:)
|
13
14
|
name = name.to_sym
|
14
15
|
receiver ||= find_suitable_receiver_for(name)
|
15
16
|
validate_decorator!(name, receiver) unless defer_validation
|
16
17
|
|
17
|
-
decorator = {
|
18
|
+
decorator = {
|
19
|
+
name: name,
|
20
|
+
receiver: receiver,
|
21
|
+
options: decorator_options || {},
|
22
|
+
}
|
23
|
+
|
18
24
|
accumulated_decorators << decorator
|
19
25
|
end
|
20
26
|
|
@@ -82,15 +88,31 @@ module Adornable
|
|
82
88
|
@class_method_decorators[name] = decorators || []
|
83
89
|
end
|
84
90
|
|
85
|
-
def run_decorators(decorators, bound_method, *
|
86
|
-
return bound_method.call(*
|
91
|
+
def run_decorators(decorators, bound_method, *method_arguments)
|
92
|
+
return bound_method.call(*method_arguments) if Adornable::Utils.blank?(decorators)
|
87
93
|
|
88
94
|
decorator, *remaining_decorators = decorators
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
95
|
+
decorator_name = decorator[:name]
|
96
|
+
decorator_receiver = decorator[:receiver]
|
97
|
+
decorator_options = decorator[:options]
|
98
|
+
validate_decorator!(decorator_name, decorator_receiver, bound_method)
|
99
|
+
|
100
|
+
context = Adornable::Context.new(
|
101
|
+
method_receiver: bound_method.receiver,
|
102
|
+
method_name: bound_method.name,
|
103
|
+
method_arguments: method_arguments,
|
104
|
+
decorator_name: decorator_name,
|
105
|
+
decorator_options: decorator_options,
|
106
|
+
)
|
107
|
+
|
108
|
+
send_parameters = if Adornable::Utils.present?(decorator_options)
|
109
|
+
[decorator_name, context, decorator_options]
|
110
|
+
else
|
111
|
+
[decorator_name, context]
|
112
|
+
end
|
113
|
+
|
114
|
+
decorator_receiver.send(*send_parameters) do
|
115
|
+
run_decorators(remaining_decorators, bound_method, *method_arguments)
|
94
116
|
end
|
95
117
|
end
|
96
118
|
|
@@ -100,15 +122,13 @@ module Adornable
|
|
100
122
|
end
|
101
123
|
end
|
102
124
|
|
103
|
-
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/
|
125
|
+
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Layout/LineLength
|
104
126
|
def validate_decorator!(decorator_name, decorator_receiver, bound_method = nil)
|
105
127
|
return if decorator_receiver.respond_to?(decorator_name)
|
106
128
|
|
107
129
|
location_hint = if bound_method
|
108
130
|
method_receiver = bound_method.receiver
|
109
|
-
|
110
131
|
method_full_name = method_receiver.is_a?(Class) ? "#{method_receiver}::#{method.name}" : "#{method_receiver.class}##{method.name}"
|
111
|
-
|
112
132
|
method_location = bound_method.source_location
|
113
133
|
"Cannot decorate `#{method_full_name}` (defined at `#{method_location.first}:#{method_location.second})."
|
114
134
|
end
|
@@ -126,6 +146,6 @@ module Adornable
|
|
126
146
|
message = [location_hint, base_message, definition_hint].compact.join(" ")
|
127
147
|
raise Adornable::Error::InvalidDecoratorArguments, message
|
128
148
|
end
|
129
|
-
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/
|
149
|
+
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Layout/LineLength
|
130
150
|
end
|
131
151
|
end
|
data/lib/adornable/utils.rb
CHANGED
@@ -14,6 +14,15 @@ module Adornable
|
|
14
14
|
def presence(value)
|
15
15
|
value if present?(value)
|
16
16
|
end
|
17
|
+
|
18
|
+
def formal_method_name(method_receiver, method_name)
|
19
|
+
receiver_name, name_delimiter = if method_receiver.is_a?(Class)
|
20
|
+
[method_receiver.to_s, '::']
|
21
|
+
else
|
22
|
+
[method_receiver.class.to_s, '#']
|
23
|
+
end
|
24
|
+
"`#{receiver_name}#{name_delimiter}#{method_name}`"
|
25
|
+
end
|
17
26
|
end
|
18
27
|
end
|
19
28
|
end
|
data/lib/adornable/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: adornable
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Keegan Leitz
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-02-
|
11
|
+
date: 2021-02-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -16,28 +16,28 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '2.2'
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '2.2'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rake
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
33
|
+
version: '13.0'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
40
|
+
version: '13.0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: rspec
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -108,6 +108,20 @@ dependencies:
|
|
108
108
|
- - "~>"
|
109
109
|
- !ruby/object:Gem::Version
|
110
110
|
version: '2.2'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: solargraph
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
111
125
|
description: Method decorators for Ruby
|
112
126
|
email:
|
113
127
|
- kjleitz@gmail.com
|
@@ -120,7 +134,6 @@ files:
|
|
120
134
|
- ".rubocop.yml"
|
121
135
|
- ".travis.yml"
|
122
136
|
- Gemfile
|
123
|
-
- Gemfile.lock
|
124
137
|
- LICENSE
|
125
138
|
- README.md
|
126
139
|
- Rakefile
|
@@ -128,6 +141,7 @@ files:
|
|
128
141
|
- bin/console
|
129
142
|
- bin/setup
|
130
143
|
- lib/adornable.rb
|
144
|
+
- lib/adornable/context.rb
|
131
145
|
- lib/adornable/decorators.rb
|
132
146
|
- lib/adornable/error.rb
|
133
147
|
- lib/adornable/machinery.rb
|
@@ -137,7 +151,7 @@ homepage: https://github.com/kjleitz/adornable
|
|
137
151
|
licenses:
|
138
152
|
- MIT
|
139
153
|
metadata: {}
|
140
|
-
post_install_message:
|
154
|
+
post_install_message:
|
141
155
|
rdoc_options: []
|
142
156
|
require_paths:
|
143
157
|
- lib
|
@@ -152,8 +166,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
152
166
|
- !ruby/object:Gem::Version
|
153
167
|
version: '0'
|
154
168
|
requirements: []
|
155
|
-
rubygems_version: 3.
|
156
|
-
signing_key:
|
169
|
+
rubygems_version: 3.1.4
|
170
|
+
signing_key:
|
157
171
|
specification_version: 4
|
158
172
|
summary: Method decorators for Ruby
|
159
173
|
test_files: []
|