bindable_block 0.0.5.1 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 5b1c8b49ff2472deebf4c6d9e551166d037b1b8a
4
+ data.tar.gz: df827455f3cd67f49550b331849fcda641ca48d3
5
+ SHA512:
6
+ metadata.gz: fe81548ae86dc946d2dbe42c3f21ecf9401877eabbec57a1da4183456dc46bc6d7a629f14b3be2c76bfeb47df1e253f39f49a163f388434157173612e8c4a653
7
+ data.tar.gz: 30b47e2b53ab657136bead810544a145b1d57b25235ff31256b7fe9458068319a01bd2821a47576de54b8ae79cad29f2fbd47532e8c2c37c0bf098b55b9a0112
data/README.md CHANGED
@@ -1,16 +1,56 @@
1
1
  # BindableBlock
2
2
 
3
- `instance_exec` can't take block arguments. Get around that with BindableProc
3
+ `instance_exec` can't take block arguments. Get around that with BindableProc (also provides optional `BasicObject#instance_exec_b`)
4
+
5
+ If you understand that previous statement, then I probably can't dissuade you from using this.
6
+ If you don't, then a word of warning: This is probably the wrong solution.
7
+ `instance_exec` is probably the wrong solution too.
8
+ Metaprogramming is almost never merited.
9
+ It's lighting your way by setting yourself on fire.
10
+ Your problem will be simpler if you don't use it.
11
+ It will be more comprehensible.
12
+ You'll waste less time later trying to figure out how to get it to do what you want.
13
+ 95% of the times I've used metaprogramming, I've later regretted it.
14
+ The whole use-case that led to the creation of this gem was wrong,
15
+ it was unnecessary, it added tremendous complexity to the implementation.
16
+ Think Rails moving from `find_by_name(name)` to `find_by(name: name)`,
17
+ and how much better that was.
18
+
19
+ BUT! If I still can't convince you, then read on:
20
+
4
21
 
5
22
  ```ruby
6
23
  require 'bindable_block'
7
24
 
8
25
  User = Struct.new :name
9
- greeter = BindableBlock.new(User) { "Welcome, #{name}" }
26
+ greeter = BindableBlock.new { "Welcome, #{name}" }
10
27
  greeter.bind(User.new "Josh").call # => "Welcome, Josh"
11
28
  ```
12
29
 
30
+ ```ruby
31
+ require 'bindable_block/instance_exec_b'
32
+
33
+ # http://rdoc.info/stdlib/core/BasicObject:instance_exec
34
+ class KlassWithSecret
35
+ def initialize
36
+ @secret = 99
37
+ end
38
+ end
39
+ k = KlassWithSecret.new
40
+ sum = 10
41
+ sum_updater = lambda { |to_add| sum += to_add }
42
+ result = k.instance_exec_b(5, sum_updater) { |x, &b| b.call(@secret+x) } # => 114
43
+ sum # => 114
44
+ ```
45
+
13
46
  [Here](https://github.com/JoshCheek/surrogate/blob/eb1d7f98a148c032f6d3ef1d8df8b703386f286d/lib/surrogate/options.rb#L32-34) is an example.
47
+ Note that it is the old-style where users had to submit the class that could be bound to.
48
+
49
+ ## Possible advances in usefulness
50
+
51
+ It just occurred to me that if we bound it to BasicObject
52
+ then it could be used on any class without the user needing to
53
+ specify which class they want to bind it to.
14
54
 
15
55
  ## Where the abstraction leaks
16
56
 
@@ -1,18 +1,60 @@
1
1
  # BindableBlock
2
2
 
3
- `instance_exec` can't take block arguments. Get around that with BindableProc
3
+ `instance_exec` can't take block arguments. Get around that with BindableProc (also provides optional `BasicObject#instance_exec_b`)
4
+
5
+ If you understand that previous statement, then I probably can't dissuade you from using this.
6
+ If you don't, then a word of warning: This is probably the wrong solution.
7
+ `instance_exec` is probably the wrong solution too.
8
+ Metaprogramming is almost never merited.
9
+ It's lighting your way by setting yourself on fire.
10
+ Your problem will be simpler if you don't use it.
11
+ It will be more comprehensible.
12
+ You'll waste less time later trying to figure out how to get it to do what you want.
13
+ 95% of the times I've used metaprogramming, I've later regretted it.
14
+ The whole use-case that led to the creation of this gem was wrong,
15
+ it was unnecessary, it added tremendous complexity to the implementation.
16
+ Think Rails moving from `find_by_name(name)` to `find_by(name: name)`,
17
+ and how much better that was.
18
+
19
+ BUT! If I still can't convince you, then read on:
20
+
4
21
 
5
22
  ```ruby
6
23
  <% test 'example', with: :magic_comments do %>
7
24
  require 'bindable_block'
8
25
 
9
26
  User = Struct.new :name
10
- greeter = BindableBlock.new(User) { "Welcome, #{name}" }
27
+ greeter = BindableBlock.new { "Welcome, #{name}" }
11
28
  greeter.bind(User.new "Josh").call # => "Welcome, Josh"
12
29
  <% end %>
13
30
  ```
14
31
 
32
+ ```ruby
33
+ <% test 'example', with :magic_comments do %>
34
+ require 'bindable_block/instance_exec_b'
35
+
36
+ # http://rdoc.info/stdlib/core/BasicObject:instance_exec
37
+ class KlassWithSecret
38
+ def initialize
39
+ @secret = 99
40
+ end
41
+ end
42
+ k = KlassWithSecret.new
43
+ sum = 10
44
+ sum_updater = lambda { |to_add| sum += to_add }
45
+ result = k.instance_exec_b(5, sum_updater) { |x, &b| b.call(@secret+x) } # => 114
46
+ sum # => 114
47
+ <% end %>
48
+ ```
49
+
15
50
  [Here](https://github.com/JoshCheek/surrogate/blob/eb1d7f98a148c032f6d3ef1d8df8b703386f286d/lib/surrogate/options.rb#L32-34) is an example.
51
+ Note that it is the old-style where users had to submit the class that could be bound to.
52
+
53
+ ## Possible advances in usefulness
54
+
55
+ It just occurred to me that if we bound it to BasicObject
56
+ then it could be used on any class without the user needing to
57
+ specify which class they want to bind it to.
16
58
 
17
59
  ## Where the abstraction leaks
18
60
 
@@ -4,7 +4,7 @@ require File.expand_path('../lib/bindable_block/version', __FILE__)
4
4
  Gem::Specification.new do |gem|
5
5
  gem.authors = ["Josh Cheek"]
6
6
  gem.email = ["josh.cheek@gmail.com"]
7
- gem.description = %q{instance_exec can't pass block arguments through. Use a bindable block instead.}
7
+ gem.description = %q{instance_exec can't pass block arguments through. Use a bindable block instead (there is also an optional core extension instance_exec_b, which will mimic the instance_exec interface, but also allow you to pass the block).}
8
8
  gem.summary = %q{Allows you to bind procs to instances of classes}
9
9
 
10
10
  gem.homepage = "https://github.com/JoshCheek/bindable_block"
@@ -15,4 +15,8 @@ Gem::Specification.new do |gem|
15
15
  gem.name = "bindable_block"
16
16
  gem.require_paths = ["lib"]
17
17
  gem.version = BindableBlock::VERSION
18
+
19
+ gem.required_ruby_version = '~> 2.1'
20
+
21
+ gem.add_development_dependency 'rspec', '>= 3.0.0', '~> 3.0'
18
22
  end
@@ -1,69 +1,70 @@
1
1
  require "bindable_block/version"
2
+ require 'bindable_block/bound_block'
2
3
 
3
- class BindableBlock
4
+ class BindableBlock < Proc
4
5
 
5
6
  # match args to arity, since instance_method has lambda properties
6
- class ArgAligner
7
- def initialize(args, instance_method)
8
- @result, @args, @parameters = [], args, instance_method.parameters.map(&:first)
9
- take num :req
10
- take [num(:opt), args.size].min
11
- take args.size if has? :rest
12
- end
13
-
14
- def call
15
- result
16
- end
17
7
 
18
- private
19
-
20
- attr_reader :args, :parameters, :result
21
-
22
- def has?(type)
23
- parameters.any? { |param| param == type }
24
- end
25
-
26
- def num(type)
27
- parameters.count { |param| param == type }
28
- end
29
-
30
- def take(n)
31
- n.times { result << args.shift }
32
- end
33
- end
34
-
35
-
36
- def initialize(klass, &block)
8
+ def initialize(klass=BasicObject, &block)
9
+ @klass = klass
37
10
  @original_block = block
38
-
39
- klass.__send__ :define_method, method_name, &block
40
- @instance_method = klass.instance_method method_name
41
- klass.__send__ :remove_method, method_name
11
+ if curried_values = block.instance_variable_get(:@curried_values)
12
+ @original_block = curried_values[:original_block]
13
+ @curried_args = curried_values[:curried_args]
14
+ @uncurried_size = curried_values[:uncurried_size]
15
+ end
16
+ @instance_method = block_to_method klass, @original_block
42
17
  end
43
18
 
44
- attr_reader :instance_method, :original_block
45
-
46
19
  def bind(target)
47
- Proc.new do |*args, &block|
48
- instance_method.bind(target).call(*align(args), &block)
20
+ bound = BoundBlock.new @original_block, &@instance_method.bind(target)
21
+ if @curried_args then bound.curry(@uncurried_size)[*@curried_args]
22
+ else bound
49
23
  end
50
24
  end
51
25
 
52
26
  def call(*args, &block)
53
- original_block.call(*args, &block)
27
+ @original_block.call(*args, &block)
54
28
  end
55
29
 
56
- def to_proc
57
- method(:call).to_proc
30
+ def arity
31
+ @original_block.arity
58
32
  end
59
33
 
60
- private
61
-
62
- def align(args)
63
- ArgAligner.new(args, instance_method).call
34
+ def curry(arity=nil)
35
+ arity ||= @instance_method.parameters.count { |type, _| type == :req }
36
+ original_block = @original_block # can't use the imeth, because it's unbound, so no curry
37
+ bindable_blk_class = self.class
38
+ klass = @klass
39
+
40
+ proc_maker = lambda do |curried_args, uncurried_size|
41
+ p = Proc.new do |*args, &block|
42
+ actual_args = curried_args + args
43
+ if uncurried_size <= actual_args.size
44
+ original_block.call(*actual_args, &block)
45
+ else
46
+ curried = proc_maker.call actual_args, uncurried_size
47
+ bindable_blk_class.new klass, &curried
48
+ end
49
+ end
50
+ p.instance_variable_set :@curried_values, {
51
+ original_block: original_block,
52
+ curried_args: curried_args,
53
+ uncurried_size: arity,
54
+ }
55
+ p
56
+ end
57
+ curried = proc_maker.call [], arity
58
+ BindableBlock.new klass, &curried
64
59
  end
65
60
 
66
- def method_name
67
- @method_name ||= "bindable_block_#{Time.now.to_i}_#{$$}_#{rand 1000000}"
61
+ private
62
+
63
+ def block_to_method(klass, block)
64
+ temp_method_name = "bindable_block_#{Time.now.to_i}_#{$$}_#{rand 1_000_0000}"
65
+ @klass.__send__(:define_method, temp_method_name, &block)
66
+ @klass.instance_method(temp_method_name)
67
+ ensure
68
+ @klass.__send__(:remove_method, temp_method_name)
68
69
  end
69
70
  end
@@ -0,0 +1,30 @@
1
+ class BindableBlock < Proc
2
+ class ArgAligner
3
+ def initialize(args, instance_method)
4
+ @result, @args, @parameters = [], args, instance_method.parameters.map(&:first)
5
+ take num :req
6
+ take [num(:opt), args.size].min
7
+ take args.size if has? :rest
8
+ end
9
+
10
+ def call
11
+ result
12
+ end
13
+
14
+ private
15
+
16
+ attr_reader :args, :parameters, :result
17
+
18
+ def has?(type)
19
+ parameters.any? { |param| param == type }
20
+ end
21
+
22
+ def num(type)
23
+ parameters.count { |param| param == type }
24
+ end
25
+
26
+ def take(n)
27
+ n.times { result << args.shift }
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,61 @@
1
+ require 'bindable_block/arg_aligner'
2
+
3
+ class BindableBlock < Proc
4
+ class BoundBlock < Proc
5
+ def initialize(original_block, &method)
6
+ f, ln, * = caller[2].split(':')
7
+ self.bound_file = f
8
+ self.bound_line_number = ln.to_i
9
+ self.original_block = original_block
10
+ self.method = method
11
+ end
12
+
13
+ def call(*args, &block)
14
+ method.call(*align(args), &block)
15
+ end
16
+
17
+ def lambda?
18
+ original_block.lambda?
19
+ end
20
+
21
+ def bound_location
22
+ [bound_file.dup, bound_line_number]
23
+ end
24
+
25
+ def parameters
26
+ original_block.parameters
27
+ end
28
+
29
+ def inspect
30
+ original_block.to_s.gsub(/^#<\w*/, "#<#{self.class.name}")
31
+ end
32
+ alias to_s inspect
33
+
34
+
35
+ def binding
36
+ raise NotImplementedError, <<-SADFACE.gsub(/^\s*/, '')
37
+ I legit tried to figure this out, and can't :(
38
+
39
+ * Can't just ask, it's private on most objects
40
+ * Can't use `__send__` b/c it's like "the binding at the top of the callstack", which would not be the binding of the obj you're invoking it on
41
+ * Can't `instance_eval { binding }`, because BasicObject doesn't have a binding method
42
+ * Can't `Kernel.instance_method(:binding).bind(obj).instance_eval { binding }` because if obj is a BasicObject, then Kernel isn't an ancestor and thus won't bind to it
43
+ * Tried all manner of adding temp methods, subclassing the singleton class, etc. In the end, I think it's just not doable.
44
+
45
+ This is your friendly reminder that whatever you're using this method for is probably bullshit.
46
+ SADFACE
47
+ end
48
+
49
+ private
50
+
51
+ attr_accessor :bound_file, :bound_line_number, :original_block, :method
52
+
53
+ def align(args)
54
+ if original_block.lambda?
55
+ args
56
+ else
57
+ ArgAligner.new(args, method).call
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,16 @@
1
+ require 'bindable_block'
2
+
3
+ class BasicObject
4
+ def instance_exec_b(*args, argument_block, &instance_block)
5
+ if argument_block.nil?
6
+ instance_exec(*args, &instance_block)
7
+ elsif argument_block.respond_to?(:call) && argument_block.respond_to?(:to_proc)
8
+ ::BindableBlock
9
+ .new(self.singleton_class, &instance_block)
10
+ .bind(self)
11
+ .call(*args, &argument_block)
12
+ else
13
+ ::Kernel.raise ::ArgumentError, "Last argument to instance_exec_b must be blocky (respond to #call and #to_proc), or nil"
14
+ end
15
+ end
16
+ end
@@ -1,3 +1,3 @@
1
- class BindableBlock
2
- VERSION = "0.0.5.1"
1
+ class BindableBlock < Proc
2
+ VERSION = "0.0.7"
3
3
  end
@@ -1,101 +1,401 @@
1
1
  require 'bindable_block'
2
2
 
3
+ # TODO: when created with a lambda, args should match like lambda args
4
+
3
5
  describe BindableBlock do
6
+ let(:name) { 'Unbound Name' }
4
7
  let(:default_name) { "Carmen" }
5
8
  let(:klass) { Struct.new :name }
6
9
  let(:instance) { klass.new default_name }
7
10
 
8
- it 'can be bound to instances of the target' do
9
- block = BindableBlock.new(klass) { self }
10
- block.bind(instance).call.should equal instance
11
+ # minitest style, b/c otherwise the levels of things being passed around gets to be a bit much
12
+ def assert_equal(a, b)
13
+ expect(b).to eq a
11
14
  end
12
-
13
- it 'can be rebound' do
14
- block = BindableBlock.new(klass) { name }
15
- block.bind(klass.new 'Josh').call.should == 'Josh'
16
- block.bind(klass.new 'Mei').call.should == 'Mei'
15
+ def assert_same(a, b)
16
+ expect(b).to equal a
17
+ end
18
+ def assert(val)
19
+ expect(val).to be_truthy
17
20
  end
21
+ def refute(val)
22
+ expect(val).to_not be_truthy
23
+ end
24
+
25
+
26
+ describe 'binding' do
27
+ it 'can be bound to any object' do
28
+ block = BindableBlock.new { self }
29
+ assert_same block.bind(instance).call, instance
30
+ end
31
+
32
+ it 'can be bound to a BasicObject' do
33
+ o = BasicObject.new
34
+ o.instance_eval { @a = 1 }
35
+ assert_equal 1, BindableBlock.new { @a }.bind(o).call
36
+ end
37
+
38
+ it 'can be rebound' do
39
+ block = BindableBlock.new { name }
40
+ assert_equal 'Josh', block.bind(klass.new 'Josh').call
41
+ assert_equal 'Mei', block.bind(klass.new 'Mei').call
42
+ end
43
+
44
+ it 'can be rebound to instances of different classes' do
45
+ klass1 = Class.new { def m; 1; end }
46
+ klass2 = Class.new { def m; 2; end }
47
+ block = BindableBlock.new { m }
48
+ assert_equal 1, block.bind(klass1.new).call
49
+ assert_equal 2, block.bind(klass2.new).call
50
+ end
18
51
 
19
- it 'can also just be invoked without being bound' do
20
- def self.a() 1 end
21
- b = 2
22
- block = BindableBlock.new(klass) { |c, &d| a + b + c + d.call }
23
- block.call(3){4}.should == 10
52
+ it 'can scope the class of objects it can be bound to' do
53
+ klass1 = Class.new
54
+ klass2 = Class.new
55
+ block = BindableBlock.new(klass1) {}
56
+ block.bind(klass1.new).call
57
+ expect { block.bind(klass2.new).call }.to raise_error TypeError, /instance of/
58
+ end
24
59
  end
25
60
 
26
- it 'can be passed to methods and shit' do
27
- doubler = lambda { |&block| block.call + block.call }
28
- doubler.call(&BindableBlock.new(klass) { 12 }).should == 24
29
-
30
- # sadly this part doesn't work, I think it unwraps the block from the proc, then re-wraps it in a new one, losing the singleton method
31
- # binder = lambda { |&block| block.bind(instance).call }
32
- # binder.call(&BindableBlock.new(klass) { name }).should == default_name
33
- #
34
- # This was my attempted to_proc implementation
35
- # bindable_block = self
36
- # proc = method(:call).to_proc
37
- # proc.define_singleton_method(:bind) { |target| bindable_block.bind target }
38
- # proc
61
+ describe 'as a block' do
62
+ it 'can be passed in the block slot to methods and shit' do
63
+ doubler = lambda { |&block| block.call + '!' }
64
+ assert_equal 'Unbound Name!', doubler.call(&BindableBlock.new { name })
65
+ assert_equal 'Carmen!', doubler.call(&BindableBlock.new { name }.bind(instance))
66
+ end
67
+
68
+ it 'can be invoked without being bound, in which case self is that of the scope it was defined in' do
69
+ def self.a() 1 end
70
+ @b = 2
71
+ c = 3
72
+ block = BindableBlock.new { |d, &e| a + @b + c + d + e.call }
73
+ assert_equal 15, block.call(4){5}
74
+ end
75
+
76
+ it 'retains it\'s bindability after being passed through a method' do
77
+ block = BindableBlock.new { name }
78
+ def m(&bindable_block)
79
+ bindable_block.bind(instance).call
80
+ end
81
+ assert_equal default_name, m(&block)
82
+ end
83
+
84
+ it 'has a #bound_location to complement #source_location' do
85
+ unbound = BindableBlock.new { }
86
+ bound = unbound.bind(instance)
87
+ assert_equal bound.bound_location, [__FILE__, __LINE__-1]
88
+ end
89
+
90
+ specify '#bound_location isn\'t susceptible to mutation of returned value' do
91
+ bound = BindableBlock.new { }.bind(instance)
92
+ bound.bound_location.first.replace "" # mutate the string
93
+ bound.bound_location.replace [] # mutate the array
94
+ assert_equal bound.bound_location, [__FILE__, __LINE__-3]
95
+ end
96
+
97
+ context 'Proc instance methods' do
98
+ let(:args_and_name) { BindableBlock.new { |arg| [arg, name] } }
99
+
100
+ example '#[]' do
101
+ assert_equal [1, 'Unbound Name'], args_and_name[1]
102
+ assert_equal [1, 'Carmen'], args_and_name.bind(instance)[1]
103
+ end
104
+
105
+ example '#===' do
106
+ assert_equal [1, 'Unbound Name'], args_and_name === 1
107
+ assert_equal [1, 'Carmen'], args_and_name.bind(instance) === 1
108
+ end
109
+
110
+ example '#yield' do
111
+ assert_equal [1, 'Unbound Name'], args_and_name.yield(1)
112
+ assert_equal [1, 'Carmen'], args_and_name.bind(instance).yield(1)
113
+ end
114
+
115
+ example '#arity' do
116
+ p = Proc.new { |a, b, c=1, d=2, *e, f| [a,b,c,d,e,f] }
117
+ b = BindableBlock.new(&p)
118
+ assert_equal p.arity, b.arity
119
+ assert_equal p.arity, b.bind(instance).arity
120
+
121
+ l = Proc.new { |a, b, c=1, d=2, *e, f| [a,b,c,d,e,f] }
122
+ b = BindableBlock.new(&l)
123
+ assert_equal l.arity, b.arity
124
+ assert_equal l.arity, b.bind(instance).arity
125
+ end
126
+
127
+ example '#binding' do
128
+ a = 1
129
+ b = BindableBlock.new { }
130
+
131
+ assert_equal 1, b.binding.eval('a')
132
+ assert_equal self, b.binding.eval('self')
133
+
134
+ pending "If anyone can solve this, I'll be so impressed"
135
+ assert_equal 1, b.bind(instance).binding.eval('a')
136
+ assert_equal instance, b.bind(instance).binding.eval('self')
137
+ end
138
+
139
+ example '#clone' do
140
+ assert_equal [1, 'Unbound Name'], args_and_name.clone.call(1)
141
+ assert_equal [1, 'Carmen'], args_and_name.bind(instance).clone.call(1)
142
+ end
143
+
144
+ example '#dup' do
145
+ assert_equal [1, 'Unbound Name'], args_and_name.dup.call(1)
146
+ assert_equal [1, 'Carmen'], args_and_name.bind(instance).dup.call(1)
147
+ end
148
+
149
+ example '#curry without being bound' do
150
+ b = BindableBlock.new { |a, b, c, &d| [a, b, c, d.call, name] }
151
+ four = lambda { 4 }
152
+
153
+ assert_equal [1, 2, 3, 4, 'Unbound Name'], b.curry[1, 2, 3, &four]
154
+ assert_equal [1, 2, 3, 4, 'Unbound Name'], b.curry[1][2][3, &four]
155
+ assert_equal [1, 2, 3, 4, 'Unbound Name'], b.curry[1, 2][3, &four]
156
+ end
157
+
158
+ example '#curry after being bound' do
159
+ b = BindableBlock.new { |a, b, c, &d| [a, b, c, d.call, name] }.bind(instance)
160
+ four = lambda { 4 }
161
+ assert_equal [1, 2, 3, 4, 'Carmen'], b.curry[1][2][3, &four]
162
+ assert_equal [1, 2, 3, 4, 'Carmen'], b.curry[1, 2][3, &four]
163
+ end
164
+
165
+ example '#curry before being bound' do
166
+ b = BindableBlock.new { |a, b, c, &d| [a, b, c, d.call, name] }
167
+ four = lambda { 4 }
168
+ assert_equal [1, 2, 3, 4, 'Carmen'], b.curry[1].bind(instance).curry[2][3, &four]
169
+ assert_equal [1, 2, 3, 4, 'Carmen'], b.curry[1, 2].bind(instance).curry[3, &four]
170
+ assert_equal [1, 2, 3, 4, 'Carmen'], b.curry[1][2].bind(instance).curry[3, &four]
171
+ assert_equal [1, 2, 3, 4, 'Carmen'], b.curry[1][2].bind(instance).curry[3, &four]
172
+ end
173
+
174
+ example '#hash' do
175
+ raise pending 'punting on this, b/c I really don\'t know what it should do here'
176
+ end
177
+
178
+ example '#lambda?' do
179
+ refute BindableBlock.new {}.lambda?
180
+ refute BindableBlock.new {}.bind(instance).lambda?
181
+
182
+ assert BindableBlock.new(&lambda {}).lambda?
183
+ assert BindableBlock.new(&lambda {}).bind(instance).lambda?
184
+ end
185
+
186
+ example '#parameters' do
187
+ b = BindableBlock.new { |a, b=1, *c, d, e:, **f, &g| }
188
+ assert_equal [[:opt, :a], [:opt, :b], [:rest, :c], [:opt, :d], [:keyreq, :e], [:keyrest, :f], [:block, :g]], b.parameters
189
+ assert_equal [[:opt, :a], [:opt, :b], [:rest, :c], [:opt, :d], [:keyreq, :e], [:keyrest, :f], [:block, :g]], b.bind(instance).parameters
190
+
191
+ b = BindableBlock.new(&lambda { |a, b=1, *c, d, e:, **f, &g| })
192
+ assert_equal [[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:keyreq, :e], [:keyrest, :f], [:block, :g]], b.parameters
193
+ assert_equal [[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:keyreq, :e], [:keyrest, :f], [:block, :g]], b.bind(instance).parameters
194
+ end
195
+
196
+ example '#source_location' do
197
+ b, f, l = BindableBlock.new { }, __FILE__, __LINE__
198
+ assert_equal [f, l], b.source_location
199
+ assert_equal [f, l], b.bind(instance).source_location
200
+ end
201
+
202
+ example '#to_proc' do
203
+ unbound = BindableBlock.new {}
204
+ assert_same unbound, unbound.to_proc
205
+
206
+ bound = unbound.bind(instance)
207
+ assert_same bound, bound.to_proc
208
+ end
209
+
210
+ example '#to_s and #inspect have the source file and line' do
211
+ # proc
212
+ unbound, f, l = BindableBlock.new {}, __FILE__, __LINE__
213
+ expect(unbound.to_s).to include "#{f}:#{l}"
214
+ expect(unbound.to_s).to include "BindableBlock"
215
+
216
+ bound = unbound.bind(instance)
217
+ expect(bound.to_s).to include "#{f}:#{l}"
218
+ expect(bound.to_s).to include "BindableBlock::BoundBlock"
219
+
220
+ # lambda
221
+ unbound, f, l = BindableBlock.new(&lambda {}), __FILE__, __LINE__
222
+ expect(unbound.to_s).to include "#{f}:#{l}"
223
+ expect(unbound.to_s).to include "BindableBlock"
224
+
225
+ bound = unbound.bind(instance)
226
+ expect(bound.to_s).to include "#{f}:#{l}"
227
+ expect(bound.to_s).to include "BindableBlock::BoundBlock"
228
+ end
229
+
230
+ specify '#to_s and #inspect should not have lambda in them' do
231
+ # proc
232
+ unbound = BindableBlock.new {}
233
+ expect(unbound.to_s).to_not include 'lambda'
234
+ expect(unbound.inspect).to_not include 'lambda'
235
+
236
+ bound = unbound.bind instance
237
+ expect(bound.to_s).to_not include 'lambda'
238
+ expect(bound.inspect).to_not include 'lambda'
239
+
240
+ # lambda
241
+ unbound = BindableBlock.new(&lambda {})
242
+ expect(unbound.to_s).to include 'lambda'
243
+ expect(unbound.inspect).to include 'lambda'
244
+
245
+ bound = unbound.bind instance
246
+ expect(bound.to_s).to include 'lambda'
247
+ expect(bound.inspect).to include 'lambda'
248
+ end
249
+ end
39
250
  end
40
251
 
252
+
41
253
  describe 'arguments' do
254
+ def assert_same_error(b1, b2)
255
+ e1 = b1.call rescue $!
256
+ e2 = b2.call rescue $!
257
+ expect(e1).to be_an Exception
258
+ expect(e2).to be_an Exception
259
+ assert_equal e1.class, e2.class
260
+ assert_equal e1.message, e2.message
261
+ end
262
+
263
+ it 'matches them lambda-style if initialized with a lambda' do
264
+ l = lambda { |a| a }
265
+ block = BindableBlock.new(&l)
266
+ assert_equal 1, block.call(1)
267
+ assert_equal 1, block.bind(instance).call(1)
268
+ assert_same_error lambda { block.call }, lambda { l.call }
269
+ assert_same_error lambda { block.call 1, 2 }, lambda { l.call 1, 2 }
270
+ assert_same_error lambda { block.bind(instance).call }, lambda { l.call }
271
+ assert_same_error lambda { block.bind(instance).call 1, 2 }, lambda { l.call 1, 2 }
272
+ end
273
+
274
+ it 'matches them proc-style if initialized with a proc' do
275
+ p = Proc.new { |a| a }
276
+ block = BindableBlock.new(&p)
277
+ assert_equal 1, block.call(1)
278
+ assert_equal p.call, block.call
279
+ assert_equal p.call(1), block.call(1)
280
+ assert_equal p.call(1, 2), block.call(1, 2)
281
+
282
+ assert_equal 1, block.bind(instance).call(1)
283
+ assert_equal p.call, block.bind(instance).call
284
+ assert_equal p.call(1), block.bind(instance).call(1)
285
+ assert_equal p.call(1, 2), block.bind(instance).call(1, 2)
286
+ end
287
+
42
288
  it 'can take a block' do
43
- block = BindableBlock.new(klass) { |a, &b| [a, (b&&b.call)] }.bind(instance)
44
- block.call(1).should == [1, nil]
45
- block.call(1){2}.should == [1, 2]
289
+ block = BindableBlock.new { |a, &b| [a, (b&&b.call)] }.bind(instance)
290
+ assert_equal [1, nil], block.call(1)
291
+ assert_equal [1, 2], block.call(1){2}
46
292
  end
47
293
 
48
294
  specify "when given ordinal arguments at the start, it doesn't care about arity" do
49
- block = BindableBlock.new(klass) { |a| [a] }.bind(instance)
50
- block.call.should == [nil]
51
- block.call(1).should == [1]
52
- block.call(1, 2).should == [1]
295
+ block = BindableBlock.new { |a| [a] }.bind(instance)
296
+ assert_equal [nil], block.call
297
+ assert_equal [1], block.call(1)
298
+ assert_equal [1], block.call(1, 2)
53
299
  end
54
300
 
55
301
  specify 'when given optional args, it matches them up correctly' do
56
- block = BindableBlock.new(klass) { |a, b=1, c=2| [a, b, c] }.bind(instance)
57
- block.call.should == [nil, 1, 2]
58
- block.call(:a).should == [:a, 1, 2]
59
- block.call(:a, :b).should == [:a, :b, 2]
60
- block.call(:a, :b, :c).should == [:a, :b, :c]
61
- block.call(:a, :b, :c, :d).should == [:a, :b, :c]
302
+ block = BindableBlock.new { |a, b=1, c=2| [a, b, c] }.bind(instance)
303
+ assert_equal [nil, 1, 2] , block.call
304
+ assert_equal [:a, 1, 2] , block.call(:a)
305
+ assert_equal [:a, :b, 2] , block.call(:a, :b)
306
+ assert_equal [:a, :b, :c] , block.call(:a, :b, :c)
307
+ assert_equal [:a, :b, :c] , block.call(:a, :b, :c, :d)
62
308
  end
63
309
 
64
310
  specify 'splat acts as a catch all' do
65
- block = BindableBlock.new(klass) { |a, *rest| [a, rest] }.bind(instance)
66
- block.call.should == [nil, []]
67
- block.call(1).should == [1, []]
68
- block.call(1, 2).should == [1, [2]]
69
- block.call(1, 2, 3).should == [1, [2, 3]]
311
+ block = BindableBlock.new { |a, *rest| [a, rest] }.bind(instance)
312
+ assert_equal [nil, []] , block.call
313
+ assert_equal [1, []] , block.call(1)
314
+ assert_equal [1, [2]] , block.call(1, 2)
315
+ assert_equal [1, [2, 3]] , block.call(1, 2, 3)
70
316
  end
71
317
 
72
318
  specify "when given ordinal arguments at the end, it doesn't care about arity" do
73
- block = BindableBlock.new(klass) { |*a, b, &c| [a, b] }.bind(instance)
74
- block.call.should == [[], nil]
75
- block.call(1).should == [[], 1]
76
- block.call(1,2).should == [[1], 2]
77
- block.call(1,2,3).should == [[1, 2], 3]
319
+ block = BindableBlock.new { |*a, b, &c| [a, b] }.bind(instance)
320
+ assert_equal [[], nil] , block.call
321
+ assert_equal [[], 1] , block.call(1)
322
+ assert_equal [[1], 2] , block.call(1,2)
323
+ assert_equal [[1, 2], 3] , block.call(1,2,3)
78
324
 
79
- block = BindableBlock.new(klass) { |a=:a, b, c| [a, b, c] }.bind(instance)
80
- block.call.should == [:a, nil, nil]
81
- block.call(1).should == [:a, 1, nil]
82
- block.call(1, 2).should == [:a, 1, 2]
83
- block.call(1, 2, 3).should == [1, 2, 3]
84
- block.call(1, 2, 3, 4).should == [1, 2, 3]
325
+ block = BindableBlock.new { |a=:a, b, c| [a, b, c] }.bind(instance)
326
+ assert_equal [:a, nil, nil] , block.call
327
+ assert_equal [:a, 1, nil] , block.call(1)
328
+ assert_equal [:a, 1, 2] , block.call(1, 2)
329
+ assert_equal [1, 2, 3] , block.call(1, 2, 3)
330
+ assert_equal [1, 2, 3] , block.call(1, 2, 3, 4)
85
331
  end
86
332
 
87
333
 
88
334
  specify "when given complex arguments, it matches that shit up right" do
89
335
  proc = Proc.new { |a, b, c=1, d=2, *e, f| [a,b,c,d,e,f] }
90
- block = BindableBlock.new(klass, &proc).bind(instance)
91
- block.call.should == proc.call
92
- block.call(:a).should == proc.call(:a)
93
- block.call(:a,:b).should == proc.call(:a,:b)
94
- block.call(:a,:b,:c).should == proc.call(:a,:b,:c)
95
- block.call(:a,:b,:c,:d).should == proc.call(:a,:b,:c,:d)
96
- block.call(:a,:b,:c,:d,:e).should == proc.call(:a,:b,:c,:d,:e)
97
- block.call(:a,:b,:c,:d,:e,:f).should == proc.call(:a,:b,:c,:d,:e,:f)
98
- block.call(:a,:b,:c,:d,:e,:f,:g).should == proc.call(:a,:b,:c,:d,:e,:f,:g)
336
+ block = BindableBlock.new(&proc).bind(instance)
337
+ assert_equal proc.call , block.call
338
+ assert_equal proc.call(:a) , block.call(:a)
339
+ assert_equal proc.call(:a,:b) , block.call(:a,:b)
340
+ assert_equal proc.call(:a,:b,:c) , block.call(:a,:b,:c)
341
+ assert_equal proc.call(:a,:b,:c,:d) , block.call(:a,:b,:c,:d)
342
+ assert_equal proc.call(:a,:b,:c,:d,:e) , block.call(:a,:b,:c,:d,:e)
343
+ assert_equal proc.call(:a,:b,:c,:d,:e,:f) , block.call(:a,:b,:c,:d,:e,:f)
344
+ assert_equal proc.call(:a,:b,:c,:d,:e,:f,:g) , block.call(:a,:b,:c,:d,:e,:f,:g)
345
+ end
346
+ end
347
+
348
+ describe 'instance_exec_b' do
349
+ it 'is only available if you require the file explicitly, since it monkey patches BasicObject' do
350
+ expect { method :instance_exec_b }.to raise_error NameError
351
+ require 'bindable_block/instance_exec_b'
352
+ method :instance_exec_b
353
+ end
354
+
355
+ it 'is on wherever instance_exec is on (BasicObject)' do
356
+ expect(method(:instance_exec_b).owner).to equal method(:instance_exec).owner
357
+ end
358
+
359
+ it 'provides an instance_exec like method, whose last argument is a block' do
360
+ a, b = 1, 2
361
+ result = instance.instance_exec_b(a, lambda { b }) { |ordinal, &block| "#{name}#{ordinal}#{block.call}" }
362
+ assert_equal "Carmen12", result
363
+ end
364
+
365
+ it 'passes no block if the last param is nil' do
366
+ a, b = 1, 2
367
+ result = instance.instance_exec_b(a, nil) { |ordinal, &block| "#{name}#{ordinal}#{!!block}" }
368
+ assert_equal "Carmen1false", result
369
+ end
370
+
371
+ it 'the block can be blocky things (can be called and put into the block slot), otherwise raises an ArgumentError' do
372
+ expect { instance.instance_exec_b(1) { |*| } }.to raise_error ArgumentError, /block/
373
+ expect { instance.instance_exec_b(1, :abc) { |*| } }.to raise_error ArgumentError, /block/ # has to_proc, but not call
374
+ o = Object.new
375
+ def o.call(*)
376
+ 2
377
+ end
378
+ expect { instance.instance_exec_b(1, o) { |*| } }.to raise_error ArgumentError, /block/ # has call, but not to_proc
379
+ def o.to_proc
380
+ Proc.new { 3 }
381
+ end
382
+ assert_equal 4, instance.instance_exec_b(1, o) { |n, &b| n + b.call }
383
+ end
384
+
385
+ it 'doesn\'t need ordinal args' do
386
+ assert instance.instance_exec_b(lambda {}) { |&block| !!block }
387
+ refute instance.instance_exec_b(nil) { |&block| !!block }
388
+ end
389
+
390
+ it 'Doesn\'t define methods on the class' do
391
+ class BasicObject
392
+ old_method_added = singleton_class.method(:method_added)
393
+ def self.method_added(method_name)
394
+ raise "METHOD ADDED: #{method_name}"
395
+ end
396
+ instance_exec_b(lambda {}) { }
397
+ define_singleton_method(:method_added, &old_method_added)
398
+ end
99
399
  end
100
400
  end
101
401
  end
metadata CHANGED
@@ -1,57 +1,78 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bindable_block
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5.1
5
- prerelease:
4
+ version: 0.0.7
6
5
  platform: ruby
7
6
  authors:
8
7
  - Josh Cheek
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2012-06-18 00:00:00.000000000 Z
13
- dependencies: []
11
+ date: 2014-07-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rspec
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 3.0.0
20
+ - - "~>"
21
+ - !ruby/object:Gem::Version
22
+ version: '3.0'
23
+ type: :development
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: 3.0.0
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '3.0'
14
33
  description: instance_exec can't pass block arguments through. Use a bindable block
15
- instead.
34
+ instead (there is also an optional core extension instance_exec_b, which will mimic
35
+ the instance_exec interface, but also allow you to pass the block).
16
36
  email:
17
37
  - josh.cheek@gmail.com
18
38
  executables: []
19
39
  extensions: []
20
40
  extra_rdoc_files: []
21
41
  files:
22
- - .gitignore
42
+ - ".gitignore"
23
43
  - Gemfile
24
44
  - LICENSE
25
45
  - README.md
26
46
  - README.mountain_berry_fields.md
27
- - Rakefile
28
47
  - bindable_block.gemspec
29
48
  - lib/bindable_block.rb
49
+ - lib/bindable_block/arg_aligner.rb
50
+ - lib/bindable_block/bound_block.rb
51
+ - lib/bindable_block/instance_exec_b.rb
30
52
  - lib/bindable_block/version.rb
31
53
  - spec/bindable_block_spec.rb
32
54
  homepage: https://github.com/JoshCheek/bindable_block
33
55
  licenses: []
56
+ metadata: {}
34
57
  post_install_message:
35
58
  rdoc_options: []
36
59
  require_paths:
37
60
  - lib
38
61
  required_ruby_version: !ruby/object:Gem::Requirement
39
- none: false
40
62
  requirements:
41
- - - ! '>='
63
+ - - "~>"
42
64
  - !ruby/object:Gem::Version
43
- version: '0'
65
+ version: '2.1'
44
66
  required_rubygems_version: !ruby/object:Gem::Requirement
45
- none: false
46
67
  requirements:
47
- - - ! '>='
68
+ - - ">="
48
69
  - !ruby/object:Gem::Version
49
70
  version: '0'
50
71
  requirements: []
51
72
  rubyforge_project:
52
- rubygems_version: 1.8.11
73
+ rubygems_version: 2.2.2
53
74
  signing_key:
54
- specification_version: 3
75
+ specification_version: 4
55
76
  summary: Allows you to bind procs to instances of classes
56
77
  test_files:
57
78
  - spec/bindable_block_spec.rb
data/Rakefile DELETED
@@ -1,2 +0,0 @@
1
- #!/usr/bin/env rake
2
- require "bundler/gem_tasks"