decors 0.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c33d4924184f251e5ee9c3a372251355e09efe7f
4
+ data.tar.gz: 9ddc59be81ccc266b78d36b3b9dc656b37c97bc0
5
+ SHA512:
6
+ metadata.gz: 3b965a9750314f1f8e921510c9e6275eae49ec18c3f8a2285b796700acfb813e9db6aa4f95e9c68d179d1cc2665bf8891c093a9617a94971f44cf4098f1f801e
7
+ data.tar.gz: 6f6e62a25f06fe9e2d474aa1ec45cc43c85f4e598d070e2b0da5ee9b42d854b8b0a44ab96f5e2d4b4567921dd9ac5a4ee75d8a4b766c46a0820b8b6b008132e4
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ .DS_Store
2
+ .rspec
3
+ .rubocop.yml
4
+ *.gem
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ # Decors changelog
2
+
3
+ ## Version 0.1 (Released April 24, 2017)
4
+
5
+ - First implementation
data/README.md ADDED
@@ -0,0 +1,18 @@
1
+ # Decors
2
+
3
+ #### Yet another implementation of Python Decorators/Java annotation in ruby
4
+
5
+ Here are other implementations:
6
+ - https://github.com/wycats/ruby_decorators
7
+ - https://github.com/michaelfairley/method_decorators
8
+ - https://github.com/fredwu/ruby_decorators
9
+ - https://gist.github.com/reu/2762650
10
+
11
+ ## Install
12
+
13
+ Add `gem 'decors'` to your Gemfile and then `bundle`
14
+
15
+ ## Usage
16
+
17
+ ## License
18
+
data/decors.gemspec ADDED
@@ -0,0 +1,22 @@
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+
4
+ require 'decors'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "decors"
8
+ gem.version = ::Decors::VERSION
9
+ gem.authors = ['Vivien Meyet']
10
+ gem.email = ['vivien@getbannerman.com']
11
+ gem.description = "Ruby implementation of Python method decorators / Java annotations"
12
+ gem.summary = gem.description
13
+ gem.homepage = 'https://github.com/getbannerman/decors'
14
+
15
+ gem.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^spec/})
18
+ gem.require_paths = ['lib']
19
+
20
+ gem.add_development_dependency 'pry-byebug', '~> 0'
21
+ gem.add_development_dependency 'rspec', '~> 0'
22
+ end
@@ -0,0 +1,25 @@
1
+ module Decors
2
+ class DecoratorBase
3
+ attr_reader :decorated_class, :decorated_method, :decorator_args, :decorator_kwargs, :decorator_block
4
+
5
+ def initialize(decorated_class, decorated_method, *args, **kwargs, &block)
6
+ @decorated_class = decorated_class
7
+ @decorated_method = decorated_method
8
+ @decorator_args = args
9
+ @decorator_kwargs = kwargs
10
+ @decorator_block = block
11
+ end
12
+
13
+ def call(instance, *args, &block)
14
+ undecorated_call(instance, *args, &block)
15
+ end
16
+
17
+ def undecorated_call(instance, *args, &block)
18
+ decorated_method.bind(instance).call(*args, &block)
19
+ end
20
+
21
+ def decorated_method_name
22
+ decorated_method.name
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,17 @@
1
+ module Decors
2
+ module DecoratorDefinition
3
+ def define_mixin_decorator(decorator_name, decorator_class)
4
+ define_decorator(decorator_name, decorator_class, mixin: true)
5
+ end
6
+
7
+ def define_decorator(decorator_name, decorator_class, mixin: false)
8
+ method_definer = mixin ? :define_method : :define_singleton_method
9
+
10
+ send(method_definer, decorator_name) do |*params, &blk|
11
+ ctx = self.singleton_class? ? ObjectSpace.each_object(self).first : self
12
+ ctx.send(:extend, MethodAddedListener)
13
+ ctx.declared_decorators << [decorator_class, params, blk]
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,35 @@
1
+ module Decors
2
+ # Mixin that add method_added hooks
3
+ module MethodAddedListener
4
+ def method_added(meth)
5
+ super
6
+ handle_method_addition(self, meth)
7
+ end
8
+
9
+ def singleton_method_added(meth)
10
+ super
11
+ handle_method_addition(singleton_class, meth)
12
+ end
13
+
14
+ def declared_decorators
15
+ @declared_decorators ||= []
16
+ end
17
+
18
+ private
19
+
20
+ # method addition handling is the same for singleton and instance method
21
+ def handle_method_addition(clazz, method_name)
22
+ # @_ignore_additions allows to temporarily disable the hook
23
+ return if @_ignore_additions || declared_decorators.empty?
24
+ decorator_class, params, blk = declared_decorators.pop
25
+
26
+ decorated_method = clazz.instance_method(method_name)
27
+
28
+ @_ignore_additions = true
29
+ decorator = decorator_class.new(clazz, decorated_method, *params, &blk)
30
+ @_ignore_additions = false
31
+
32
+ clazz.send(:define_method, method_name) { |*args, &block| decorator.call(self, *args, &block) }
33
+ end
34
+ end
35
+ end
data/lib/decors.rb ADDED
@@ -0,0 +1,7 @@
1
+ require 'decors/decorator_base'
2
+ require 'decors/decorator_definition'
3
+ require 'decors/method_added_listener'
4
+
5
+ module Decors
6
+ VERSION = '0.1.0'
7
+ end
@@ -0,0 +1,369 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe Decors do
4
+ before { stub_class(:TestClass) { extend Decors::DecoratorDefinition } }
5
+ before {
6
+ stub_class(:Spy) {
7
+ def self.passed_args(*args, **kwargs, &block)
8
+ arguments(args: args, kwargs: kwargs, evald_block: block&.call)
9
+ end
10
+
11
+ def self.arguments(*); end
12
+ }
13
+ }
14
+
15
+ context 'when simple case decorator' do
16
+ before {
17
+ stub_class(:SimpleDecorator, inherits: [::Decors::DecoratorBase]) {
18
+ def initialize(decorated_class, decorated_method, *deco_args, **deco_kwargs, &deco_block)
19
+ super
20
+ Spy.passed_args(*deco_args, **deco_kwargs, &deco_block)
21
+ end
22
+
23
+ def call(instance, *args, &block)
24
+ super
25
+ Spy.passed_args(*args, &block)
26
+ end
27
+ }
28
+
29
+ TestClass.class_eval { define_decorator :SimpleDecorator, SimpleDecorator }
30
+ }
31
+
32
+ context 'It receive all the parameters at initialization' do
33
+ it { expect(SimpleDecorator).to receive(:new).with(TestClass, anything, 1, 2, a: 3).and_call_original }
34
+ it { expect(Spy).to receive(:arguments).with(args: [1, 2], kwargs: { a: 3 }, evald_block: 'ok') }
35
+
36
+ after {
37
+ TestClass.class_eval {
38
+ SimpleDecorator(1, 2, a: 3) { 'ok' }
39
+ def test_method(*); end
40
+
41
+ def test_method_not_decorated(*); end
42
+ }
43
+ }
44
+ end
45
+
46
+ context 'It receive the instance and the arguments passed to the method when called' do
47
+ let(:instance) { TestClass.new }
48
+ before {
49
+ TestClass.class_eval {
50
+ SimpleDecorator()
51
+ def test_method(*args, &block)
52
+ Spy.passed_args(*args, &block)
53
+ end
54
+
55
+ def test_method_not_decorated; end
56
+ }
57
+ }
58
+
59
+ it { expect_any_instance_of(SimpleDecorator).to receive(:call).with(instance, 1, a: 2, b: 3, &proc { 'yes' }) }
60
+ it { expect(Spy).to receive(:arguments).with(args: [1], kwargs: { a: 2, b: 3 }, evald_block: 'yes').twice }
61
+
62
+ after {
63
+ instance.test_method(1, a: 2, b: 3) { 'yes' }
64
+ instance.test_method_not_decorated
65
+ }
66
+ end
67
+ end
68
+
69
+ context 'when decorator is defining a method during initialization' do
70
+ before {
71
+ stub_class(:StrangeDecorator, inherits: [::Decors::DecoratorBase]) {
72
+ def initialize(decorated_class, decorated_method, *deco_args, **deco_kwargs, &deco_block)
73
+ super
74
+ decorated_class.send(:define_method, :foo) { 42 }
75
+ end
76
+
77
+ def call(*)
78
+ super * 2
79
+ end
80
+ }
81
+
82
+ TestClass.class_eval { define_decorator :StrangeDecorator, StrangeDecorator }
83
+ }
84
+
85
+ before {
86
+ TestClass.class_eval {
87
+ StrangeDecorator()
88
+ StrangeDecorator()
89
+ def test_method
90
+ 5
91
+ end
92
+ }
93
+ }
94
+
95
+ it { expect(TestClass.new.test_method).to eq 5 * 2 * 2 }
96
+ it { expect(TestClass.new.foo).to eq 42 }
97
+ end
98
+
99
+ context 'when mutiple decorators' do
100
+ before {
101
+ Spy.class_eval {
102
+ @ordered_calls = []
103
+
104
+ class << self
105
+ attr_reader :ordered_calls
106
+
107
+ def calling(name)
108
+ self.ordered_calls << name
109
+ end
110
+ end
111
+ }
112
+
113
+ stub_class(:Deco1, inherits: [::Decors::DecoratorBase]) {
114
+ def call(*)
115
+ Spy.calling(:deco1_before)
116
+ super
117
+ Spy.calling(:deco1_after)
118
+ end
119
+ }
120
+
121
+ stub_class(:Deco2, inherits: [::Decors::DecoratorBase]) {
122
+ def call(*)
123
+ Spy.calling(:deco2_before)
124
+ super
125
+ Spy.calling(:deco2_after)
126
+ end
127
+ }
128
+
129
+ TestClass.class_eval {
130
+ define_decorator :Deco1, Deco1
131
+ define_decorator :Deco2, Deco2
132
+
133
+ Deco2()
134
+ Deco1()
135
+ def test_method(*)
136
+ Spy.calling(:inside)
137
+ end
138
+ }
139
+ }
140
+
141
+ before { TestClass.new.test_method }
142
+ it { expect(Spy.ordered_calls).to eq [:deco2_before, :deco1_before, :inside, :deco1_after, :deco2_after] }
143
+ end
144
+
145
+ context 'when method has return value' do
146
+ before {
147
+ stub_class(:ModifierDeco, inherits: [::Decors::DecoratorBase])
148
+
149
+ TestClass.class_eval {
150
+ define_decorator :ModifierDeco, ModifierDeco
151
+
152
+ ModifierDeco()
153
+ def test_method
154
+ :ok
155
+ end
156
+ }
157
+ }
158
+
159
+ it { expect(TestClass.new.test_method).to eq :ok }
160
+ end
161
+
162
+ context 'when method has arguments' do
163
+ before {
164
+ stub_class(:ModifierDeco, inherits: [::Decors::DecoratorBase])
165
+
166
+ TestClass.class_eval {
167
+ define_decorator :ModifierDeco, ModifierDeco
168
+
169
+ ModifierDeco()
170
+ def test_method(*args, &block)
171
+ Spy.passed_args(*args, &block)
172
+ end
173
+ }
174
+ }
175
+
176
+ it { expect(Spy).to receive(:arguments).with(args: [1, 2, 3], kwargs: { a: :a }, evald_block: 'yay') }
177
+ after { TestClass.new.test_method(1, 2, 3, a: :a) { 'yay' } }
178
+ end
179
+
180
+ context 'when changing arguments given to the method' do
181
+ before {
182
+ stub_class(:ModifierDeco, inherits: [::Decors::DecoratorBase]) {
183
+ def call(instance, *)
184
+ undecorated_call(instance, 1, 2, 3, a: :a, &proc { 'yay' })
185
+ end
186
+ }
187
+
188
+ TestClass.class_eval {
189
+ define_decorator :ModifierDeco, ModifierDeco
190
+
191
+ ModifierDeco()
192
+ def test_method(*args, &block)
193
+ Spy.passed_args(*args, &block)
194
+ end
195
+ }
196
+ }
197
+
198
+ it { expect(Spy).to receive(:arguments).with(args: [1, 2, 3], kwargs: { a: :a }, evald_block: 'yay') }
199
+ after { TestClass.new.test_method }
200
+ end
201
+
202
+ context 'when method is recursive' do
203
+ before {
204
+ stub_class(:AddOneToArg, inherits: [::Decors::DecoratorBase]) {
205
+ def call(instance, *args)
206
+ undecorated_call(instance, args.first + 1)
207
+ end
208
+ }
209
+
210
+ TestClass.class_eval {
211
+ define_decorator :AddOneToArg, AddOneToArg
212
+
213
+ AddOneToArg()
214
+ def test_method(n)
215
+ return 0 if n.zero?
216
+ n + test_method(n - 2)
217
+ end
218
+ }
219
+ }
220
+
221
+ it { expect(TestClass.new.test_method(4)).to eq 5 + 4 + 3 + 2 + 1 }
222
+ end
223
+
224
+ context 'when already has a method_added' do
225
+ before {
226
+ stub_module(:TestMixin) {
227
+ def method_added(*)
228
+ Spy.called
229
+ end
230
+ }
231
+ stub_class(:Deco, inherits: [::Decors::DecoratorBase])
232
+ }
233
+ it { expect(Spy).to receive(:called) }
234
+
235
+ after {
236
+ TestClass.class_eval {
237
+ extend TestMixin
238
+
239
+ define_decorator :Deco, Deco
240
+
241
+ def test_method; end
242
+ }
243
+ }
244
+ end
245
+
246
+ context 'when inherited' do
247
+ before {
248
+ stub_class(:Deco, inherits: [::Decors::DecoratorBase])
249
+
250
+ TestClass.class_eval {
251
+ define_decorator :Deco, Deco
252
+
253
+ Deco()
254
+ def test_method
255
+ :ok
256
+ end
257
+ }
258
+ }
259
+
260
+ it {
261
+ stub_class(:TestClass2, inherits: [TestClass])
262
+ TestClass2.class_eval {
263
+ Deco()
264
+ def test_method
265
+ :ko
266
+ end
267
+ }
268
+
269
+ expect(TestClass.new.test_method).to eq :ok
270
+ expect(TestClass2.new.test_method).to eq :ko
271
+ }
272
+
273
+ it {
274
+ stub_class(:TestClass3, inherits: [TestClass])
275
+
276
+ TestClass3.class_eval {
277
+ Deco()
278
+ def test_method
279
+ "this is #{super}"
280
+ end
281
+ }
282
+
283
+ expect(TestClass3.new.test_method).to eq 'this is ok'
284
+ }
285
+ end
286
+
287
+ context 'when decorating a class method' do
288
+ before {
289
+ stub_class(:Deco, inherits: [::Decors::DecoratorBase]) {
290
+ def call(*)
291
+ super
292
+ Spy.called
293
+ end
294
+ }
295
+ }
296
+
297
+ context 'when mixin extended on the class (singleton method in class)' do
298
+ before {
299
+ TestClass.class_eval {
300
+ define_decorator :Deco, Deco
301
+
302
+ Deco()
303
+ def self.test_method
304
+ :ok
305
+ end
306
+ }
307
+ }
308
+
309
+ it { expect(Spy).to receive(:called) }
310
+ after { TestClass.test_method }
311
+ end
312
+
313
+ context 'when mixin extended on the class (method in singleton class)' do
314
+ before {
315
+ TestClass.class_eval {
316
+ class << self
317
+ extend Decors::DecoratorDefinition
318
+
319
+ define_decorator :Deco, Deco
320
+
321
+ Deco()
322
+ def test_method
323
+ :ok
324
+ end
325
+ end
326
+ }
327
+ }
328
+
329
+ it { expect(Spy).to receive(:called) }
330
+ after { TestClass.test_method }
331
+ end
332
+
333
+ context 'when mixin extended on the class (both method in singleton class and singleton method in class)' do
334
+ before {
335
+ TestClass.class_eval {
336
+ define_decorator :Deco, Deco
337
+
338
+ Deco()
339
+ def self.test_method__in_class
340
+ :ok
341
+ end
342
+
343
+ def self.untest_method__in_class
344
+ end
345
+
346
+ class << self
347
+ extend Decors::DecoratorDefinition
348
+
349
+ define_decorator :Deco, Deco
350
+
351
+ Deco()
352
+ def test_method__in_singleton
353
+ :ok
354
+ end
355
+
356
+ def untest_method__in_singleton
357
+ end
358
+ end
359
+ }
360
+ }
361
+
362
+ it { expect(Spy).to receive(:called) and TestClass.test_method__in_class }
363
+ it { expect(Spy).to receive(:called) and TestClass.test_method__in_singleton }
364
+ it { expect(Spy).to_not receive(:called) and TestClass.untest_method__in_class }
365
+ it { expect(Spy).to_not receive(:called) and TestClass.untest_method__in_singleton }
366
+ end
367
+ end
368
+ end
369
+
@@ -0,0 +1,19 @@
1
+ require File.expand_path('../../lib/decors', __FILE__)
2
+
3
+ module SpecHelpers
4
+ def stub_class(class_name, inherits: [], &class_eval)
5
+ klass = stub_const(class_name.to_s, Class.new(*inherits))
6
+ klass.class_eval(&class_eval) if class_eval
7
+ klass
8
+ end
9
+
10
+ def stub_module(module_name, &module_eval)
11
+ mod = stub_const(module_name.to_s, Module.new)
12
+ mod.module_eval(&module_eval) if module_eval
13
+ mod
14
+ end
15
+ end
16
+
17
+ RSpec.configure do |config|
18
+ config.include SpecHelpers
19
+ end
metadata ADDED
@@ -0,0 +1,83 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: decors
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Vivien Meyet
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-04-25 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: pry-byebug
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: Ruby implementation of Python method decorators / Java annotations
42
+ email:
43
+ - vivien@getbannerman.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - ".gitignore"
49
+ - CHANGELOG.md
50
+ - README.md
51
+ - decors.gemspec
52
+ - lib/decors.rb
53
+ - lib/decors/decorator_base.rb
54
+ - lib/decors/decorator_definition.rb
55
+ - lib/decors/method_added_listener.rb
56
+ - spec/decors_spec.rb
57
+ - spec/spec_helper.rb
58
+ homepage: https://github.com/getbannerman/decors
59
+ licenses: []
60
+ metadata: {}
61
+ post_install_message:
62
+ rdoc_options: []
63
+ require_paths:
64
+ - lib
65
+ required_ruby_version: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ required_rubygems_version: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ requirements: []
76
+ rubyforge_project:
77
+ rubygems_version: 2.6.8
78
+ signing_key:
79
+ specification_version: 4
80
+ summary: Ruby implementation of Python method decorators / Java annotations
81
+ test_files:
82
+ - spec/decors_spec.rb
83
+ - spec/spec_helper.rb