amenable 0.0.2 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 133a9c4b0a84e4325394ededc6826cb7408a9505d1c45bdfdd06674a10fac5ff
4
- data.tar.gz: 1345444593420700a2b84938c64f58343e6f33c8a6399fd955fb09cd717c74c0
3
+ metadata.gz: 641c3e9528672faac363aedc32ab19e35494d4ed23b247f8a0962f02b0c53bc7
4
+ data.tar.gz: e4e39cf70b4f5b90b78c8896655c50df183c005eb59550b2556541df1d46c5a6
5
5
  SHA512:
6
- metadata.gz: 7fcb746e4541e5360d25b5281d0b407be1b851ba97196d5bae01fec41eae898227bc87aa09dba59d91e22b5045aa14dd93d63856323805cc4944641b79df71b6
7
- data.tar.gz: 9072334b6a123882a656f07db3797064cf726fdaf7900ecddc90e808b27ca6503246c0417e1f953e4941fc9c69e2ddce1a5a04be69e245aada6a3b6cf64bccf1
6
+ metadata.gz: b4bafaf94f2a612bc53f1000426d56a6d42f67076fc33dd887fc53c36dca2a616080a6eb7c35f4c2a59a41b2b2b5a408b4040dce948d655954d53a275d2e9d87
7
+ data.tar.gz: d1c499194cb06d2975ccbe73ae044ad217952feac63257b310be218c0b18f0262eb7a9ade8c5d25c158bcbac154a594612a709aa9a51ac64408176c15846f6a7
data/lib/amenable.rb CHANGED
@@ -4,12 +4,8 @@ module Amenable
4
4
  extend self
5
5
 
6
6
  def call(fn, *args, **kwargs, &block)
7
- wrap(fn).call(*args, **kwargs, &block)
8
- end
9
-
10
- def wrap(fn)
11
- unless fn.respond_to?(:call)
12
- raise ArgumentError, "fn must be callable: #{fn}"
7
+ unless fn.is_a?(Method) || fn.is_a?(Proc)
8
+ raise ArgumentError, "expecting type Method or Proc, found: #{fn.class}"
13
9
  end
14
10
 
15
11
  rest = keyrest = false
@@ -29,12 +25,60 @@ module Amenable
29
25
  end
30
26
  end
31
27
 
32
- proc do |*args, **kwargs, &block|
33
- # remove unreferenced params
34
- args.slice! params.count unless rest
35
- kwargs = kwargs.slice(*keys) unless keyrest
28
+ # remove excessive args
29
+ args = args.slice(0, params.count) unless rest
36
30
 
31
+ if !keys.empty? || keyrest
32
+ # remove excessive keywords
33
+ kwargs = kwargs.slice(*keys) unless keyrest
37
34
  fn.call(*args, **kwargs, &block)
35
+ else
36
+ fn.call(*args, &block)
37
+ end
38
+ end
39
+
40
+ refine Object do
41
+ def amenable(name)
42
+ # identify method and way to dynamically redefine it
43
+ if methods(false).include?(name)
44
+ fn = method(name).unbind # rebind later to support subclassing
45
+ definer = method(:define_singleton_method)
46
+ elsif private_methods(false).include?(name)
47
+ fn = method(name).unbind
48
+ definer = ->(name, &block) do
49
+ define_singleton_method(name, &block)
50
+ private_class_method(name)
51
+ end
52
+ elsif private_instance_methods(false).include?(name)
53
+ fn = instance_method(name)
54
+ definer = ->(name, &block) do
55
+ define_method(name, &block)
56
+ private(name)
57
+ end
58
+ elsif protected_instance_methods(false).include?(name)
59
+ fn = instance_method(name)
60
+ definer = ->(name, &block) do
61
+ define_method(name, &block)
62
+ protected(name)
63
+ end
64
+ elsif instance_methods(false).include?(name)
65
+ fn = instance_method(name)
66
+ definer = method(:define_method)
67
+ else
68
+ raise NoMethodError
69
+ end
70
+
71
+ # rebuild method with Amenable wrapper
72
+ definer.call(name) do |*args, &block|
73
+ # bind method to instance or subclass, which is now available
74
+ if fn.respond_to? :bind
75
+ fn = fn.bind(self)
76
+ end
77
+
78
+ Amenable.call(fn, *args, &block)
79
+ end
80
+
81
+ name
38
82
  end
39
83
  end
40
84
  end
@@ -1,3 +1,3 @@
1
1
  module Amenable
2
- VERSION = '0.0.2'
2
+ VERSION = '0.1.0'
3
3
  end
@@ -1,32 +1,7 @@
1
1
  describe Amenable do
2
- describe '.wrap' do
3
- let(:fn) { proc {} }
4
-
5
- it 'requires a function-like input' do
6
- expect { Amenable.wrap(nil) }.to raise_error(ArgumentError)
7
- end
8
-
9
- it 'wraps a function with a function' do
10
- res = Amenable.wrap(fn)
11
- expect(res).to be_a Proc
12
- expect(res).not_to be fn
13
- end
14
-
15
- it 'returns a function that takes any input' do
16
- params = Amenable.wrap(fn).parameters.map &:first
17
- expect(params).to eq [ :rest, :keyrest, :block ]
18
- end
19
- end
20
-
21
2
  describe '.call' do
22
3
  let(:fn) { proc {|x, y = :y, a:, b: 2| [ [ x, y ], { a: a, b: b } ] } }
23
4
 
24
- it 'calls .wrap under the hood' do
25
- fn = proc {}
26
- expect(Amenable).to receive(:wrap).with(fn).and_call_original
27
- Amenable.call(fn)
28
- end
29
-
30
5
  it 'takes args and kwargs as expected' do
31
6
  expect(fn).to receive(:call).with(:x, :y, a: 1, b: 2)
32
7
  Amenable.call(fn, :x, :y, a: 1, b: 2)
@@ -115,5 +90,93 @@ describe Amenable do
115
90
  ])
116
91
  end
117
92
  end
93
+
94
+ context 'with a method that does not take params' do
95
+ before do
96
+ def test_fn; end
97
+ end
98
+
99
+ it 'works without args' do
100
+ expect(Amenable.call(method(:test_fn))).to be_nil
101
+ end
102
+
103
+ it 'works with args' do
104
+ expect(Amenable.call(method(:test_fn), :x, :y)).to be_nil
105
+ end
106
+
107
+ it 'works with kwargs' do
108
+ expect(Amenable.call(method(:test_fn), a: 1, b: 2)).to be_nil
109
+ end
110
+ end
111
+
112
+ context 'with a method that only takes args' do
113
+ before do
114
+ def test_fn(arg)
115
+ arg
116
+ end
117
+ end
118
+
119
+ it 'fails without args' do
120
+ expect {
121
+ Amenable.call(method(:test_fn))
122
+ }.to raise_error(ArgumentError)
123
+ end
124
+
125
+ it 'works with args' do
126
+ expect(Amenable.call(method(:test_fn), :x, :y)).to be :x
127
+ end
128
+
129
+ it 'works with kwargs' do
130
+ expect(Amenable.call(method(:test_fn), :x, a: 1, b: 2)).to be :x
131
+ end
132
+ end
133
+
134
+ context 'with a method that only takes varargs' do
135
+ before do
136
+ def test_fn(*args)
137
+ args
138
+ end
139
+ end
140
+
141
+ it 'works without args' do
142
+ expect(Amenable.call(method(:test_fn))).to eq []
143
+ end
144
+
145
+ it 'works with args' do
146
+ expect(Amenable.call(method(:test_fn), :x, :y)).to eq [ :x, :y ]
147
+ end
148
+
149
+ it 'works with kwargs' do
150
+ expect(Amenable.call(method(:test_fn), :x, :y, a: 1, b: 2)).to eq [ :x, :y ]
151
+ end
152
+ end
153
+
154
+ context 'with a method that only takes keywords' do
155
+ before do
156
+ def test_fn(a:)
157
+ a
158
+ end
159
+ end
160
+
161
+ it 'fails without args' do
162
+ expect {
163
+ Amenable.call(method(:test_fn))
164
+ }.to raise_error(ArgumentError)
165
+ end
166
+
167
+ it 'works with args' do
168
+ expect(Amenable.call(method(:test_fn), :x, a: 1)).to be 1
169
+ end
170
+
171
+ it 'works with kwargs' do
172
+ expect(Amenable.call(method(:test_fn), a: 1, b: 2)).to be 1
173
+ end
174
+ end
175
+
176
+ it 'requires a Method or Proc' do
177
+ expect {
178
+ Amenable.call(:foo)
179
+ }.to raise_error(ArgumentError)
180
+ end
118
181
  end
119
182
  end
@@ -0,0 +1,21 @@
1
+ class Dog
2
+ using Amenable
3
+
4
+ amenable def bark(n = 1)
5
+ ([ :woof ] * n).join(" ")
6
+ end
7
+ end
8
+
9
+ describe Dog do
10
+ it 'barks' do
11
+ expect(Dog.new.bark).to eq "woof"
12
+ end
13
+
14
+ it 'barks n times' do
15
+ expect(Dog.new.bark(2)).to eq "woof woof"
16
+ end
17
+
18
+ it 'ignores excessive parameters' do
19
+ expect(Dog.new.bark(2, 3, :x, a: 1)).to eq "woof woof"
20
+ end
21
+ end
@@ -0,0 +1,159 @@
1
+ class Animal
2
+ using Amenable
3
+
4
+ def initialize(name)
5
+ @name = name
6
+ end
7
+
8
+ amenable def name
9
+ @name
10
+ end
11
+
12
+ amenable def self.species
13
+ to_s.downcase
14
+ end
15
+ end
16
+
17
+ class Dog < Animal
18
+ using Amenable
19
+
20
+ def bark(n = 1)
21
+ [ :woof ] * n
22
+ end
23
+ amenable :bark
24
+ end
25
+
26
+
27
+ describe Dog do
28
+ let(:gino) { Dog.new('Gino') }
29
+
30
+ before do
31
+ expect(Amenable).to receive(:call).and_call_original
32
+ end
33
+
34
+ describe '#bark' do
35
+ it 'works with the default value' do
36
+ expect(gino.bark).to eq [ :woof ]
37
+ end
38
+
39
+ it 'accepts args' do
40
+ expect(gino.bark(2)).to eq [ :woof, :woof ]
41
+ end
42
+
43
+ it 'ignores excessive args' do
44
+ expect(gino.bark(2, :x, a: 4)).to eq [ :woof, :woof ]
45
+ end
46
+ end
47
+
48
+ describe '#name' do
49
+ it 'works without args' do
50
+ expect(gino.name).to eq 'Gino'
51
+ end
52
+
53
+ it 'works with any args' do
54
+ expect(gino.name(:x, a: 1)).to eq 'Gino'
55
+ end
56
+ end
57
+
58
+ describe '.species' do
59
+ it 'works without args' do
60
+ expect(Dog.species).to eq 'dog'
61
+ end
62
+
63
+ it 'works with any args' do
64
+ expect(Dog.species(:x, a: 1)).to eq 'dog'
65
+ end
66
+ end
67
+ end
68
+
69
+
70
+ class Cat < Animal
71
+ using Amenable
72
+
73
+ private amenable def asleep?
74
+ true
75
+ end
76
+
77
+ protected def lives
78
+ 9
79
+ end
80
+ amenable :lives
81
+
82
+ private
83
+
84
+ amenable def nice?
85
+ false
86
+ end
87
+
88
+ private_class_method def self.agreeable?
89
+ false
90
+ end
91
+ amenable :agreeable?
92
+
93
+ class << self
94
+ private
95
+
96
+ amenable def evil?
97
+ true
98
+ end
99
+ end
100
+ end
101
+
102
+ describe Cat do
103
+ let(:cat) { Cat.new('Cheshire') }
104
+
105
+ describe '#lives' do
106
+ it 'is protected' do
107
+ expect(Cat.protected_instance_methods(false)).to include(:lives)
108
+ expect { cat.lives }.to raise_error(NoMethodError)
109
+ end
110
+
111
+ it 'works with any params' do
112
+ expect(cat.send(:lives, :x, :y, a: 1, b: 2)).to be 9
113
+ end
114
+ end
115
+
116
+ describe '#asleep?' do
117
+ it 'is private' do
118
+ expect(Cat.private_instance_methods(false)).to include(:asleep?)
119
+ expect { cat.asleep? }.to raise_error(NoMethodError)
120
+ end
121
+
122
+ it 'works with any params' do
123
+ expect(cat.send(:asleep?, :x, :y, a: 1, b: 2)).to be true
124
+ end
125
+ end
126
+
127
+ describe '#nice?' do
128
+ it 'is private' do
129
+ expect(Cat.private_instance_methods(false)).to include(:nice?)
130
+ expect { cat.nice? }.to raise_error(NoMethodError)
131
+ end
132
+
133
+ it 'works with any params' do
134
+ expect(cat.send(:nice?, :x, :y, a: 1, b: 2)).to be false
135
+ end
136
+ end
137
+
138
+ describe '.agreeable?' do
139
+ it 'is private' do
140
+ expect(Cat.private_methods(false)).to include(:agreeable?)
141
+ expect { Cat.agreeable? }.to raise_error(NoMethodError)
142
+ end
143
+
144
+ it 'works with any params' do
145
+ expect(Cat.send(:agreeable?, :x, :y, a: 1, b: 2)).to be false
146
+ end
147
+ end
148
+
149
+ describe '.evil?' do
150
+ it 'is private' do
151
+ expect(Cat.private_methods(false)).to include(:evil?)
152
+ expect { Cat.evil? }.to raise_error(NoMethodError)
153
+ end
154
+
155
+ it 'works with any params' do
156
+ expect(Cat.send(:evil?, :x, :y, a: 1, b: 2)).to be true
157
+ end
158
+ end
159
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: amenable
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Pepper
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-06-26 00:00:00.000000000 Z
11
+ date: 2021-07-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: byebug
@@ -38,20 +38,6 @@ dependencies:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
- - !ruby/object:Gem::Dependency
42
- name: rake
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - ">="
46
- - !ruby/object:Gem::Version
47
- version: '0'
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - ">="
53
- - !ruby/object:Gem::Version
54
- version: '0'
55
41
  - !ruby/object:Gem::Dependency
56
42
  name: rspec
57
43
  requirement: !ruby/object:Gem::Requirement
@@ -89,6 +75,8 @@ files:
89
75
  - lib/amenable.rb
90
76
  - lib/amenable/version.rb
91
77
  - spec/amenable_spec.rb
78
+ - spec/readme_spec.rb
79
+ - spec/refinement_spec.rb
92
80
  homepage: https://github.com/dpep/amenable_rb
93
81
  licenses:
94
82
  - MIT
@@ -114,3 +102,5 @@ specification_version: 4
114
102
  summary: Amenable
115
103
  test_files:
116
104
  - spec/amenable_spec.rb
105
+ - spec/refinement_spec.rb
106
+ - spec/readme_spec.rb