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 +7 -0
- data/README.md +42 -2
- data/README.mountain_berry_fields.md +44 -2
- data/bindable_block.gemspec +5 -1
- data/lib/bindable_block.rb +49 -48
- data/lib/bindable_block/arg_aligner.rb +30 -0
- data/lib/bindable_block/bound_block.rb +61 -0
- data/lib/bindable_block/instance_exec_b.rb +16 -0
- data/lib/bindable_block/version.rb +2 -2
- data/spec/bindable_block_spec.rb +364 -64
- metadata +35 -14
- data/Rakefile +0 -2
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
|
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
|
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
|
|
data/bindable_block.gemspec
CHANGED
@@ -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
|
data/lib/bindable_block.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
40
|
-
|
41
|
-
|
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
|
-
|
48
|
-
|
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
|
57
|
-
|
30
|
+
def arity
|
31
|
+
@original_block.arity
|
58
32
|
end
|
59
33
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
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
|
-
|
67
|
-
|
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.
|
1
|
+
class BindableBlock < Proc
|
2
|
+
VERSION = "0.0.7"
|
3
3
|
end
|
data/spec/bindable_block_spec.rb
CHANGED
@@ -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
|
-
|
9
|
-
|
10
|
-
|
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
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
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
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
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
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
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
|
44
|
-
|
45
|
-
block.call(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
|
50
|
-
|
51
|
-
block.call(1)
|
52
|
-
block.call(1, 2)
|
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
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
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
|
66
|
-
|
67
|
-
|
68
|
-
block.call(1, 2)
|
69
|
-
|
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
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
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
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
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(
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
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
|
-
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:
|
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: '
|
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:
|
73
|
+
rubygems_version: 2.2.2
|
53
74
|
signing_key:
|
54
|
-
specification_version:
|
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