bindable_block 0.0.5.1 → 0.0.7

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: 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"